开发代码优化技巧
开发优化技巧
一、对象的创建与使用
-
避免在循环体中声明创建对象,即使该对象占用内存空间不大。
这种情况在我们的实际应用中经常遇到,而且我们很容易犯类似的错误,例如下面的代码:
for (int i = 0; i < 10000; ++i) { Object obj = new Object(); System.out.println("obj= " + obj); }
上面的做法会浪费较大的内存空间。正确的做法如下所示:
Object obj = null; for (int i = 0; i < 10000; ++i) { obj = new Object(); System.out.println("obj= "+ obj); }
采用上面的第二种编写方式,仅在内存中保存一份对该对象的引用,而不像上面的第一种编写方式中代码会在内存中产生大量的对象引用,浪费大量的内存空间,而且增大了垃圾回收的负荷。因此在循环体中声明创建对象的编写方式应该尽量避免。
-
不要提前创建对象
void f(){ int i; A a = new A(); if(…){ a.showMessage(); } } //改成 void f(){ int i; A a = null; if(…){ //用到时才实例化 a = new A(); a.showMessage(); } }
-
尽早释放无用对象的引用
A a = new A(); //当使用对象a之后主动将其设置为空 a = null;
-
尽量重用对象
特别是String 对象的使用中,出现字符串连接情况时应用
StringBuffer
代替。由于系统不仅要花时间生成对象,以后可能还需花时间对这些对象进行垃圾回收和处理。因此,生成过多的对象将会给程序的性能带来很大的影响。 -
StringBuffer
的使用:StringBuffer
有三个构造方法 :
StringBuffer ();
//默认分配16个字符的空间
StringBuffer (int size);
//分配size个字符的空间
StringBuffer (String str);
//分配16个字符+str.length()
个字符空间
你可以通过StringBuffer
的构造函数来设定它的初始化容量,这样可以明显地提升性能。这里提到的构造函数是
StringBuffer(int length)
,length参数表示当前的StringBuffer
能保持的字符数量。你也可以使用ensureCapacity(int minimumcapacity)
方法在StringBuffer
对象创建之后设置它的容量。首先我们看看StringBuffer
的缺省行为,然后再找出一条更好的提升性能的途径。
StringBuffer
在内部维护一个字符数组,当你使用缺省的构造函数来创建StringBuffer
对象的时候,因为没有设置初始化字符长度,StringBuffer
的容量被初始化为16个字符,也就是说缺省容量就是16个字符。当StringBuffer
达到最大容量的时候,它会将自身容量增加到当前的2倍再加2,也就是(2旧值+2)。如果你使用缺省值,初始化之后接着往里面追加字符,在你追加到第16个字符的时候它会将容量增加到34(216+2),当追加到34个字符的时候就会将容量增加到70(2*34+2)。无论何事只要StringBuffer
到达它的最大容量它就不得不创建一个新的字符数组然后重新将旧字符和新字符都拷贝一遍――这也太昂贵了点。所以总是给StringBuffer
设置一个合理的初始化容量值是错不了的,这样会带来立竿见影的性能增益。StringBuffer
初始化过程的调整的作用由此可见一斑。所以,使用一个合适的容量值来初始化StringBuffer
永远都是一个最佳的建议。 -
StringBuffer
和StringBuilder
的区别:java.lang.StringBuffe
r线程安全的可变字符序列。一个类似于 String 的字符串缓冲区,但不能修改。StringBuilder
。与该类相比,通常应该优先使用java.lang.StringBuilder
类,因为它支持所有相同的操作,但由于它不执行同步,所以速度更快。为了获得更好的性能,在构造StirngBuffer
或StirngBuilde
r 时应尽可能指定它的容量。当然,如果你操作的字符串长度不超过 16 个字符就不用了。 相同情况下使用StirngBuilde
r 相比使用StringBuffer
仅能获得 10%-15% 左右的性能提升,但却要冒多线程不安全的风险。而在现实的模块化编程中,负责某一模块的程序员不一定能清晰地判断该模块是否会放入多线程的环境中运行,因此:除非你能确定你的系统的瓶颈是在StringBuffer
上,并且确定你的模块不会运行在多线程模式下,否则还是用StringBuffer
吧。 -
不用new关键词创建类的实例
用new关键词创建类的实例时,构造函数链中的所有构造函数都会被自动调用。但如果一个对象实现了
Cloneable
接口,我们可以调用它的clone(
)方法。clone()
方法不会调用任何类构造函数。在使用设计模式(Design Pattern)的场合,如果用Factory模式创建对象,则改用
clone()
方法创建新的对象实例非常简单。例如,下面是Factory模式的一个典型实现:public static Credit getNewCredit() { return new Credit(); } //改进后 private static Credit BaseCredit = new Credit(); public static Credit getNewCredit() { return (Credit) BaseCredit.clone(); }
-
日期类型的对象会占用很大的空间,如果你要存储大量的日期对象,可以考虑把它存储为
long型,然后在使用的时候转换为Date类型
二、数学计算的使用
-
乘法和除法
for (val = 0; val < 100000; val +=5) { alterX = val * 8; myResult = val * 2; } //替换成 for (val = 0; val < 100000; val += 5) { alterX = val << 3; myResult = val << 1; }
用移位操作替代乘法操作可以极大地提高性能;
三、变量的使用
-
尽量使用局部变量
调用方法时传递的参数以及在调用中创建的临时变量都保存在栈(Stack)中,速度较快。
其他变量,如静态变量、实例变量等,都在堆(Heap)中创建,速度较慢。另外,依赖于具体的编译器/JVM,局部变量还可能得到进一步优化; -
不要重复初始化变量
默认情况下,调用类的构造函数时, Java会把变量初始化成确定的值:所有的对象被设置成null,整数变量(byte、short、int、long)设置成0,float和double变量设置成0.0,逻辑值设置成false。当一个类从另一个类派生时,这一点尤其应该注意,因为用new关键词创建一个对象时,构造函数链中的所有构造函数都会被自动调用。
-
尽量减少对变量的重复计算
for(int i = 0;i < list.size; i ++) { … } //替换为 for(int i = 0,int len = list.size();i < len; i ++){ … }
-
尽量采用lazy loading 的策略,即在需要的时候才开始创建
String str = "aaa"; if(i == 1) { list.add(str); } //替换为 if(i == 1) { String str = "aaa"; list.add(str); }
四、资源的使用与释放
-
数据库连接、I/O流及时的关闭与释放
-
对象使用完毕,建议手动置成null
由于JVM的有其自身的GC机制,不需要程序开发者的过多考虑,从一定程度上减轻了开发者负担,但同时也遗漏了隐患,过分的创建对象会消耗系统的大量内存,严重时会导致内存泄露,因此,保证过期对象的及时回收具有重要意义。
JVM回收垃圾的条件是:对象不在被引用;然而,JVM的GC并非十分的机智,即使对象满足了垃圾回收的条件也不一定会被立即回收。所以,建议我们在对象使用完毕,应手动置成null。