字符串常量池StringTable简介

一、StringTable

  StringTable也叫串池,听名字就可以知道它和String的存储有关。在1.6,它是存在于永久代中的,到了1.7之后,StringTable被放在了堆中。
  为了说明StringTable,我使用了几个例子。

1.1 例一 字面量创建字符串

public static void main(String[] args){
    String s1 = "a"; 
    String s2 = "b";
    String s3 = "ab";
}

  在上面这段代码中,通过字符串字面量的方式新建了几个String。对于变量s1,s2,s3,我们都知道它们被存在了栈中。可是后面的字符串呢?它被存储在哪个地方呢?
  经过反编译,我们得到如下jvm指令。
例一
  这里的#2就是“a”,当类加载的时候,常量池中的信息会加载到运行时常量池中,此时的a,b,ab都还是符号,没有变成java对象。当运行此方法,执行到对应的代码时,才会将符号a变成“a”字符串对象,并将对象放入StringTable中。 需要注意的是,普通的java对象在类加载的时候就会生成并放入堆中,而这种方式生成的String不同,只有当执行到新建String的代码时才会生成字符串对象。
  StringTable是一个哈希表,长度固定,“a”就是哈希表的key。一开始的时候,会根据“a”到串池中找其对象,一开始是没有的,所以就会创建一个并放入串池中。串池为 [“a”]。
  执行到指令ldc #3时,会和上面一样,生成一个“b”对象并放入串池中,串池变为[“a”, “b”]。
  同样地,后面会生成“ab”对象并放入串池中。串池变为[“a”, “b”, “ab”]。

  小结一下:字面量创建字符串对象是懒惰的,即只有执行到相应代码才会创建相应对象(和一般的类不同)并放入串池中。如果串池中已经有了,就直接使用串池中的对象(让引用变量指向已有的对象)。串池中的对象只会存在一份,也就是只会有一个“a”对象。

1.2 例二 字符串变量拼接

  观察下面的代码,请问输出结果是什么?

	String s1 = "a";
    String s2 = "b";
    String s3 = "ab";
    String s4 = s1 + s2; 
    
    System.out.println(s3 == s4);  // false

  一样,先拿到反编译的jvm指令。
例二
  前面的指令我们已经很熟悉,观察行号为9的指令,这里是个new。这就说明s4的创建方式和s1、s2、s3不同,它是在堆里新建了一个对象,前面根据字面量创建的则是在串池中生成了字符串对象。
  观察行号9的指令后面的注释,可以知道这里是new了一个StringBuilder对象。接着看17,21,可以发现“s1 + s2”的方式是通过StringBuilder对象调用append方法实现的。
  最后看24,最后是调用了toString方法生成了新的字符串对象。

// StringBuilder中的toString方法
public String toString(){
    // 即根据拼接好的值,创建一个新的字符串对象
	return new String(value, 0, count);
}

  以上分析就想要说明:即当两个字符串变量拼接时,jvm会创建一个StringBuilder对象,利用其append方法实现变量的拼接。最后再通过其toString方法生成一个新的String对象。
  最后我们看输出结果,发现s3不等于s4,这说明s3指向串池中的“ab”对象,s4指向堆中的“ab”对象。这是两个不同的对象

1.3 例三 字符串常量拼接

  观察下面的代码,请问输出结果是什么?

	String s1 = "a";
    String s2 = "b";
    String s3 = "ab";
    String s4 = s1 + s2;
    String s5 = "a" + "b"; 
    
    System.out.println(s4 == s5); // false

  直接反编译:
例三
  可以看到s5的创建和s3一样。这也就是说,即两个字符串常量拼接时,最后的结果和直接用一个字符串字面量新建一个String一样,都是在串池里创建对应的对象。所以最后s4也是不等于s5的。
  至于为什么会以这种方式创建s5,这其实是编译期的优化。编译期间,编译器发现这是两个常量相加,结果是确定的,所以就直接让s5等于“ab”。

1.4 例四 intern方法

  接下来,我们来聊聊String的intern方法。

	String s = new String("a") + new String("b");
	String s2 = s.intern(); 
	
    System.out.println(s1 == "ab"); // true
	System.out.println(s == "ab")// false

  首先反编译。
例四
  反编译的结果我们也很熟悉,也是新建了一个StringBuilder实现字符串的拼接与创建。最终的结果就是在堆中创建了一个“ab”字符串对象。并且在串池中加入“a”,“b”对象。
  intern方法的作用就是在尝试把堆中对象放入串池中。如果串池中已有,会返回串池中的对象。并且s调用intern方法后依旧指向堆中的对象。如果串池中没有,会在串池中创建一个“ab”对象并返回,并且会让s指向串池中的“ab”对象。
  注意上面是jdk1.7之后的做法,在jdk1.6,当一个String调用intern方法时,如果串池中没有,会将堆中的字符串对象复制一份放到串池中,最后返回StringTable中刚加入的对象。

1.5 小结

  • 懒加载。当用字符串常量创建字符串时,在执行到对应的代码之前,该常量只是符号,只有执行到该代码时,才会创建相应的字符串对象并放入串池中。
  • 可以利用串池的机制,来避免重复创建字符串的对象。
  • 字符串变量的拼接的原理时StringBuilder (1.8)。
  • 字符串常量拼接的原理时编译期优化。
  • 可以使用intern方法,主动将串池中还没有的字符串对象放入串池( s.intern() )。

二、StringTable调优

  1)StringTable是一个哈希表,所以它的性能就和它的大小密切相关,所以StringTable调优其实就是调桶的个数。桶的数量越大,就越不容易产生哈希碰撞,效率就越好。可以通过 -XX:StringTableSize = …进行设置。
  2)考虑将字符串对象是否入池。如果要存的字符串过多并且很多重复,可以通过intern方法,把字符串从堆中入池,就可以减少字符串对象的个数,节约堆内存。
喜马拉雅山

  • 7
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值