探索String类下的intern()方法

探索String类下的intern()方法

参考美团技术文章:深入解析String#intern

String类的使用方式

  1. String text1 = "text";

使用双引号声明出来的String对象会直接存储在常量池中

  1. String text2 = new String("text");

intern()方法概述

使用intern()方法,查询当前字符串是否存在于常量池中,不存在将当前字符串放在常量池中

  • 如果常量池中存在当前字符串,直接返回常量池中它的引用
  • 如果不存在,将当前字符串的引用放入常量池,再返回该字符串的引用
// 是一个本地方法
public native String intern();
复制代码

大体实现结构:JAVA 使用 jni 调用c++实现的StringTableintern方法, StringTableintern方法跟Java中的HashMap的实现是差不多的, 只是不能自动扩容。默认大小是1009。

在jdk7中,StringTable的长度可以通过一个参数指定:-XX:StringTableSize=99991

字符串常量池:

字符串常量池中只存储引用,不存储内容

HotSpot VM里,记录interned string的一个全局表叫做StringTable,它本质上就是个HashSet。注意它只存储对java.lang.String实例的引用,而不存储String对象的内容

实例探究

案例1

String s1 = new String("1");
s1.intern();
String s2 = "1";
System.out.println(s1 == s2);
// jdk6 => false
// jdk7 => false
复制代码

s1.intern();下移一行

String s1 = new String("1");
String s2 = "1";
s1.intern();
System.out.println(s1 == s2);
// jdk6 => false
// jdk7 => false
复制代码

为什么jdk6 输出结果都是false?

Untitled Diagram (3).png

jdk6中,常量池是放在Perm区的,new出来的对象是放在Heap堆区,将Heap区的对象地址和Perm区中的常量池的对象地址进行比较一定是不同的。调用intern()方法无效。

Perm 区是一个类静态的区域,主要存储一些加载类的信息,常量池,方法片段等内容,默认大小只有4m,一旦常量池中大量使用 intern 是会直接产生java.lang.OutOfMemoryError: PermGen space错误的

jdk7分析:

Untitled Diagram (5).png

在 jdk7 的版本中,字符串常量池已经从 Perm 区移到正常的 Java Heap 区域了。为什么要移动,Perm 区域太小是一个主要原因,当然据消息称 jdk8 已经直接取消了 Perm 区域,而新建立了一个元区域。应该是 jdk 开发者认为 Perm 区域已经不适合现在 JAVA 的发展了。

  • String s1 = new String("1");

    产生2个对象,分别是常量池对象1->”1″,堆对象2->new String(“1”)。

  • s1.intern();常量池中已存在”1″字符串对象,直接返回常量池中的引用,但并未使用。

  • String s2 = "1";常量池中已经存在”1″字符串对象了,直接返回引用。

  • s1指向的是堆中的引用,s2指向的是字符串常量池的引用。所以s1==s2返回false。

s1.intern();的次序在这里没有什么影响。

案例2

代码块1:

String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4);
// jdk6 => false
// jdk7 => true
复制代码

s3.intern();下移一行

代码块2:

String s3 = new String("1") + new String("1");
String s4 = "11";
s3.intern();
System.out.println(s3 == s4);
// jdk6 => false
// jdk7 => false
复制代码

代码块1:

jdk6就不做分析了。

jdk7:

Untitled Diagram (7).png

  • String s3 = new String("1") + new String("1");

    产生四个对象,分别是常量池对象1->”1″,对象2、对象3->new String(“1”),对象4 -> new String(“11”)。

  • s3.intern();常量池中不存在”11″,直接将堆中”11″字符串的引用存储在字符串常量池中。

  • String s4 = "11";常量池中已经存在”11″字符串的引用了,直接返回引用。

代码块2:

jdk6不做分析。

jdk7分析:

Untitled Diagram (8).png

  • String s3 = new String("1") + new String("1");

    产生四个对象,分别是常量池对象1->”1″,对象2、对象3->new String(“1”),对象4 -> new String(“11”)。

  • String s4 = "11";常量池中不存在”11″字符串对象的。

  • s3.intern();常量池中已存在”11″对象,返回”11″对象的引用,但并未使用。

代码改造

String s1 = new String("1");
String s2 = "1";
s1 = s1.intern();
System.out.println(s1 == s2);
// jdk7 true

String s3 = new String("1") + new String("1");
String s4 = "11";
s3 = s3.intern();
System.out.println("s3==s4:" + (s3 == s4));
// jdk7 true
复制代码

因为我们把s1的引用指向s1.intern();返回的新引用了。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享