String和StringTable

一、字符串前生今世

1. 字符串有六种基本的创建(出生)方式

  • 使用 char[] 数组配合 new 来创建
    String s = new String(new char[]{'a', 'b', 'c'});
    String和StringTable_第1张图片
  • 使用 byte[] 数组配合 new 来创建
    String s = new String(new byte[]{97, 98, 99});
    String和StringTable_第2张图片
  • 使用 int[] 数组配合 new 来创建
    String s = new String(new int[]{0x1F602}, 0, 1);
    String和StringTable_第3张图片
  • 使用 已有字符串配合 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;
}

String和StringTable_第4张图片

  • 使用字面量创建(不使用 new )
    String s = "abc";
    String和StringTable_第5张图片
    说明:其中“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 表的基本特性
String和StringTable_第6张图片
下面的代码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:在方法区中
    String和StringTable_第7张图片
  • JDK1.8:在堆内存中
    String和StringTable_第8张图片

总结

  1. 本文介绍了创建字符串的六种方式,可形象地分为两大类:家养的(通过字面量创建)和野生的(通过非字面量创建),家养的字符串对象是指可以由StringTable统一管理的对象。
  2. 同时介绍了StringTable这种数据结构,其底层采用的是hash表来实现的,即数组+链表,有点类似于HashMap。
  3. 通过public native String intern()方法可以将野生的字符串转化为家养的,这样子可以减少重复地创建相同的对象的现象,提高效率。
  4. StringTable内存位置的改变,由JDK1.6的方法区变为JDK1.8的堆内存

你可能感兴趣的:(字符串,数据结构,java,jdk,c++)