String和StringTable详解
一、字符串前生今世
1. 字符串有六种基本的创建(出生)方式
- 使用 char[] 数组配合 new 来创建
String s = new String(new char[]{'a', 'b', 'c'});
- 使用 byte[] 数组配合 new 来创建
String s = new String(new byte[]{97, 98, 99});
- 使用 int[] 数组配合 new 来创建
String s = new String(new int[]{0x1F602}, 0, 1);
- 使用 已有字符串配合 new 来创建
String s1 = new String(new char[]{'a', 'b', 'c'});
String s2 = new String(s1);
//源码
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
-
使用字面量创建(不使用 new )
String s = "abc";
说明:其中“abc”编译后会被加载到方法区中的运行时常量池中,执行到了代码区中的idc #2
后会利用常量池中的"abc"在堆内存中创建一个字符串对象,然后栈帧中的局部变量s会引用的对象 -
合二为一,使用 + 运算符来拼接创建
String s = "a" + "b";
二、字符串之家 - StringTable
1.家养与野生
前面我们讲解了 String 的六种创建方式,除了字面量方式创建的字符串是家养的以外,其它方法创建的字符串都是野生的。什么意思呢?
- 字面量方式创建的字符串,会放入 StringTable 中,StringTable 管理的字符串,才具有不重复的特性,这种就像是家养的
- 而 char[],byte[],int[],String,以及 + 方式本质上都是使用 new 来创建,它们都是在堆中创建新的字符串对象,不会考虑字符串重不重复,这种就像是野生的,野生字符串的缺点就是如果存在大量值相同的字符串,对内存占用非常严重
说明:也就是说我们可以把通过字面量方式创建的字符串看作是家养的,可以在StringTable中统一管理,而其他方式创建的字符串是野生的,即不受StringTable的管理。
如何保证家养的字符串对象不重复呢?
JDK 使用了 StringTable 来解决,StringTable 是采用 c++ 代码编写的,数据结构上就是一个 hash 表,字符串对象就充当 hash 表中的 key,key 的不重复性,是 hash 表的基本特性
下面的代码s1和s2引用的都是StringTable中的同一个对象,他们都是家养的
String s1 = "abc"; //家养
String s2 = "abc"; //家养
2.野生变家养
public native String intern();
它会尝试将调用者放入 StringTable
- 如果 StringTable 中已有
例子:
String x = new String(new char[]{'a', 'b', 'c'}); // 野生的
String y = "abc"; // 将 "abc" 加入 StringTable
String z = x.intern(); // 已有,返回 StringTable 中 "abc",即 y
System.out.println(y == z); //true
System.out.println(x == z); //false
说明:String z = x.intern()
尝试将野生的x转化为家养的,即存到StringTable中,但是StringTable中已经有亲儿子y了,所以会转化失败,x还是野生的,返回StringTable中已经存在的y,即 y == z,但是 x != z
- 如果 StringTable 中没有(1.7 以上 JDK 的做法)
例子:
String x = new String(new char[]{'a', 'b', 'c'}); // 野生的
String z = x.intern(); // 野生的 x 加入 StringTable,StringTable 中有了 "abc"
String y = "abc"; // 已有,不会产生新的对象,用的是 StringTable 中 "abc"
System.out.println(x == z); //true
System.out.println(y == z); //true
说明:JDK1.7以上的做法是String z = x.intern();
野生的x尝试转化为家养的,发现StringTable中没有 "abc"对象,所以会顺利变为家养的,同时返回StringTable中的 “abc” 对象 x,即 x 和 z 引用了同一对象,x == z。
接下来String y = "abc";
会到StringTable中看有没有"abc"对象了,发现有了,所以y引用的仍然是StringTable中的"abc"对象,所以y == z。
- 如果 StringTable 中没有(1.6 JDK 的做法)
String x = new String(new char[]{'a', 'b', 'c'}); // 野生的
String z = x.intern(); // 野生的 x 被复制后加入 StringTable,StringTable 中有了 "abc"
String y = "abc"; // 已有,不会产生新的对象,用的是 StringTable 中 "abc"
System.out.println(x == z); //false
System.out.println(y == z); //true
说明:JDK1.6的做法是String z = x.intern()
发现StringTable中没有"abc"对象,把野生的x拷贝一份,将拷贝的那份加入到StringTable中,x本身还是野生的,返回StringTable中的"abc"对象,所以 x != z。String y = "abc";
StringTable中已经有"abc"对象了,y引用该对象,所以 y == z。
3.家的位置
- JDK1.6:在方法区中
- JDK1.8:在堆内存中
总结
- 本文介绍了创建字符串的六种方式,可形象地分为两大类:家养的(通过字面量创建)和野生的(通过非字面量创建),家养的字符串对象是指可以由StringTable统一管理的对象。
- 同时介绍了StringTable这种数据结构,其底层采用的是hash表来实现的,即数组+链表,有点类似于HashMap。
- 通过
public native String intern()
方法可以将野生的字符串转化为家养的,这样子可以减少重复地创建相同的对象的现象,提高效率。 - StringTable内存位置的改变,由JDK1.6的方法区变为JDK1.8的堆内存
本文地址:https://blog.csdn.net/Beyondczn/article/details/107053931