Java程序优化.png

    Java程序优化 - 图1

    • 字符串优化

      • String对象
        实现主要由3部分组成:char数组、偏移量、String的长度

        • 3个基本特点

          • 不变性 - 指对象一旦创建,则不能在对它进行改变。

          • 针对常量池的优化 - 两个对象拥有相同值时,他们只引用常量池中的同一拷贝

          • 类的final定义 - 有助于虚拟机寻找机会,内联所有的final方法,从而提高系统效率

        • subString()方法的内存泄漏
          底层采用new String()构造了一个新的对象,只是偏移量和长度修改,实际的内容没有变,采用的是一种空间换时间的策略

        • 字符串分割和查找

          • split

          • StringTokenizer

          • indexOf() && subString()

          • charAt() - 判断字符串是否为指定开头或者结尾

      • StringBuffer和StringBuilder
        由于String对象是不可变对象,因此对字符串操作时,都会创建新的对象。
        JDK提供了创建和修改字符串的工具

        • String常量的累加操作 - Java在编译时就做了充分的优化。

        • String变量的累加操作 - Java在编译的时候会转StringBuilder来累加。

        • 构建超大的String对象 - 编辑器不一定够聪明
          string = string +i
          string = string.concat(String.valueOf(i))
          sb.append(i) —- StringBuilder

        • StringBuffer和Stringbuilder的选择
          StringBuffer所有的方法都做了同步 Synchronized 保证线程安全性
          而StringBuilder没有做同步, 存在线程安全性问题

        • 容量参数
          默认容量大小为16,当容量不满足的时候,会进行扩容。
          扩容规则是 现有的长度翻倍(value.length + 1) *2
          建议明确长度的时候直接指定,避免频繁的内存复制

    • 核心数据结构

      • List
        ArrayList和Vector都采用动态数组的方式实现,主要差别在于Vector的方法都是线程安全的,LinkedList采用循环双向链表的结构

        • 三种数据结构

          • ArrayList

          • Vector

          • LinkedList

            • 元素内容

            • 前驱表项

            • 后驱表项

        • 增加元素到列表尾端

          • ArrayList添加元素的时候,会去判断容量是否满足,不满足进行容量扩容。扩容到原始容量的1.5倍,采用system.arraycopy方法复制数组。

          • LinkedList内部采用链表的最后端,没有扩容操作

        • 增加元素到列表任意位置

          • ArrayList需要进行重新排列,而LinkedList尾部插入数据和任意位置插入数据是一样,不会导致性能下降
        • 删除任意位置元素

          • ArrayList删除需要进行数组重组,删除越前面的数据,开销越大。删除越后面的数据,开销越小。

          • LinkedList删除指定位置的元素,会通过index和size比较。如果删除的位置处于前半段,则从前往后找,若其位置处于后半段,则从后往前查找。效率比较低

        • 容量参数

          • 有效地评估ArrayList数组大小初始化值的情况下,指定容量大小能对其性能有比较大的提升
        • 遍历列表
          推荐使用迭代器的方式

          • forEach

          • 迭代器

          • for

      • Map

        • HashMap - 无序的

          • 高性能

            • hash算法必须是高效的

            • hash值到内存地址的算法是快速的

            • 根据内存地址可以直接取得对应的值

          • hash冲突
            需要存放至HashMap的两个元素通过Hash计算查找出内存中的同一地址

            • 新的Entry以来会被安放在对应的索引下标内,并替换原来的值,同时,为了保证旧值不会丢失,会将新的Entry的next指向旧值。

            • 基于HashMap的实现机制,只要hashCode()和hash()方法实现得足够好,能够尽可能地减少冲突的产生,对于HashMap的操作几乎等价于对数组的随机访问操作

          • 容量参数

            • 默认情况下,初始化长度为16,负载因子为0.75

            • 负载因子=元素个数/内部数组总大小

            • 内部还定义了一个threshold变量,定义为当前数组总容量和负载因子的乘积,表示HashMap的阙值

            • 设置初始化大小和负载因子,可以有效减少Hash Map的扩容次数

        • LinkedHashMap - 有序的

          • 提供两种类型的顺序
            accessOrder 属性控制

            • 元素插入时的顺序 - false

            • 最近访问的顺序 - true

          • 设置accessOrder属性为true,采用迭代器的方式访问元素会报ConcurrentModificationException,因为get()方法会对数组进行重排
            迭代器的场景,对数据结构进行修改操作会报如上错误

        • TreeMap

          • 能够对元素进行排序

          • 两种方式

            • 构造函数中注入一个Comparator

            • 使用一个实现了Comparable接口的key

      • Set
        Set集合中的元素是不能重复的

        • HashSet — > HashMap

        • LinkedHashSet —- > LinkedHashMap

        • TreeSet —-> TreeMap

      • 优化集合访问

        • 分离循环中被重复调用的代码
          for(int i=0,length=array.length;i<length;i++)

        • 省略相同的操作
          s = collection.get(i) 抽一个局部变量

        • 减少方法调用

      • RandomAccess接口

        • 标志接口,本身没有提供任何方法,任何实现

        • 主要目的是标识那些可支持快速随机访问的List实现

        • 任何一个基于数组的List都实现了此接口

    • 使用NIO提升性能

      • 特性

        • 为所有的原始类型提供(Buffer)缓存支持

        • 使用Java.nio.charset.Charset作为字符集编码解决方案

        • 增加通道(Channel)对象,作为新的原始I/O抽象

        • 支持缩合内存映射文件的文件访问接口

        • 提供了基于Selector的异步网络I/O

      • 两个重要的组件

        • 缓冲Buffer - 连续的内存块, 数据的中转地

        • 通道Channel - 表示缓存数据的源头或者目的地

      • Buffer

        • 3个重要参数

          • 位置 - position

          • 容量 - capacity

          • 上限 - limit

        • 相关操作

          • allocate()从堆中分配缓冲区,或者从一个既有数组中创建缓冲区

          • 重置和清空缓冲区
            三个方法并不是清空buffer的内容,而是重置了buffer的各项标识位

            • rewind

            • clear

            • flip

          • 读/写缓冲区

            • get

            • put

          • 标志缓冲区

            • mark用于记录当前的位置

            • reset用于恢复到mark所在的位置

          • 复制缓冲区

            • duplicate
          • 缓冲区分片
            将在现有的缓冲区中,创建新的子缓冲区,子缓冲区和父缓冲区共享数据。有助于将系统模块化

            • slice
          • 只读缓冲区

            • asReadOnlyBuffer 保证核心数据的安全
          • 文件映射内存

            • FileChannel.map
          • 处理结构化数据 - 散射和聚集

            • ScatteringByteChannel

            • GatheringByteChannel

        • 直接内存访问 - DirectBuffer

          • DirectBuffer继承自ByteBuffer

          • 普通的ByteBuffer仍然在JVM堆上分配空间,受到最大堆的限制。而DirectBuffer直接分配在物理内存中。

          • DirectBuffer的读取性能优于ByteBuffer,但是对于创建和效率性能比较差

    • 引用类型

      • 强引用 - FinalReference

        • 可以直接访问目标对象

        • 所指向的对象在任何时候都不会被系统回收。JVM宁愿抛出OOM异常,也不回收强引用所指向的对象

        • 可能导致内存泄漏

      • 软引用 - SoftReference

        • 一个持有软引用的对象,不会很快被JVM回收,JVM会根据当前堆的使用情况来判断何时回收。当堆使用率临近阙值时,才会去回收软引用对象。
      • 弱引用 - WeakReference

        • 系统gc时,只要发现弱引用,不管系统堆空间是否足够,都会将对象进行回收

        • 垃圾回收的线程通常优先级别很低,并不一定很快地发现持有弱引用的对象

      • 虚引用 - PhantomReference

        • 一个持有虚引用的对象,和没有引用几乎是一样的,随时都可能被垃圾回收器回收

        • 作用在于跟踪垃圾回收过程,清理被销毁对象的相关资源

      • WeakHash Map

        • 弱引用的一种类型应用,可以作为简单的缓存表解决方案

        • 内部实现:Entry继承扩展了WeakReference

    • 有助于改善性能的技巧

      • 慎用异常
        比如for循环中使用try-catch。可以将try-catch挪到for循环外部

      • 使用局部变量
        局部变量的访问速度远远高于类的成员变量

      • 位运算代替乘除法
        比如乘除2 可以使用 <<= 1 >>=1

      • 替换switch
        利于数组来替换switch的值

      • 一维数组代替二维数组
        提取数组长度到变量,以减少重复循环时的开销

      • 提取表达式
        将公共的操作提取表达式,方便复用

      • 展开循环
        减少循环次数,循环次数+3,内部实现+3

      • 布尔运算符代替位运算

      • 使用System.arrayCopy()数组复制
        底层是native

      • 使用Buffer进行I/O操作

      • 使用clone()代替new
        clone只会进行浅拷贝,只会复制对象引用,对于值的 深拷贝需要重新实现clone

      • 静态方法替代实例方法
        工具类的方法尽量都使用static