首页 » 编写高质量代码:改善Java程序的151个建议 » 编写高质量代码:改善Java程序的151个建议全文在线阅读

《编写高质量代码:改善Java程序的151个建议》第10章 性能和效率

关灯直达底部

快,快点,再快点……大脑已经跟不上鼠标!

——佚名

在这个快餐时代,系统一直在提速,从未停步过,从每秒百万条指令的CPU到现在的每秒万亿条指令的多核CPU,从最初发布一个帖子需要等待N小时才有回复到现在的微博,一个消息在几分钟内就可以传遍全球;从N天才能完成的一次转账交易,到现在的即时转账——我们进入了一个光速发展的时代,我们享受着,也在被追逐着——榨干硬件资源,加速所有能加速的,提升所有能提升的。

建议132:提升Java性能的基本方法

Java从诞生之日起就被质疑:字节码在JVM中运行是否会比机器码直接运行的效率会低很多?很多技术高手、权威网站都有类似的测试和争论,从而来表明Java比C(或C++)更快或效率相同。此类话题我们暂且不表(这类问题的争论没完没了,也许等到我们退休的时候,还想找个活动脑筋的方式,此类问题就会是最好的选择),我们先从如何提高Java的性能方面入手,看看怎么做才能让Java程序跑得更快,效率更高,吞吐量更大。

(1)不要在循环条件中计算

如果在循环(如for循环、while循环)条件中计算,则每循环一遍就要计算一次,这会降低系统效率,就比如这样的代码:


//每次循环都要计算count*2

while(i<count*2){

//Do Something

}

应该替换为:

//只计算一遍

int total=count*2;

while(i<total){

//Do Something

}


(2)尽可能把变量、方法声明为final static类型

假设要将阿拉伯数字转换为中文数字,其定义如下:


public String toChineseNum(int num){

//中文数字

Stringcns={/"零/",/"壹/",/"贰/",/"叁/",/"肆/",/"伍/",/"陆/",/"柒/",/"捌/",/"玖/"};

return cns[num];

}


每次调用该方法时都会重新生成一个cns数组,注意该数组不会改变,属于不变数组,在这种情况下,把它声明为类变量,并且加上final static修饰会更合适,在类加载后就生成了该数组,每次方法调用则不再重新生成数组对象了,这有助于提高系统性能,代码如下。


//声明为类变量

final static Stringcns={/"零/",/"壹/",/"贰/",/"叁/",/"肆/",/"伍/",/"陆/",/"柒/",/"捌/",/"玖/"};

public String toChineseNum(int num){

return cns[num];

}


(3)缩小变量的作用范围

关于变量,能定义在方法内的就定义在方法内,能定义在一个循环体内的就定义在循环体内,能放置在一个try……catch块内的就放置在该块内,其目的是加快GC的回收。

(4)频繁字符串操作使用StringBuilder或StringBuffer

虽然String的联接操作(“+”号)已经做了很多优化,但在大量的追加操作上StringBuilder或StringBuffer还是比“+”号的性能好很多,例如这样的代码:


String str=/"Log file is ready……/";

for(int i=0;i<max;i++){

//此处生成三个对象

str+=/"log/"+i;

}

应该修改为:

StringBuilder sb=new StringBuilder(20000);

sb.append(/"Log file is ready……/");

for(int i=0;i<max;i++){

sb.append(/"log/"+i);

}

String log=sb.toString();


(5)使用非线性检索

如果在ArrayList中存储了大量的数据,使用indexOf查找元素会比java.utils.Collections.binarySearch的效率低很多,原因是binarySearch是二分搜索法,而indexOf使用的是逐个元素比对的方法。这里要注意:使用binarySearch搜索时,元素必须进行排序,否则准确性就不可靠了。

(6)覆写Exception的fillInStackTrace方法

我们在第8章中提到fillInStackTrace方法是用来记录异常时的栈信息的,这是非常耗时的动作,如果我们在开发时不需要关注栈信息,则可以覆盖之,如下覆盖fillInStackTrace的自定义异常会使性能提升10倍以上:


class MyException extends Exception{

public Throwable fillInStackTrace(){

return this;

}

}


(7)不建立冗余对象

不需要建立的对象就不能建立,说起来很容易,要完全遵循此规则难度就很大了,我们经常就会无意地创建冗余对象,例如这样一段代码:


public void doSomething(){

//异常信息

String exceptionMsg=/"我出现异常了,快来就救我!/";

try{

Thread.sleep(10);

}catch(Exception e){

//转换为自定义运行期异常

throw new MyException(e, exceptionMsg);

}

}


注意看变量exceptionMsg,这个字符串变量在什么时候会被用到?只有在抛出异常时它才有用武之地,那它是什么时候创建的呢?只要该方法被调用就创建,不管会不会抛出异常。我们知道异常不是我们的主逻辑,不是我们代码必须或经常要到达的区域,那为了这个不经常出现的场景就每次都多定义一个字符串变量,合适吗?而且还要占用更多的内存!所以,在catch块中定义exceptionMsg方法才是正道:需要的时候才创建对象。

我们知道运行一段程序需要三种资源:CPU、内存、I/O,提升CPU的处理速度可以加快代码的执行速度,直接表现就是返回时间缩短了,效率提高了;内存是Java程序必须考虑的问题,在32位的机器上,一个JVM最多只能使用2GB的内存,而且程序占用的内存越大,寻址效率也就越低,这也是影响效率的一个因素。I/O是程序展示和存储数据的主要通道,如果它很缓慢就会影响正常的显示效果。所以我们在编码时需要从这三个方面入手接口(当然了,任何程序优化都是从这三方面入手的)。

Java的基本优化方法非常多,这里不再罗列,相信读者也有自己的小本本,上面所罗列的性能优化方法可能远比这里多,但是随着Java的不断升级,很多看似很正确的优化策略就逐渐过时了(或者说已经失效了),这一点还需要读者注意。最基本的优化方法就是自我验证,找出最佳的优化途径,提高系统性能,不可盲目信任。