Java程序性能优化

Java程序性能优化

最近在做code review时,也对这段时间写的代码做了一次全身“体检”,对于结果,受益匪浅,至少知道目前还有需要提升的空间,所以整理了这次“体检结果”,并结合自身体会以及程序优化的重要性,增加了一些Java程序有必要需要知道的优化细节,如果有不对之处,还请各位指正,当然了,程序优化道路千千万,希望各位有什么好的优化建议,还望请教。

程序优化的目标

首先,程序的优化,除了要消除错误之外,还有一些不易发觉的细节,这些细节可能不影响你现有代码的运行,从性能上来说,好像也看不出什么差别,但是长此以往,不仅对整个系统会造成不良影响,也会对后期维护带来更大的成本,更重要的是,会对我们程序猿的编程思维、编程习惯等造成后患。所以,我们在写代码的时候,从源头上开始注意各个细节,权衡并使用最佳的选择,将会很大程度上提高程序的性能,提高效率。那么既然时程序性能优化,当然我们的优化目标就是

  • 从时间上提高运行效率
  • 从空间上减小性能损耗

代码优化细节

部分参考自《Effective Java》以及《阿里巴巴Java开发手册》,关于命名规范等在此不做讨论,另外,也可以在IDE中下载安装Alibaba Java Coding Guidelines插件,时刻提醒自己。

1、静态工厂方法代替构造器

我们通常在程序中创建对象,一般都是使用new关键字,通过构造器来创建对象,但是也有的是通过静态方法来创建对象,比如下面的将boolean类型转换为Boolean类型。

public static Boolean valueOf(boolean b) {
    return (b ? TRUE : FALSE);
}

这样做的好处就是,不必每次调用的时候都去创建对象,减少创建对象开辟的空间,在一定程度上优化了内存消耗。这也是《Effective Java》一书中推荐的写法。

2、尽量做到重用对象

比如String对象,在做字符串拼接的时候,很多人喜欢用+号拼接,这样做其实是很消耗内存的,因为每次拼接的时候,并不是在原有基础上进行拼接,而是会创建一个新的对象,在进行拼接,而拼接后的又是一个新的对象,如果出现大量字符串拼接的话,这无疑将会给程序的性能带来很大的影响。所以在做字符串拼接的时候,我们应尽量使用StringBuilder或者StringBuffer。

3、及时释放资源

很多时候,我们在进行数据连接操作,或者是IO操作时,应在使用结束后及时关闭以释放资源。

4、减少变量的重复计算、方法的重复调用

在调用方法的时候,如果在循环中调用同一个方法,即便该方法消耗很小,那也应该减小这种不必要的消耗。比如最常见的写法:

List<String> list = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
    //todo
}

在上述代码中,循环调用size()方法,如果size()结果很大,那肯定会造成一些不必要的损耗。那么建议换成如下写法:

List<String> list = new ArrayList<>();
int size = list.size();
for (int i = 0; i < size; i++) {
    //todo
}
5、必要时采用懒加载策略

看下面一段代码:

List<String> list = new ArrayList<>();
    String str = "world";
    for (String item : list) {
        if ("hello".equals(item)) {
            list.add(str);
        }
    }

咋一看,好像并没有什么问题。的确是没有什么问题,但是细想一下,如果list集合中并没有“hello”这个字符串,那我们岂不是在内存中白白占用了一块地创建“world”?所以是否应换成如下写法呢:

List<String> list = new ArrayList<>();
    for (String item : list) {
        if ("hello".equals(item)) {
            String str = "world";
            list.add(str);
        }
    }
6、底层数组实现的集合框架,尽量指定存储容量

在操作如ArrayList、StringBuilder、HashMap之类的集合框架时,如果能知道大概要存储多少数据,那应尽量在初始化容器的时候,指定容器的存储容量。
就拿StringBuiler来说,当它达到最大容量的时候,便会进行扩容,将自身容量扩大到原来的2倍加2,在扩容的时候,StringBuilder会创建一个新的字符串数组,然后将旧的字符数组拷贝到新的字符数组中,这是一个很耗费性能的操作。所以,如果我们在预先知道存储容量的时候,在创建StringBuilder的时候,应指定容量大小。

7、尽量避免在循环内创建对象
List<String> list = new ArrayList<>();
int size = list.size();
for (int i = 0; i < size; i++) {
    Object o = new Object();
    //todo
}

很明显,这样做,会创建size份对象,占用大量内存空间,所以必要的话,可改成:

List<String> list = new ArrayList<>();
int size = list.size();
Object o = null;
for (int i = 0; i < size; i++) {
    o = new Object();
    //todo
}
8、性能和线程安全性之间的选择

在Java中提供了很多集合相关的类给我们使用,并且这些集合类各有优缺点,在此需要说明的是,在使用集合框架类时,如果是单线程或者对线程安全性没有太大要求,那么尽量使用ArrayList、HashMap、StringBuilder等,而在对线程安全性有高要求的话,应尽量使用Vector、HashTable、StringBuffer等。

在JDK1.8以后,提供了更多在性能和线程安全方面的选择,后续在Java集合框架系列中会说明。

9、减少静态变量的使用

当某个对象被定义为static的变量所引用,那么gc通常是不会回收这个对象所占有的堆内存的,如:

public class Test {
    private static Object o = new Object();  
}

此时静态变量o的生命周期与Test类相同,如果Test类不被卸载,那么引用o指向的Object对象会常驻内存,直到程序终止。

10、集合中选择最佳的方式进行遍历操作

在集合框架中,实现了RandomAccess接口的类,支持快速随机访问,在遍历时,应使用普通for循环的方式,而没有实现RandomAccess接口的类,应该使用iterator的方式进行遍历。可参考另一篇文章:ArrayList为什么要实现RandomAccess接口?

本文转自https://www.jianshu.com/p/93472e9bce81

原文地址:https://www.cnblogs.com/snake107/p/11939034.html