Java性能优化系列之二-程序优化

Java性能优化系列之二--程序优化

常用的程序设计优化技巧:

1、字符串优化处理

(1)String类的特点:不变性、针对常量池的优化(String.intern()方法的意义)

(2)subString方法的内存泄漏:

(3)字符串分割和查找不要使用split函数,效率低,而是使用StringTokenizer或者indexOf结合subString()函数完成分割。

(4)用charAt()方法代替startWith()方法。

(5)对于静态字符串或者变量字符串的连接操作,Java在编译的时候会进行彻底的优化,将多个连接操作的字符串在编译时合成一个单独的字符串,而不是生成大量的String实例。只生成一个对象。

(6)在无需考虑线程安全情况下尽量使用StringBuilder

(7)StringBufferStringBuilder初始化的时候都可以设置一个初始值,默认是16B。如果字符串的长度大于16B的时候,则需要进行扩容。扩容策略是将原有的容量大小翻倍,以新的容量申请内存空间,建立char数组,然后将数组中的内容复制到这个新的数组中,使用Arrays.copyOf()函数。因此,如果能预先评估StringBuilder的大小,则可以节省这些复制操作,从而提高系统的性能。

2、List接口

1ArrayListVector的区别:它们几乎使用了相同的算法,它们的唯一区别是对多线程的支持。ArrayList是不安全的,而Vector是线程安全的。

2LinkedListArrayList的区别:

    |---1linkedList采用链表实现,适合于数据删除和插入非常频繁的情况,不适合随机访问。

    |---2ArrayList采用数组实现,适用于随机查找和顺序读的情况,不适合删除和插   入数据非常频繁的场景。

(3)基于数组的List都会有一个容量参数。当ArrayList所存储的元素容量超过其已有大小的时候就会进行扩容,数组的扩容会导致整个数组进行一次内存复制。因此合理的数组大小会减小数组扩容的次数从而提高系统性能。

(4)遍历列表的时候尽量使用迭代器,速度块。

2、Map接口:

(1)HashMap的实现原理:简单的说,HashMap就是将keyhash算法,然后将hash值映射到内存地址,直接取得key所对应的数据。在HashMap中,底层数据结构使用的是数组,所谓的内存地址指的是数组的下标索引。

(2)容量参数与扩容:默认情况下,hashmap的初始容量为16,负载因子为0.75,也就是说当hashmap的实际容量达到了初始容量*负载因子(hashmap内部维护的一个threshold值)的时候,hashmap就会进行扩容。在扩容时,会遍历整个hashmap,因此应该设置合理的初始大小和负载因子,可以减小hashmap扩容的次数。

(3)LinkedHashMap--有序的HashMapHashMap的最大缺点是其无序性,被存入到Hashmap 中的元素,在遍历HashMap的时候,其输出不一定按照输入的顺序,而是HashMap会根据hash算法设定一个查找高效的顺序。如果希望保存输入顺序,则需要使用LinkedHashMapLinkedHashmap在内部又增加了一个链表,用于保存元素的顺序。

(4)LinkedList可以提供两种类型的顺序:一个是元素插入时候的顺序,一个是最近访问的顺序。注意:LinkedHashMap在迭代过程中,如果设置为按照最后访问时间进行排序,即:每当使用get()方法访问某个元素时,该元素便会移动到链表的尾端。但是这个时候会出现异常,因此,LinkedHashMap工作在这种模式的时候,不能在迭代器中使用get()操作。

(5)关于ConcurrentModificationException:该异常一般会在集合迭代过程中被修改时抛出。因此,不要在迭代器模式中修改集合的结构。这个特性适合于所有的集合类,包括HashMapVectorArrayList等。

(6)TreeMap--如果要对元素进行排序,则使用TreeMapkey实现自定义排序,有两种方式:在TreeMap的构造函数中注入一个Comparator或者使用一个实现了Comparablekey

(7)如果需要将排序功能加入HashMap,最好是使用Treemap而不是在应用程序自定义排序。

(8)HashMap基于Hash表实现,TreeMap基于红黑树实现。

3MapSet的关系:

1)所有Set的实现都只是对应的Map的一种封装,其内部维护一个Map对象。即:Set只是相应的MapValue是一种特殊的表现形式的一种特例。

2Set主要有三种实现类:HashSetLinkedHashSetTreeSet。其中HashSet是基于Hash的快速元素插入,元素之间无序。LinkedHashSet同时维护着元素插入顺序,遍历集合的时候,总是按照先进先出的顺序排序。TreeSet是基于红黑树的实现,有着高效的基于元素Key的排序算法。

4、优化集合访问代码:

  (1)、分离循环中被重复调用的代码:例如,for循环中使用集合的size()函数,则不应该把这个函数的调用放到循环中,而是放到循环外边、

   (2)、省略相同的操作:

5RandomAccess接口:通过RandomAccess可知道List是否支持随机快速访问。同时,如果应用程序需要通过索引下标对List做随机访问,尽量buyaoshiyongLinkedListArrayList或者Vector可以。

6JavaNIO的特性:

  1、为所有的原始类型提供Buffer支持。

  2、使用Java.nio.charset.Charset作为字符编码解码解决方案。

  3、增加通道抽象代替原有的IO流抽象。

  4、支持锁和内存映射文件的文件访问接口。

  5、提供基于Selector的异步网络IO

7JavaNIO的使用。Channel是一个双向通道,即可读也可写。应用程序不能直接操作Channel,必须借助于Buffer。例如读数据的时候,必须把数据从通道读入到缓冲区,然后在缓冲区中进行读取。以文件读取为例,首先通过文件输入流获得文件通道,然后把文件通道的内容读入到缓冲区中,然后就可以对缓冲区操作。

8Buffer的基本原理:

1Buffer的创建:Buffer的静态allocate(int size)方法或者Buffer.wrap(byte[]src)

2Buffer的工作原理:三个变量:position,代表当前缓冲区的位置,写缓冲区的时候,将从position的下一个位置写数据。Capacity,代表缓冲区的总容量上限。Limit,缓冲区的实际上限,也就是说,读数据的时候,数据即是从positionlimit之间的数据

3flip操作:limit=position,position=0,一般是在读写切换的时候使用。写完数据之后,需要限定下有效数据范围,才能读数据;

4clear操作:position-0limit=capacity.。为重新写入缓冲区做准备。

5rewind操作:position=0,为读取缓冲区中有效数据做准备,一半limit已经被合理设置。

9、读写缓冲区:

1public byte get():顺序读取缓冲区的一个字节,position会加一

2public Buffer get(byte[]dst):将缓冲区中的数据读入到数组dst中,并适当的移动position

3public byte get(int index):得到第index个字节,但不移动posoiion

4public ByteBuffer put(byte b):将字节b放入到缓冲区中,并移动position

5public ByteBuffer put(int index,byte b):将字节b放到缓冲区的index位位置

6pubglic final ByteBuffer(byte[]src):将字节数组src放到缓冲区中。

10、标志缓冲区:类似于一个书签的功能,在数据的处理过程中,可随时记录当前位置。然后在任意时刻,回到这个位置。Mark用于记录当前位置,reset用于恢复到mark所在的位置、

11、复制缓冲区:使用Bufferduplicate方法可以复制一个缓冲区,副本缓冲区和原缓冲区共享一份空间但是有有着独立的positioncapacitylimit值。

20、缓冲区分片:缓冲区分片使用slice方法实现。它将在现有的缓冲区中,创建的子缓冲区。子缓冲区和父缓冲区共享数据。这个方法有助于将系统模块化。缓冲区切片可以将一个大缓冲区进行分割处理,得到的子缓冲区都具有缓冲的缓冲区模型结构;因此。这个操作有助于系统的模块化。

12、只读缓冲区:只读缓冲区可以保证核心数据的安全,如果不希望数据被随意篡改,返回一个只读缓冲区是很有帮助的。

13、文件映射到内存:NIO提供了一种将文件映射到内存的方法进行IO操作,这种方法比基于流IO快很多。这个操作主要由FileChanne.map()操作。使用文件内存的方式,将文本通过FileChannel映射到内存中。然后从内存中读取数据。同时,通过修改Buffer,将对内存中数据的修改写到对应的硬盘文件中。

14、处理结构化数据:散射和聚集。散射就是将数据读入到一组bytebuffer中,而聚集正好相反。通过ScatteringByteChannelGatheringByteChannel可以简化对结构数据的操作。

15、直接内存访问:DirectBuffer直接分配在物理内存中,并不占用对空间,因此也不受对空间限制。DirectBuffer的读写操作比普通Buffer块,因为DirectBuffer直接操纵的就是内核缓冲区。

16、引用类型:强、软、若、虚四种引用类型。

   WeakHashMap:是弱引用的一中典型应用,它使用弱引用作为内部数据的存储方案。可以作为简单的缓存表解决方案。

如果在系统中,需要一张很大的Map表,Map中的表项作为缓存之用。这也意味着即使没能从该Map中取得相应地数据,系统也可以通过选项方案获取这些数据,虽然这样会消耗更多的时间,但是不影响系统的正常运行。这个时候,使用WeakHashMap是最合适的。因为WeakHashMap会在系统内存范围内,保存所有表项,而一旦内存不够,在GC时,没有被引用的又会很快被清除掉,避免系统内存溢出。

17、有助于改善系统性能的技巧:

   1、慎用异常:for循环中使用try-catch会大大降低系统性能

   2、使用局部变量:局部变量的访问速度远远高于类的静态变量的访问速度,因为类的 变量是存在在堆空间中的。

   3、位运算代替乘除法:右移代表除以二、左移代表乘以二。

   4、有的时候考虑是否可以使用数组代替位运算。

   5、一维数组代替二维数组。

   6、提取表达式:尽可能让程序少做重复的计算,尤其要关注在循环体的代码,从循环提中提取重复的代码可以有效的提升系统性能。

参考书籍:

Java程序性能优化-让你的Java程序更块、更稳定 葛一鸣

Effctive java 第二版