从底层彻底搞懂String,StringBuilder,StringBuffer的实现

在深入学习字符串类之前, 我们先搞懂JVM是怎样处理新生字符串的. 当你知道字符串的初始化细节后, 再去写 Strings="hello"或 Strings=newString("hello")等代码时, 就能做到心中有数 。
首先得搞懂字符串常量池的概念 。
常量池是JAVA的一项技术, 八种基础数据类型除了float和double都实现了常量池技术. 这项技术从字面上是很好理解的: 把经常用到的数据存放在某块内存中, 避免频繁的数据创建与销毁, 实现数据共享, 提高系统性能 。
字符串常量池是Java常量池技术的一种实现, 在近代的JDK版本中(1.7后), 字符串常量池被实现在Java堆内存中 。
下面通过三行代码让大家对字符串常量池建立初步认识:

从底层彻底搞懂String,StringBuilder,StringBuffer的实现

文章插图
 
我们先来看看第一行代码 Strings1="hello";干了什么.
从底层彻底搞懂String,StringBuilder,StringBuffer的实现

文章插图
 
对于这种直接通过双引号""声明字符串的方式, 虚拟机首先会到字符串常量池中查找该字符串是否已经存在. 如果存在会直接返回该引用, 如果不存在则会在堆内存中创建该字符串对象, 然后到字符串常量池中注册该字符串 。
在本案例中虚拟机首先会到字符串常量池中查找是否有存在"hello"字符串对应的引用. 发现没有后会在堆内存创建"hello"字符串对象(内存地址0x0001), 然后到字符串常量池中注册地址为0x0001的"hello"对象, 也就是添加指向0x0001的引用. 最后把字符串对象返回给s1 。
温馨提示: 图中的字符串常量池中的数据是虚构的, 由于字符串常量池底层是用HashTable实现的, 存储的是键值对, 为了方便大家理解, 示意图简化了字符串常量池对照表, 并采用了一些虚拟的数值 。
下面看 Strings2=newString("hello");的示意图:
从底层彻底搞懂String,StringBuilder,StringBuffer的实现

文章插图
 
当我们使用new关键字创建字符串对象的时候, JVM将不会查询字符串常量池, 它将会直接在堆内存中创建一个字符串对象, 并返回给所属变量 。
所以s1和s2指向的是两个完全不同的对象, 判断s1 == s2的时候会返回false 。
如果上面的知识理解起来没有问题的话, 下面看些难点的.

从底层彻底搞懂String,StringBuilder,StringBuffer的实现

文章插图
 
第一行代码 Strings1=newString("hello ")+newString("world");的执行过程是这样子的:
1.依次在堆内存中创建"hello "和"world"两个字符串对象
2.然后把它们拼接起来 (底层使用StringBuilder实现, 后面会带大家读反编译代码)
3.在拼接完成后会产生新的"hello world"对象, 这时变量s1指向新对象"hello world"
执行完第一行代码后, 内存是这样子的:
从底层彻底搞懂String,StringBuilder,StringBuffer的实现

文章插图
 
第二行代码 s1.intern();
String类的源码中有对 intern()方法的详细介绍, 翻译过来的意思是: 当调用 intern()方法时, 首先会去常量池中查找是否有该字符串对应的引用, 如果有就直接返回该字符串; 如果没有, 就会在常量池中注册该字符串的引用, 然后返回该字符串 。
由于第一行代码采用的是new的方式创建字符串, 所以在字符串常量池中没有保存"hello world"对应的引用, 虚拟机会在常量池中进行注册, 注册完后的内存示意图如下:
从底层彻底搞懂String,StringBuilder,StringBuffer的实现

文章插图
 
第三行代码 Strings2="hello world";
这种直接通过双引号""声明字符串背后的运行机制我们在第一个案例提到过, 这里正好复习一下 。
首先虚拟机会去检查字符串常量池, 发现有指向"hello world"的引用. 然后把该引用所指向的字符串直接返回给所属变量 。
执行完第三行代码后, 内存示意图如下:
从底层彻底搞懂String,StringBuilder,StringBuffer的实现

文章插图
 
如图所示, s1和s2指向的是相同的对象, 所以当判断s1 == s2时返回true 。
最后我们对字符串常量池进行总结:
当用new关键字创建字符串对象时, 不会查询字符串常量池; 当用双引号直接声明字符串对象时, 虚拟机将会查询字符串常量池. 说白了就是: 字符串常量池提供了字符串的复用功能, 除非我们要显式创建新的字符串对象, 否则对同一个字符串虚拟机只会维护一份拷贝 。


推荐阅读