详解StringTable
String的基本特性
- 1.9之前String使用char[]数组实现的,1.9之后使用byte[]数组实现,因为大部分存储的字符串都是datin-1(用一个字节就可以表示),所有如果使用byte[]可以节省一半的空间,对于中文这些必须用两个byte的,对其进行标记
- String是不可变的字符串
- 字符串常量池是不会存储相同的字符串的,底层是通过HashSet来实现不重复的,所有设置常量池大小越大,不容易产生哈希碰撞,效率越好(比如在xxx.intern()时,需要先看对应位置有没有重复的xxx的操作)
- 注意这个HashSet存储的是字符串的地址,因为字符串的实现其实是一个char[]数组(1.9之前)
String的内存分配
- 1.6以及之前,字符串常量池在永久代
- 1.7时,调整到了堆中,因为永久代比较小,放大量字符串会OOM,而字符串操作很频繁,永久代垃圾回收频率低
- 1.8方法区变成元空间实现,字符串常量池仍在堆中
字符串的拼接操作
- 常量与常量的拼接结果也在常量池,编译期优化
- 只要有一个是变量,结果就在堆中,变量拼接的原理是StringBuilder,而且每次拼接都换创建一个新的StringBuilder
- 如果拼接结果主动调用intern(),主动将字符串常量池中还没有的字符串放入池中,返回对象地址
案例一:常量拼接
String s1 = "a" + "b" + "c"; // 编译器就会进行优化,对于这种常量拼接,直接变成拼接后的结果
String s2 = "abc";
s1 == s2; // true 因为编译器优化,s2已经存在在常量池中,直接返回其结果
案例二:变量拼接,会在堆中new一个String对象出来,所有地址会不同(如果变量被final修饰,当作常量处理)
String s1 = "javaEE";
String s2 = "hadoop";
String s3 = "javaEEhadoop";
String s4 = "javaEE" + "hadoop";
s3 == s4? // true 同案例一
String s5 = s1 + "hadoop";
s3 == s5; // false s5是堆区对象
String s6 = "javaEE" + s2;
s3 == s6; // false 同上
String s7 = s1 + s2;
s3 == s7; // false 同上
案例三:intern()
String s1 = "java";
String s2 = "javahello";
String s3 = s1 + "hello"; // 变量拼接 new出来的对象在堆区
String s4 = s3.intern(); // 调用intern返回结果
s2 == s4; //true 调用intern会返回在字符串常量池中的查询结果,结果有,并且与s2的地址相同(如果不存在,会加载一份,返回其地址)
intern()方法
当前字符串.intern(); 去字符串常量池中找该字符串,存在就返回其地址,不存在就创建一个字符串在字符串常量池中,并返回其地址
分析下面代码
String s1 = new String("a") + new String("b"); // StringBuilder并不会在底层把”ab“添加到字符串常量池
s1.intern();// ”ab"添加进常量池
String s2 = "ab"; // 常量池
System.out.println(s1 == s2); // true
为什么会是true? 首先经过测试 ,答案是true没有错,但是很奇怪,因为s1是在堆中的,s2是常量池中对象,为什么会相等
- jdk7之前,这段代码是false,因为当时字符串常量池在方法区
- 但是到了jdk7时,将字符串常量池转移到了堆区,这时如果将new 出来的String对象调用intern(),就是在常量池中的一个区域中添加数,添加的不是“ab”,而是这个String对象的地址,所有s1== s2时,s2虽然在常量池,但它保存的数据就是s1的地址\
- 总结:new String(“ab”)会有两个对象,new + new 不会在常量池创建一个结果对象,这时候如果时高版本,调用intern()就会创建一个常量池对象,但对象中不是字符串而是new+new产生对象的地址
new一个String对象,到底创建了几个对象?
代码: new String(“hello”);
答案:2个
- new关键字在堆中创建的对象
- 字符串常量池中的对象
代码:new String(“a”) + new String(“b”);
答案:6个
- 常量池拼接底层会new StringBuilder();
- new在堆中的“a”对象
- 常量池中“a”
- new在堆中的“b”对象
- 常量池中“b”
- StringBuilder().toString() new出来的“ab”对象
- “ab”? 有没有ab呢,StringBuilder返回字符串使用toString()方法,就是new String(“ab”); 但是这个new 并不会在常量池中添加“ab”
对象
7. “ab”? 有没有ab呢,StringBuilder返回字符串使用toString()方法,就是new String(“ab”); 但是这个new 并不会在常量池中添加“ab”