性能优化之Java程序优化细节(珍藏版)

1. 尽量指定类、方法的final修饰符

为类指定final修饰符可以让类不可以被继承,为方法指定final修饰符可以让方法不可以被重写。类似于java.lang.String就是final类,如果指定了一个类为final,则该类所有的方法都是final的。Java编译器会寻找机会内联所有的final方法,内联对于提升Java运行效率作用重大,此举能够使性能平均提高50%。

2. 尽量重用对象

特别是String对象的使用,出现字符串连接时应该使用StringBuilder/StringBuffer代替。特别小心String.split()方法,如果对一个巨大的String字符串进行split操作,在JDK1.7中很容易发生Full GC,因为split是会生成无数个新的String对象。

3. 尽量使用局部变量

方法中创建的变量,随着方法的运行结束,这些内容就没了,不需要额外的垃圾回收

4. 及时关闭流

数据库连接、I/O流操作时务必小心,在使用完毕后,及时关闭以释放资源

5. 尽量减少对变量的重复计算

for (int i = 0; i < list.size(); i++){...}

替换为:

for (int i = 0, length = list.size(); i < length; i++){...}

6. 尽量采用懒加载的策略,即在需要的时候才创建

String str = "aaa";
if (i == 1) {
 	list.add(str);
}

替换为:

if (i == 1) {
	String str = "aaa";
 	list.add(str);
}

7. 慎用异常

异常对性能是不利的,尽量避免抛出无缘无故的异常

8. 不要在循环中使用try…catch…,应该把其放在最外层

除非不得已吧

9. 若能估计到待添加的内容长度,为底层以数组方式实现的集合、工具类指定初始长度

比如ArrayList、LinkedLlist、StringBuilder、StringBuffer、HashMap、HashSet等等。因为集合在发现容量不够的时候会进行扩容,不同集合扩容的容量不同,而且在这过程中可能会移动原有的数据,既浪费空间又消耗性能

10. 当复制大量数据时,使用System.arraycopy()命令

System.arraycopy()底层采用native

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 = 1; 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. 尽量使用HashMap、ArrayList、StringBuilder,除非线程安全需要,否则不推荐使用Hashtable、Vector、StringBuffer,后三者由于使用同步机制而导致了性能开销

15. 不要将数组声明为public static final

因为这毫无意义,这样只是定义了引用为static final,数组的内容还是可以随意改变的,将数组声明为public更是一个安全漏洞,这意味着这个数组可以被外部类所改变

16. 尽量在合适的场合使用单例

17. 尽量避免随意使用静态变量

当某个对象被定义为static的变量所引用,那么gc通常是不会回收这个对象所占有的堆内存的

18. 及时清除不再需要的会话

为了清除不再活动的会话,许多应用服务器都有默认的会话超时时间,一般为30分钟。当应用服务器需要保存更多的会话时,如果内存不足,那么操作系统会把部分数据转移到磁盘,应用服务器也可能根据MRU(最近最频繁使用)算法把部分不活跃的会话转储到磁盘,甚至可能抛出内存不足的异常。如果会话要被转储到磁盘,那么必须要先被序列化,在大规模集群中,对对象进行序列化的代价是很昂贵的。

19. 实现RandomAccess接口的集合比如ArrayList,应当使用最普通的for循环而不是foreach循环来遍历

实际经验表明,实现RandomAccess接口的类实例,假如是随机访问的,使用普通for循环效率将高于使用foreach循环;反过来,如果是顺序访问的,则使用Iterator会效率更高。可以使用类似如下的代码作判断:

if (list instanceof RandomAccess) { 
	for (int i = 0; i < list.size(); i++){}
} else{
	Iterator iterator = list.iterable(); 
	while (iterator.hasNext()){iterator.next()}
}

20. 使用同步代码块替代同步方法

21. 将常量声明为static final,并以大写命名

这样在编译期间就可以把这些内容放入常量池中,避免运行期间计算生成常量的值。另外,将常量的名字以大写命名也可以方便区分出常量与变量

22. 不要创建一些不使用的对象,不要导入一些不使用的类

这毫无意义,如果代码中出现”The value of the local variable i is not used”、”The import java.util is never used”,那么请删除这些无用的内容

23. 程序运行过程中避免使用反射

如非必要,不建议在程序运行过程中使用尤其是频繁使用反射机制,特别是Method的invoke方法,如果确实有必要,一种建议性的做法是将那些需要通过反射加载的类在项目启动的时候通过反射实例化出一个对象并放入内存

24. 使用数据库连接池和线程池

这两个池都是用于重用对象的,前者可以避免频繁地打开和关闭连接,后者可以避免频繁地创建和销毁线程

25. 使用带缓冲的输入输出流进行IO操作

带缓冲的输入输出流,即BufferedReader、BufferedWriter、BufferedInputStream、BufferedOutputStream,这可以极大地提升IO效率

26. 顺序插入和随机访问比较多的场景使用ArrayList,元素删除和中间插入比较多的场景使用LinkedList

ArrayList内部是数组结构,适合顺序插入和随机访问场景;LinkedList内部是链表结构,适合元素删除和中间插入的场景

27. 不要让public方法中有太多的形参

public方法即对外提供的方法,如果给这些方法太多形参的话主要有两点坏处:

  • 违反了面向对象的编程思想,Java讲求一切都是对象,太多的形参,和面向对象的编程思想并不契合
  • 参数太多势必导致方法调用的出错概率增加

至于这个”太多”指的是多少个,3、4个吧

28. 字符串变量和字符串常量equals的时候将字符串常量写在前面

String str = "123";
if (str.equals("123")) {...}

修改为:

String str = "123";
if ("123".equals(str)){...}

避免空指针异常

29. 在java中if (i == 1)和if (1 == i)是没有区别的,但从阅读习惯上讲,建议使用前者

30. 不要对数组使用toString()方法

数组的toString并不会打印出数组的内容,而且还有可能造成空指针异常

31. 不要对超出范围的基本数据类型做向下强制转型

这绝不会得到想要的结果:

public static void main(String[] args) {
	long l = 12345678901234L;
	int i = (int)l;
	System.out.println(i);//结果:1942892530
}

Java中long是8个字节64位的,一个int型数据是4个字节32位的,转换时会丢失的

32. 公用的集合类中不使用的数据一定要及时remove掉

如果一个集合类是公用的(也就是说不是方法里面的属性),那么这个集合里面的元素是不会自动释放的,因为始终有引用指向它们。所以,如果公用集合里面的某些数据不使用而不去remove掉它们,那么将会造成这个公用集合不断增大,使得系统有内存泄露的隐患。

33. 把一个基本数据类型转为字符串,基本数据类型.toString()是最快的方式、String.valueOf(数据)次之、数据+""最慢

34. 使用最有效率的方式去遍历Map

遍历Map的方式有很多,通常场景下我们需要的是遍历Map中的Key和Value,那么推荐使用的、效率最高的方式是使用迭代器:

public static void main(String[] args) {
	HashMap hm = new HashMap();
	hm.put("111", "222");

	Set> entrySet = hm.entrySet();
	Iterator> iter = entrySet.iterator(); while (iter.hasNext()) {
		Map.Entry entry = iter.next();
		System.out.println(entry.getKey() + "\t" + entry.getValue());
	}
}

如果你只是想遍历一下这个Map的key值,那用”Set keySet = hm.keySet(); ”会比较合适一些

35. 对资源的close()建议分开操作

比如我有这么一段代码:

try{ 
	XXX.close();
	YYY.close();
}catch (Exception e){...}

建议修改为:

try{ XXX.close(); }catch (Exception e) { ... }
try{ YYY.close(); }catch (Exception e) { ... }

虽然有些麻烦,却能避免资源泄露。如果为前面的代码,万一XXX.close()抛异常了,那么就进入了cath块中了,YYY.close()不会执行,YYY这块资源就不会回收了,一直占用着,这样的代码一多,是可能引起资源句柄泄露的。

36. 尽量处理好包装类型和基本类型的使用场所

虽然包装类型和基本类型在使用过程中是可以相互转换的,但是它们两者所产生的内存区域是完全不同的。基本类型数据在栈中处理,包装类型是对象,在堆中生产实例。在集合类对象中,有对象方面需要的处理适合用包装类型,其他的处理提倡使用基本类型。

37. 尽量不要使用finalize方法

实际上,将资源清理放在finalize方法中完成是非常不好的选择,除非有必要的情况。由于GC的工作量很大,尤其是回收年轻代内存时,大都会引起应用程序暂停,所以再选择使用finalize方法进行资源清理,会导致GC负担加重,程序运行效率更差

38. 尽量在finally块中释放资源

39. 尽量早释放无用对象的引用

这种情况其实更是和在一个执行很长的方法中的场景,提早将引用置为null,那么垃圾回收器在回收时就会把没有引用的对象给回收掉。

public void test() {
	Object obj = new Object();
	//...使用到obj的一些逻辑处理...
	obj = null;
	//...没使用到obj的一些逻辑处理,长达10秒...
}

40. 尽量避免使用二维数组

二维数组占用的内存空间比一维数组多得多,大概10倍以上

41. 尽量避免String.split

split会耗费大量资源,创建大量的对象(JDK1.7之后)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值