Java性能优化常识

9.1 前提
首先让程序运行起来,再考虑变得更快——但只有在自己必须这样做、而且经证实
在某部分代码中的确存在一个性能瓶颈的时候,才应进行优化。除非用专门的工具分析
瓶颈,否则很有可能是在浪费自己的时间。性能提升的隐含代价是自己的代码变得难于
理解,而且难于维护。
对于像字符串的连接操作不使用“+”而使用专有方法 concat 等其他方法,这类问
题,则不能称为性能优化,而只能叫做基本常识。因而这类问题的解决并不能影响程序
的可读性和易维护性,所以我们提倡为性能优化打基础。以下将介绍一些常识:


9.2 运算时间

Java性能优化常识
标准时间 = 语句执行时间/本地赋值时间


9.3 java.lang.String
字串的开销:字串连接运算符“+”看似简单,但实际需要消耗大量系统资源。编
译器可高效地连接字串,但变量字串却要求可观的处理器时间。该操作要创建并拆除一
个StringBuffer 对象以及一个String 对象。
上述问题的通常解决方法是新建一个StringBuffer(字串缓冲),用append 方法追加
自变量,然后用toString()将结果转换回一个字串。当要追加多个字串,则可考虑直接使
用一个字串缓冲——特别是能在一个循环里重复利用它的时候。通过在每次循环里禁止
新建一个字串缓冲,可节省980 单位的对象创建时间(见上表)。
更有效的解决办法是:在构造 StringBuffer 时,应该粗略的估计出它最终的总长度。默
认构造函数预设了16 个字符的缓存容量。append()方法首先计算字符串追加完成后的总长
度,如果这个总长度大于StringBuffer 的存储能力,append()方法调用私有的expandCapacity()
方法。expandCapacity()方法在每次被调用时使StringBuffer 存储能力加倍,并把现有的字符
数组内容复制到新的存储空间。存储能力的扩展,从而导致了两次代价昂贵的复制操作。
因此,我们至少有一点可以做得比编译器更好,这就是分配一个初始存储容量大于或者等
于最终字符长度StringBuffer。
因此,使用默认构造函数创建的StringBuffer 在字符串连接操作上的效率其实和用“+”
是一样的。如果首先估计出整个字符串最终的总长度,才会显著提高效率!
其他的字符串运算操作尽可能使用 String 已经提供的方法。比如,短字符串的连接可
以使用 concat;子串的查找可以使用 indexOf,substring 等。


9.4 java.util.Vector
一个Vector 就是一个java.lang.Object 实例的数组。Vector 与数组相似,它的元素可
以通过整数形式的索引访问。但是,Vector 类型的对象在创建之后,对象的大小能够根
据元素的增加或者删除而扩展、缩小。
1) 避免把新元素添加到Vector 的最前面
除非有绝对充足的理由要求每次都把新元素插入到Vector 的前面,否则对性能不
利。在默认构造函数中,Vector 的初始存储能力是10 个元素,如果新元素加入时存储
能力不足,则以后存储能力每次加倍。Vector 类就象StringBuffer 类一样,每次扩展存
储能力时,所有现有的元素都要复制到新的存储空间之中。
2) 避免从中间删除元素
由于Vector 中各个元素之间不能含有“空隙”,删除除最后一个元素之外的任意其
他元素都导致被删除元素之后的元素向前移动。也就是说,从Vector 删除最后一个元
素要比删除第一个元素“开销”低好几倍。
3) 删除所有元素的最好方法是 removeAllElements()
4) 避免二次搜索
假设Vector 类型的对象v 包含字符串“Hello”。考虑下面的代码,它要从这个Vector
中删除“Hello”字符串:
String s = "Hello";
int i = v.indexOf(s);
if(i != -1)
v.remove(s);
在这段代码中,indexOf()方法对v 进行顺序搜索寻找字符串“Hello”,remove(s)方法
也要进行同样的顺序搜索。改进之后的版本是:
String s = "Hello";
int i = v.indexOf(s);
if(i!= -1) v.remove(i);
这个版本中我们直接在remove()方法中给出待删除元素的精确索引位置,从而避免
了第二次搜索。一个更好的版本是:
String s = "Hello";
v.remove(s);
5) 循环内部的代码不会以任何方式修改Vector 类型对象大小时,应该提前取得
Vector.size()


9.5 线程
9.5.1 防止过多的同步
不必要的同步常常会造成程序性能的下降。因此,如果程序是单线程,则一定不
要使用同步。
9.5.2 避免同步整个代码段
对某个方法或函数进行同步比对整个代码段进行同步的性能要好。因为代码段的
同步牵涉的范围比对某个方法或函数进行同步广。
9.5.3 对每个对象使用多“锁”的机制来增大并发
一般每个对象都只有一个“锁”,这就表明如果两个线程执行一个对象的两个不同
的同步方法时,会发生“死锁”。即使这两个方法并不共享任何资源。为了避免这个问
题,可以对一个对象实行“多锁”的机制。


9.6 循环
9.6.1 边界
循环的边界是指完成所有循环操作的起点和终点。如果循环体内的操作不影响边
界,那么应该在循环体外,计算并且求得边界值。例如:
for(int i = 0; i < array.length; i++)
{
array[i]=i;
}
上述代码中每次循环操作,都要计算一次 array.length。
9.6.2 循环体内避免构建新对象
如果在循环体内用到新对象,需要在循环体开始以前构建好该对象。由标准时间
表可以看出构建对象有很大的系统消耗,并且在一次循环中还要清除掉该对象,下循
环再重新构建。
JAVA 编码规范 版本:0.0.0-1.1.1 第22页
沈阳东软软件股份有限公司 软件开发事业部
9.6.3 break
遍历数组、向量或者树结构时,如果满足条件的元素找到,一定要使用 break 语
句退出循环。


本文转自passover 51CTO博客,原文链接:http://blog.51cto.com/passover/425922,如需转载请自行联系原作者

上一篇:HTTP请求头和响应头


下一篇:Java Swing编程:JTable表格