前言:
优化代码,一个很重要的课题。可能有些人以为没用,一些细小的地方有什么好修改的,
改与不改对代码的运行效率有什么影响呢?这个问题我是真么考虑的,比如人吃饭,吃一粒米,
没用,可是一万,十万呢,这样的效率就很可观了。
代码优化的目标是:
1.减少代码体积;
2.提升代码运行效率。java
具体方法:
1.尽可能指定类,方法的final修饰符
带有final的修饰符的类是不可派生的。在java核心API中,有许多应用final的例子,例如:
java.long.String,整个类都是final的。为类指定final修饰符可让类不可被继承,为方法
指定final修饰符可让方法不被重写。若是指定了一个类为final,则该类全部的方法都是final的。
java编译器会寻找机会内联全部的final方法,内联对于提高java运行效率做用重大,
大概能使性能提高50%。
内联:一般是用来消除调用函数时所须要的时间。
2.尽可能复用对象
特别是String对象,出现字符串链接时应该使用StringBuffer/StringBuilder代替。因为java虚拟机
不只要花时间生成对象,之后可能还须要对这些对象进行垃圾回收和处理,所以,生成过多对象
会给程序的性能带来很大影响。
3.尽量使用局部变量
调用方法是传递的参数以及在调用中建立的临时变量都保存在栈中,相对速度比较快。其余变量
如,静态变量,实例变量等,都在堆中建立,速度较慢。另外,栈中建立的变量,随着方法的
运行结束,这些内容就消失了,不须要额外的垃圾回收。
4.及时关闭流
java编程过程当中,进行的数据库链接,I/O流等操做时务必小心,在使用完毕后,应及时关闭流以
释放资源。由于这些大对象的操做会形成系统大的开销,会大大影响程序运行效率。
5.尽可能减小对变量的重复计算
明确概念,对方法的调用,即便方法中只有一条语句,也是要加载的。包括建立堆栈。
调用方法时保护现场,方法结束时恢复现场等。如:
for(int i = 0 ; i < list.size();i++) {
}
能够替换为:
for(int i = 0,length=list.size();i < length;i++) {
}
这样,若是list.size()里的数据有不少时(如2000000左右),会减小不少性能消耗。
6.尽可能使用懒加载策略,即在须要时才建立
如:
String str = "aaa";
if(i == 1) {
list.add(str);
}
能够替换为:
if(i == 1) {
String str = "aaa";
list.add(str);
}
7.慎用异常
异常对性能不利。抛出异常首先要建立一个新的对象,Throwable接口的构造函数调用名为
filllnStackTrace()的本地同步方法,filllnStackTrace()方法检查堆栈,收集调用跟踪信息。
只要有异常被抛出,java虚拟机就必须调整调用堆栈,由于在处理过程当中建立了一个新的对象
。异常只能用于错误处理,不该该用来控制流程程序。
8.不要在循环中使用try···catch···,应该把它放到最外层。
9.若是能估计到待添加的内容长度,为底层以数组方式实现的集合,工具类指定初始长度。
好比ArrayList、LinkedList、StringBuilder、StringBuffer、HashMap、HashSet等等,
以StringBuilder为例:
(1) StringBuilder() //默认分配16个字符空间
(2) StringBuilder(int size) //默认分配size个字符空间
(3) StringBuilder(String str) //默认分配16个字符+str.length()个空间
能够经过类(不只仅是StringBuilder)来设定它的初始化容量,这样能够明显提高性能。好比
StringBuilder吧,length表示当前的StringBuilder能保持的字符数量。由于当StringBuilder
达到最大容量的时候,它会将自身容量加到当前的2倍在加2,不管什么时候,只要StringBuilder达到
它的最大容量值,它就会建立一个新的字符数组而后将旧的字符数组里面的内容拷贝到新数组里
是一个十分耗时的工做。好比,一个字符数组大概要放5000个字符而不指定长度,最接近5000的
2次幂是4096,那么:
(1) 在4096的基础上,在申请8194+2个大小的新数组,加起来至关于共申请了12292的内存空间
,若是一开始就指定长度5000或5500的话,就能节省一倍的空间。
(2) 把原来的4096个字符拷贝到新的字符数组中,这样不只浪费内存又下降代码运行效率。
因此,给底层以数组实现的集合、工具类设置一个合理的初始值是不会有错的。
可是,注意,向HashMap这种以数组+链表实现的集合,别把初始值大小和你预估的大小设置的
同样,由于一个table上链接一个对象的几率几乎为0。建议初始大小值设为2的N此幂,若是预估
是2000个元素,设置成 new HashMap(128)、new HashMap(256)均可以。
10.当复制大量数据时,使用System.arraycopy()命令。
11.乘法和除法使用移位操做
如:
for(val = 0;val < 100000;val += 5) {
a = val * 8;
b = val / 2;
}
用移位操做能够极大的提高性能,由于在计算机底层,对位的操做是最方便的
能够替换为:
for(val = 0;val < 100000;val += 5) {
a = val << 3;
b = val >> 1;
}
注: 移位操做虽然方便,可是可能使代码不太好理解,所以须要加上相应的注释。
12.循环内不要不断建立对象引用
如:
for(int i = 0;i <= count; i++) {
Object obj = new Object();
}
这种作法会致使内存中有count份Object对象引用存在,count很大的话,就耗费内存了,
能够替换为:
Object obj = null;
for(int i = 0;i <= count;i++) {
obj = new Object();
}
这样的话,内存中只有一份Object对象引用,每次new Object()的时候,Object对象引用指向不一样
的Object而已,可是内存中只有一份,就能够节省不少内存空间了。
13.基于效率和类型检查的考虑,应该尽量使用array,没法肯定数组大小
时才使用ArrayList。
14.尽可能使用算法
除非线程安全须要,不然不推荐使用Hashtable,Vector
,StringBuffer,后三者因为使用同步机制而致使了性能开销。
15.不要将数组声明为public static final
由于这样毫无心义,这样知识定义了引用为static,final,数组的内容仍是能够随意改变的,将
数组声明为一个public更是一个安全漏洞,这意味着整个数组能够被外部类所改变。
16.尽可能在什么时候的场合使用单例
使用单例能够减轻加载的负担,缩短加载时的时间,提升加载的效率,但并非全部的地方都
适用于单例,简单说,单例主要适用于如下三个方面:
(1) 控制资源的使用,经过线程同步来控制资源的并发访问;
(2) 控制实例的产生,达到节约资源的目的;
(3) 控制数据的共享,在不创建直接关联的条件下,让多个不相关的进程或线程之间实现通讯。
17.尽可能避免随意使用静态变量
由于当某个对象被定义为static时,gc一般是不会回收这个对象所占有的堆内存的,
如:
public class A {
private static B b = new B();
}
此时静态变量b的生命周期与A类相同,若是A类不被卸载,那么引用B指向的B对象会一直
存在内存中,直到程序终止。
18.及时清除再也不须要的会话
当应用服务器须要保存更多会话时,若是内存不足,操做系统会把部分数据转移到磁盘里,
应用服务器也可能根据MRU(最近频繁使用的会话)算法,把部分不活跃的会话转存到磁盘里,
甚至可能抛出内存不足的异常。若是会话要被转存到磁盘,就必须先序列化,在大规模集群
中,对对象进行序列化代价是很大的。所以,应及时调用HttpSession的invalidate()方法
清除会话。
19.实现RandomAccess接口的集合好比ArrayList,应当使用for循环而不是foreach来遍历
JDK API对于RandomAccess接口的解释是:实现RandomAccess接口用来代表其支持快速随机
访问,此接口的主要目的是容许通常的算法更改其行为,从而将其应用到随机或连续访问列表
时可以提供良好的性能。
实现RandomAccess接口类实例,加入是随机访问的,使用for循环比foreach效率高;若是不是
随机访问的使用foreach效率高。
如:
if(list instanceof RandomAccess) {
for(int i = 0 ;i < list.size();i++){}
} else {
for(List li : list) {
System.out.println(li);
}
}
foreach底层实现原理就是迭代器(iterator)
20.使用同步代码块代替同步方法
除非能肯定整个方法都是须要进行同步的,不然尽可能使用同步代码块,避免对那些不须要
同步的代码也进行同步,从而影响效率。
21.将常量声明为 STATIC FINAL
这样在编译运行时就能够把这些内容放入常量池中,避免运行期间计算生成常量的值。另外,
将常量的名字进行大写的缘由。
22.程序运行过程当中避免使用反射
反射是java提供给用户一个很强大的功能,可是功能强大效率却不是很高。不建议在程序运行
过程当中频繁是哦那个反射机制,特别是Method的invoke方法。若是确实必要,建议将那些须要经过
反射加载的类在项目启动的时候经过反射实例化出一个对象并放入内存。
23.使用数据库链接池和线程池
这两个池都是重用与对象的,前者能够避免频繁打开和关闭链接:
后者能够避免频繁建立和销毁线程。
24.使用带缓冲的输入,输出流进行I/O操做
带缓冲的输入,输出流即:
BufferedReader,bufferedWrite,BufferedInputStream,BufferedOutputStream
它们能够大大提高I/O的效率
25.顺序插入和随机访问比较多的场景使用ArrayList,元素删除和中间插入比较多的
场景使用LinkedList
理解这两个集合有何不一样便可
26.不要让public方法中有太多形参
public方法是对外提供的方法,若是给这些方法太多形参的话有两点坏处:
(1) 违反面相对象的思想,java讲究万物皆对象,太多形参,和java编程
思想不和
(2) 参数较多会致使出错几率增长
27.字符串变量和字符串常量equals的时候将字符串常量写在前面
如:
Strring str = "123";
if(str.equals("123")) {
}
能够替换为:
Strring str = "123";
if("123".equals(str)) {
}
这么作是为了不空指针的出现(中期项目有讲过)
28.不要对数组使用toString()方法
本意是想打印数组里的内容,却可能由于数组引用对象为空而致使空指针异常。虽然对数组
toString()没有意义,可是对集合toString()是能够打印出集合中的内容的,由于集合的父类
AbstractCollections重写了Object的toString()方法。
29.不要对超出范围的基本数据类型作向下强制转换
获得的结果绝对是错误的。
30.把一个基本数据类型转为字符串,对象点toString()是最快的方法,对象点valueOf(数据)次之,
数据+""最慢
如,想把Integer i转为字符串类型,有三种方式:
(1) i.toString()
(2) i.valueOf(i)
(3) i+""
下面测试
public static void main(String[] args) {
int loopTime = 50000;
Integer i = 0;
long startTime = System.currentTimeMillis();
for(int j = 0 ;j < loopTime;j++) {
String str = String.valueOf(i);
}
System.out.println("String.valueOf():"+(System.currentTimeMillis()- startTime) +"ms");
startTime = System.currentTimeMillis();
for(int j = 0;j < loopTime;j++) {
String str = i.toString();
}
System.out.println("Integer.toString():"+(System.currentTimeMillis()- startTime) +"ms");
startTime = System.currentTimeMillis();
for(int j = 0 ;j < loopTime;j++) {
String str = i + "";
}
System.out.println("i+\"\":"+(System.currentTimeMillis()- startTime) +"ms");
}
结果:
String.valueOf():11ms;
Integer.toString():5ms;
i + "":25ms;
原理是:
(1) String.valueOf()方法调用了Integer.toString()方法,可是
在调用前先作了一次空判断;
(2) Integer.toString()是直接调用;
(3) i + ""是使用了StringBuilder实现,先用了append方法拼接,在用
toString()获取字符串
数据库