String和StringTable

    技术2022-07-10  140

    一、字符串前生今世

    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的堆内存
    Processed: 0.014, SQL: 9