一、编程优化
1、字符串优化
String字符串是开发中使用最频繁,也是最容易忽略的性能委托。高效使用字符串可以提升系统整体性能
String 类被 final 关键字修饰了,而且变量 char 数组也被 final 修饰了。我们知道类被 final 修饰代表该类不可继承,而 char[]被 final+private 修饰,代表了 String 对象不可被更改。Java 实现的这个特性叫作 String 对象的不可变性,即 String 对象一旦创建成功,就不能再对它进行改变。这样做的好处是:
第一,保证 String 对象的安全性。假设 String 对象是可变的,那么 String 对象将可能被恶意修改。
第二,保证hash属性值不会频繁变更,确保了唯一性,是的类似HashMap容器才能实现对应的key-value缓存功能。
第三,可以实现字符串常量池。
在java中,通常有两种创建字符串方式:
一是通过字符串常量方式,如String str = “abc”,这种方式JVM首先会检查对象是否在字符串常量池中,如果在就返回引用,否则创建新的字符串常量在常量池中,这种方式可以减少同一个值的字符串对象的重复创建,节省内存;
二是字符变量通过new方式创建,如String str = new String(“abc”)。这种方式,首先在编译类文件时,“abc”常量字符串将会放入到常量结构中,在类加载时,“abc”将会在常量池中创建,同时引用常量池中的“abc”字符串,在堆中创建一个String对象,最后str引用堆中的String对象。
就以下代码,输出结果为例
String str1= "abc";
String str2= new String("abc");
String str3= str2.intern();
assertSame(str1==str2);
assertSame(str2==str3);
assertSame(str1==str3)
str1首先会在常量池中创建“abc”字符串,自己为指向字符串的引用;
str2使用的new,则在堆中创建一个内存,内存指向常量池中的字符串,自己为指向堆中的内存的引用;
str3使用的str2的intern方法,返回的是常量池中的引用;
所以说str1 == str3 != str2
如何使用String.intern节省内存?了解了上面,再看字符串的储存问题。
使用String.intern来节省内存空间,从而优化String对象的存储,具体做法就是在每次赋值时使用String的intern方法,如果常量池中有相同值,就会重复使用该对象,返回对象引用。这样一开始的对象就可以被回收掉,使得字符串存储大小降低。看如下
String a =new String("abc").intern();
String b = new String("abc").intern();
if(a==b) {
System.out.print("a==b");
}
结果显而易见:会输出a==b
在字符串常量中,默认会将对象放入常量池;在字符串变量中,对象是会创建在堆内存中,同时会在常量池中创建一个字符串对象,String对象中的char数组将会引用常量池中的char数组,并返回堆内存对象引用。
如果调用intern方法,会去查看字符串常量池中是否有等于该对象的引用,如果没有,在jdk.6中会复制堆中的字符串到常量池中,并返回该字符串引用,堆内存中原有的字符串由于没有引用指向它,会被GC。在jdk1.7版本以后,由于字符串常量池(根据我翻阅的资料来看,运行时常量池依然在方法区)已经合并到堆中所以不会在复制具体字符串,只是把首次遇到的字符串的引用添加到常量池中;如果有,就返回常量池中的字符串引用。
再看上面的例子,创建a和b两个字符串调用了new方法,都会在堆中创建一个String对象,String对象中的char数组将会引用常量池中的字符串。调用intern方法后,会去常量池查找有没有相等的字符串,有就返回引用;没有jdk1.6就把堆中的字符串复制到字符串常量池,因为堆中的字符串没有引用了,下次gc就会被回收; jdk1.7就把堆中字符串的引用放入字符串常量池,并且返回常量池中的引用,这个引用和new方法创建的在堆中字符串的引用是同一个!!!用下图表示:
另外使用intern方法还有注意的一点是:要结合实际场景,因为常量池的实现时类似于一个HashTable,存储的数据量越大,遍历的性能就越差,会增加整个字符串常量池的负担。
此外,字符串的分割方法使用不当也会导致性能下降,Split()方法使用了正则表达式实现了其强大的分割功能,但是正则表达式性能非常不稳定,使用不恰当会导致性能的下降。所以慎重使用Split方法,使用indexof来替代完成字符串的分割。