四 字符串的性能不容小觑,百M内存轻松存储几十G数据

286 阅读4分钟

大家好,我是小水珠。 从第二个个模块开始,我将带你学习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性能.jpg

二 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字符串的创建分配内存情况:

String字符串的创建分配内存.jpg

3.如何使用字符串的分割方法?

最后我想跟你们聊一聊字符串的分割,这种方法在编程中也很常见。Split()方法使用了正则表达式实现了其强大的功能,而正则表达式的性能是非常不稳定的,使用不恰当会引起回溯问题,很可能导致CPU居高不下。

四 总结

这一讲中,我们认识到做好String字符串性能优化,可以提升系统的整体性能。在这个理论基础上,Java版本在迭代过程中不断的改变成员变量,节约内存空间,对String对象进行优化。

我们还特别提到了字符串的不可变性,正式这个特性实现了字符串常量池,通过减少同一个值得字符串对象的重复创建,进一步节约内存。

但也正因为这个特性,我们在做长字符串拼接时,需要显示的使用StringBulider,以提高字符串的拼接性能。最后,在优化方面,我们还可以使用intern方法,让变量字符串对象重复使用常量池中相同值得对象,进而节约内存。