gofor's blog

  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

  • 搜索

Java 性能调优(一)

发表于 2018-12-15 更新于 2020-01-10 分类于 技术 阅读次数: Valine:

造成系统瓶颈计算资源的因素有很多,包括CPU资源的调度、内存的读写、磁盘IO、网络IO、数据库、竞争锁、异常等。而系统性能的衡量指标有响应时间、启动时间、执行时间、执行速度、磁盘吞吐量、网络吞吐量、负载承受能力等。那么,从软件的角度看,性能优化的目标有,编写更高效的代码、使用更高效的算法、减少竞争锁、分布式集群、微服务等。而性能的优化策略有,用空间换时间、用时间换空间、优化代码、并行处理等。系统的优化来自三个方面,基础技术、层次方面、架构方面。

Java API调用优化建议

面向对象及基础类型
  1. 采用clone()方式创建对象:clone()是Object类中的方法,使用时需实现Cloneable接口。当我们使用new关键字创建实例的时候,构造函数中所有构造参数都会被自动调用,而clone()不会调用任何构造函数,避免参数的初始化,在保留原有信息的基础上,创建速度更快。

    • 拷贝对象返回的是一个新对象,而不是原来对象的引用地址。
    • 拷贝对象与new关键字操作符返回的新对象的区别是,这个拷贝已经包含了一些原来对象的信息,而不是对象的初始化信息。
  2. 避免对boolean的判断:boolean被定义为存储8位(1字节)数值的形式,但只能是true或者false。在使用boolean时,避免使用等于判断,准确的说是不要使用3=3 == true这种形式的判断。

  3. 多用条件操作符:大部分时候,我们在代码中比较多用if(...) return; else return;这种形式的判断,但是条件使用太多,对代码的可读性带来影响,因此一些条件语句可以替换为诸如return isdone?0:1这种形式。

  4. 静态(static)方法代替实例方法:为了方便的实现多态的支持,实例方法需要维护一张类似虚拟函数导向表的结构,因此,在调用实例方法时会消耗更多的系统资源,使用类加载时就会初始化的静态方法会更快一些。静态方法和实例方法的两点区别:

    • 在外部调用静态方法时,可以使用“类名.方法名”或“对象名.方法名”的方式,而实例方法只能通过后者调用,即静态方法无需创建对象。
    • 静态方法在访问本类成员时,只允许访问静态成员,而不允许访问实例成员变量和实例方法,,实例方法则无此限制。
  5. 有条件的使用final关键字:final关键字可以修饰类、变量、方法,在使用匿名内部类的时候,经常会用到final关键字,如String类。使用final可以锁定方法,以防止任何继承类修改它的含义,在早期的Java版本中,会将final方法转为内嵌调用,JDK6之后无需此优化。

  6. 避免不需要的instanceof操作:instanceof关键字是Java的一个二元操作符,和“==”“>”类似,使用instanceof比较左边的对象是否是右边类的实例,会返回true或false。合理利用多态特性,可以避免instanceof的使用。

  7. 避免子类中存在父类转换:Java中,所有子类隐含的“等于”父类,因此在子类中无需再转换。

  8. 多使用局部变量:调用方法时传递的参数在调用中创建的临时变量都被保存在栈(Stack)里面,因此读写速度更快。像静态变量、实例变量,它们在堆(Heap)中被创建,保留在堆中,读写速度较慢。

    • 实例对象属于对象的属性,使用时必须创建对象,其实例对象才会被分配空间,才能使用它。静态变量属于类,只要程序加载了类的字节码,不用创建任何实例对象,就会被分配空间。
  9. 使用效率最高的位运算:位运算表达式由操作数和位运算符组成,实现对整数类型的二进制数位运算。位运算符可以分为逻辑运算符(包括~、&、|、^)及移位运算符(包括>>、>>>、<<)。

    • >>>和 >>的区别是,在执行运算时,>>>运算符的操作数高位补0,而>>运算符的操作数高位移入原来的高位的值。
    • 右移以为相当于除以2,左移一位相当于乘以2(位运算速度高于乘除运算)。
    • 若进行位逻辑运算的两个操作数的长度不同,则返回值以数据长度较长的数据类型为准。
    • 按位异或可以不使用临时变量完成两个值的交换,也可以使某个整型数的特定位的翻转。
    • 按位与运算可以用来屏蔽特定的位,也可以用来取某个整型数中特定的位。
    • 按位或运算可以用来对某个整型数的特定位置的值置1.
  10. 用一维数组代替二维数组:数组的优点是随机访问性能好。二维数组的访问速度要优于一位数组,但是,二维数组占用空间是一维数组的10倍左右。在性能敏感的系统中使用二维数组,如果内存不足,尽量将二维数组替换为一维数组再处理。(二维数组是典型的空间换时间做法)

  11. 布尔运算代替位运算:使用位运算代替布尔运算会使系统多很多无效计算,比如a&&b&&c,当有一个为false时,会跳过后面的计算直接返回结果,但是位运算需要将所有的表达式都计算完才返回结果。

  12. 提取表达式优化:将方法中或代码块中的相同的表达式提取出来,避免重复计算,如a*b*c+d,没次计算都会消耗资源,提取出来只计算一次即可。

  13. 不要总是使用取反操作符(!):!取反操作符表示异或操作,使用起来方便,但是它会降低程序的可读性。

  14. 不要重复初始化变量:默认情况下,调用类的构造函数时,Java会把变量初始化为一个确定的值,例如将所有对象设置为null,因此,一般情况下,通过构造函数初始化对象。

  15. 在switch语句中使用字符串:在switch或case表达式中,值不能为null,否则会抛空指针异常。在编译时,将字符串hashcode()之后,转为与整数类型兼容的格式。

  16. 数字字面常量的改进:可以将十进制、十六进制转为二进制表示,比如数字9用二进制表示为0b001001。当一个数字太长的时候,使用_分隔符分开,比如5000000可以改成5_000_000。

  17. 优化变长参数方法的调用:一般情况下,方法的参数不宜过多,且最后一个参数为变长参数时,避免和泛型类型一起使用。

  18. 基本数据类型-空变量:通过显式的赋空变量,Eden(新生代的一个区域)就能在新对象创建之前获得自由空间,这样垃圾收集就会更快。

集合处理优化方案

  1. 集合中删除元素:List list = new ArrayList<~>()中,不能直接使用remove()方法删除元素,会发生错误,应该使用Iterator中的删除方法。
  2. 集合内部避免返回null:在需要返回数组或集合的方法中,如果需要返回空数据,返回大小为0的数组或集合,避免返回null(在调用方不判空情况下,返回null可能会发生NPE)。
  3. 使用迭代、流、并行流:JDK8及以上提供,可读性更好,不易出错,容易并行化。比如:forEach()是线程安全的。可以对其进行并行处理,stream替换为parallelStream(对性能造成严重影响)就可以执行过滤和统计操作。

字符串优化方案

  1. 善用subString()方法:String.subString()可以截取字符串,该方法适用于字符串长度较短时,当字符串长度很大时,会占用很大的内存,造成内存溢出,可以使用new String(smallStr.toCharArray())替代。因此,当需要截取的字符串长度总和远小于原始字符串文本长度时,使用new String(smallStr.toCharArray()),在大文本字符串处理中有很大优势。当需要截取的字符串长度总和大于原始文本字符串长度时,使用String.subString()可以达到共享内存的目的。
  2. 查找单个字符,用chart()代替startsWith()和endsWith():从性能角度来说,前者会快一些。
  3. 字符串相加时,仅一个字符,使用’’替代””:使用字符替代。
  4. 字符串切割:使用split()方式切分字符串时性能较差。对比发现,使用StringTokenizer解析字符串更快。StringTokenizer类允许一个应用程序进入一个令牌(Token),StringTokenizer类的对象在内部已经标示化的字符串中维持了当前位置。一些操作使得现有位置上的字符串提前得到处理,一个令牌的值是由获得其曾经创建StringTokenizer类对象的字符串返回的。
  5. 字符串连接:字符串连接的方式一般有string对象连接、concat方法、StringBuilder类。而StringBuffer对所有方法都做了同步处理,效率较低,但是在多线程环境下,StringBuilder不是线程安全,无法使用。StringBuilder和StringBuffer两者底层实现都是char[]。对比,性能依次为StringBuilder > concat > string+'a'。
  6. 正则表达式:正则表达式不是万能的,根据实际情况使用。

其它优化

  1. 循环优化:应尽可能减少循环。
  2. 使用arrayCopy():数组复制是一个使用频率很高的功能,System.arrayCopy()函数是native函数,通常native函数性能优于普通函数,ArrayList中大量使用该函数。arrayCopy的本质是让处理器利用一条指令处理一个数组中的多个记录,有点像汇编语言里面的串操作指令,只需要指定头指针,然后开始循环即可,即执行一次指令,指针就后移一个位置,操作多少次就循环多少次。
  3. 使用Buffer进行IO操作:多使用缓冲区。
  • 本文作者: gofor
  • 本文链接: https://acehjm.github.io/2018/12/15/Java-性能调优(一)/
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
# java # 性能
Mybatis知识点梳理
Redis数据类型
gofor

gofor

Programming technology
14 日志
4 分类
14 标签
RSS
GitHub StackOverflow
Creative Commons
© 2016 – 2020 gofor
由 Hexo 强力驱动 v3.9.0
|
主题 – NexT.Pisces v7.3.0
0%