评价一个程序就像评价一个人一样,除了要看他穿着是否得体外,还要去看他的内心是不否优秀。程序也不例外,评价程序是否良好,除了要看该程序代码是否可整洁、可运行、易懂外,还要考虑程序本身的性能是否良好。我就以我平时开发中代码编写时的一些经验做些总结,以供大家作为参考。
一、先编码,然后考虑程序的执行性能
在设计和开发一个项目的时候,我个人认为首先应该先把注意力放在集中编码上,以保证程序的可读性、整洁、可运行。只有当该程序可运行时,才可再从代码上进行优化调整,以提高程序的性能。否则,你也许所能得到的是时间上的浪费,甚至由于不必要的性能扩展而阻碍了项目进度的开发。通过剖析程序中的代码,你可能会发现最经常被执行的那部分代码,可能仅仅只有10%的可能的机率执行到那部份,而大多数的执行效率是没有什么意义的,这常常称为二八原理或一九原理,所以提高程序性能要尽量增加最经常被执行那部分代码的效率。提高效率最好的办法是:测试被更改代码确定的分支,然后,再通过比较更改前效率的差别来确定更改后是否对效率的提高有帮助,还是仅仅提高了效率却给代码带来了很大混乱,如果这样你就得不偿失了。当对代码的更改对效率提高不明显是还是维持代码的抽象性、复用性、可维护可扩展性比较好。
二、尽量使用面向对象的代码和增加第三方库的使用
通常,面向对象语言刻画客观系统较为自然,便于软件扩充与复用。面向对象语言往往在设计中趋向于更多的类、设计模式,采用MVC(Model—View—Controller)模式,来实现高内聚低耦合。使用第三方库可以减少代码的编写量,达到代码的统一。相反,如果是在手机移动领域(J2ME)开发中,由于受内存的限制,就要尽量少用MVC模式,因为这种设模式会使代码量呈指数性增长,有时在面对性能和内存时,你也许需要面向过程的方法来解决。另外在移动领域也要避免使用第三方API来存储空间和运行空间。如:在网络通讯时,使用自己定义的数据解析可能会提高程序的运行效率,但要避免使用一个更加抽象的工业化定义的数据通讯方法,如SOAP网络协议。因为这种协议不仅会增加程序的大小,也会带来解析和建立XML数据传递的额外过程。另外,移动领域为了提高网络通讯的性能,需要避免不断的从服务器中取回数据,要多利用缓存(请见wap开发中如何有效的利用缓存 )。
三、垃圾回收调优
作为一名Java程序员,应该知道垃圾回收器是Java语言中的一大优点,也正是由于这一大亮点,为我们程序员节省了不少代码量,而不用像C++语言那样调用delete方法来释放内存空间。但是,由于JVM对垃圾回收的不确定性,不知道何时才会对垃圾进行收集,所以会造成内存空间没有来得及释放,使得资源一直被占用,最后导致内存溢出。这种情况尤其在J2ME中更明显。因此我们可以通过一些措施来促进垃圾回收,如:将不再使用的对象置为NULL,特别是不再使用的Thread、重新利用对象池机制,从而避免重新创建对象。与可以通过调用System.gc()或者Runtie.getRuntime().gc()来强制执行垃圾回收(此种方法有时行不通)。
四、避免在循环条件中使用复杂表达式、进行同步和try/catch块
在循环中,循环条件会被反复计算,如果不使用复杂表达式,而使循环条件值不变的话,程序将会运行的更快。如:int size = list.size () ; for (int i = 0; i < size;i++) {}比for (int i = 0; i < list.size (); i++) {}执行的更快
避免在循环条件中进行同步,是因为每一次循环都会有lock和Unlock的过程,这样会严重影响程序的执行效率。另在循环中倒数(递减)比正数(递增)要快且在循环体中实例化临时变量将会增加内存消耗
如果把try/catch块放入循环体内,会极大的影响性能,如果编译JIT被关闭或者你所使用的是一个不带JIT的JVM,性能会将下降21%之多。所以因将try/catch块移出循环
五、为'Vectors' 和 'Hashtables'定义初始大小
JVM为Vector扩充大小的时候需要重新创建一个更大的数组,将原先数组中的内容给复制过来,最后,原先的数组对象再被回收。可见Vector容量的扩大是一个颇费时间的事情。通常,默认的10个元素大小是不够的。你最好能准确的估计你所需要的最佳大小。
六、在finally块中关闭Stream流操作
程序中所有使用到的资源应当全都被释放,以避免资源泄漏。这最好在finally块中去做。不管程序执行的结果如何,finally块总是会执行的,以确保资源的正常关闭。
七、尽量使用内置方法
要实现将数据从一个数组复制到另一个数组时,使用'System.arraycopy ()'代替通过来循环复制数组或自己创建新方法的效率快的多
八、让访问实例内变量的getter/setter方法变成”final”
简单的getter/setter方法应该被置成final,这会告诉编译器,这个方法不会再被重载。
九、避免不需要的instanceof操作
如果左边的对象的静态类型等于右边的,instanceof表达式返回永远为true。
十、使用移位操作来代替'a / b'或'a * b'操作
"/"或"*"是一个很“昂贵”的操作,使用移位操作将会更快更有效。
如:int i = a/4; 替换为int i = a>>2;
int j = b*4;替换为int j = b<<2;
十一、对于boolean值,避免不必要的等式判断
将一个boolean值与一个true比较是一个恒等操作(直接返回该boolean变量的值). 移走对于boolean的不必要操作至少会带来2个好处:
1)代码执行的更快 (生成的字节码少了5个字节);
2)代码也会更加干净 。
十二、用'StringTokenizer' 代替 'indexOf()' 和'substring()'
字符串的分析在很多应用中都是常见的。使用indexOf()和substring()来分析字符串容易导致StringIndexOutOfBoundsException。而使用StringTokenizer类来分析字符串则会容易一些,效率也会高一些。
十三、正确使用String与String Buffer
对于常量字符串,用'String' 代替 'StringBuffer' 对于需改变的变量用'StringBuffer 代替 'String'',
StringBuffer的构造器会创建一个默认大小(通常是16)的字符数组。在使用中,如果超出这个大小,就会重新分配内存,创建一个更大的数组,并将原先的数组复制过来,再丢弃旧的数组。在大多数情况下,你可以在创建StringBuffer的时候指定大小,这样就避免了在容量不够的时候自动增长,以提高性能。
十四、尽可能的使用栈变量
如果一个变量需要经常访问,那么你就需要考虑这个变量的作用域了。static? local?还是实例变量?访问静态变量和实例变量将会比访问局部变量多耗费2-3个时钟周期。
十五、与一个接口 进行instanceof操作
基于接口的设计通常是件好事,因为它允许有不同的实现,而又保持灵活。只要可能,对一个对象进行instanceof操作,以判断它是否某一接口要比是否某一个类要快。
十六、其他编码建议:
1、如果只是查找单个字符的话,用charAt()代替startsWith()
2、在字符串相加的时候,使用 ' ' 代替 " ",如果该字符串只有一个字符的话
3、不要总是使用取反操作符(!),取反操作符(!)降低程序的可读性,所以不要总是使用
4、使用条件操作符(cond?0:1)代替"if...else" 结构
5、使用变量比使用数组更有效率
6、使用类似于x+=1替代x=x+1
因此,我们写代码时就像做人一样,不但要求代码整洁,还要注重代码的质量!