大家好,我是小水珠。 从第二个个模块开始,我将带你学习Java编程的性能优化。今天我们就从最基础的String字符串优化讲起。
在开始之前,我想先问你们一个问题,也是面试时经常会被问的一个问题。虽是老生常谈了,但错误率依然很高,当前也有一些面试者答对了,但能解释清楚答案背后原理的人少之又少,问题如下:
String str1 = "abc";
String str2 = new String("abc");
String str3 = str2.intern();
assertSame(str1==str2);
assertSame(str2==str3);
assertSame(str1==str3);
你可以先想想答案,以及这样回答的原因。希望通过今天的学习,你能拿到满分。
一 String对象是如何实现的?
在Java语言中,Sun公司的工程师们对String对象做了大量的优化,来节约内存空间,提升String对象在系统中的性能。一起来看看优化过程,如下图所示:
二 String对象的不可变性
Java这样做的好处在哪里呢?
1.保证String对象的安全性。
2.保证hash属性值不会频繁变更,确保了唯一性。
3.可以实现字符串常量池。
这里附上一个你可能会想到的反例。
平常编程时,对一个String对象str赋值"holle",然后又让str的值为"world",这个时候str的值变成了"world"。那么str的值确实改变了,为什么我还说String对象不可变呢?
三 String对象的优化
1.如何构建超大的子字符串?
编程过程中,字符串的拼接很常见。前面我讲过String对象是不可变的,如果我们使用对象相加,拼接我们想要的字符串,是不是就会产生多个对象呢?例如以下代码:
String str = "ab" + "cd" + "ef";
分析代码可知:首先会生成ab对象,再生成abcd对象,最后生成abcdef对象,从理论上来说,这段代码是低效的。
但实际运行时,我们发现只有一个对象生成。
String str = "abcdef"
上面我们介绍的时字符串常量的累计,我们再来看看字符串变量的累计又是怎么样的呢?
String str = "abcdef";
for(int i = 0; i < 1000; i++) {
str = str + i;
}
上面的代码编译后,你可以看到编译器同样对这段代码进行了优化。不难发现,Java在进行字符串的拼接时,偏向使用StringBuilder,这样可以提高程序的效率。
String str = "abcdef";
for(int i = 0; i < 1000; i++) {
str = (new StringBuilder(String.valueOf(str))).append(i).toString();
}
2.如何使用String.intern节省内存?
讲完了构建字符串,我们再来讨论一下String对象的存储问题。先看一个案例。
Twitter每次发布消息状态的时候,都会产生一个地址信息,以当时Twitter的用户规模预估,服务器需要32G的内存来存储地址信息。
public class Location {
private String city;
private String region;
private String countryCode;
private String longitude;
private String latitude;
}
考虑到其中很多用户在地址信息上有重复的,比如:国家,省份,城市等,这时候就可以将这部分信息单独列出一个类,以减少重复,代码如下:
public class ShareLocation {
private String city;
private String region;
private String countryCode;
}
public class Location {
private ShareLocation shareLocation;
private String longitude;
private String latitude;
}
通过优化,数据存储大小减少到了20G左右。但对于内存存储这个数据来说,依然很大,怎么办?
ShareLocation shareLocation = new ShareLocation();
shareLocation.setCity(messageInfo.getCity().intern());
shareLocation.setRegion(messageInfo.getRegion().intern());
shareLocation.setCountryCode(messageInfo.getCountryCode().intern());
Location location = new Location();
location.setShareLocation(shareLocation);
location.setLongitude(messageInfo.getLongitude());
location.setLatitude(messageInfo.getLatitude());
为了更好的理解,我们再来通过一个简单的例子,回顾下其中的原理:
String a = new String("abc").intern();
String b = new String("abc").intern();
if(a == b) {
System.out.print("a==b");
}
输出结果:a==b
以一张图来总结String字符串的创建分配内存情况:
3.如何使用字符串的分割方法?
最后我想跟你们聊一聊字符串的分割,这种方法在编程中也很常见。Split()方法使用了正则表达式实现了其强大的功能,而正则表达式的性能是非常不稳定的,使用不恰当会引起回溯问题,很可能导致CPU居高不下。
四 总结
这一讲中,我们认识到做好String字符串性能优化,可以提升系统的整体性能。在这个理论基础上,Java版本在迭代过程中不断的改变成员变量,节约内存空间,对String对象进行优化。
我们还特别提到了字符串的不可变性,正式这个特性实现了字符串常量池,通过减少同一个值得字符串对象的重复创建,进一步节约内存。
但也正因为这个特性,我们在做长字符串拼接时,需要显示的使用StringBulider,以提高字符串的拼接性能。最后,在优化方面,我们还可以使用intern方法,让变量字符串对象重复使用常量池中相同值得对象,进而节约内存。