Fork me on GitHub

Java中String类的intern方法详解

String类中intern方法的详解

其实博主也是在看Java虚拟机时看到的这么一个有趣的东西,所以今天就来给大家说说

咱们先来看看例子

1
2
3
4
5
6
7
public class Test_01_String_intern {
public static void main(String[] args) {
String s1 = "hellow";
String s2 = "hellow";
System.out.println(s1==s2);//true
}
}

上面代码的运行结果是true,这个显然很简单,但是我还是要说一下

我们知道在jdk1.6的时候jvm中会在方法区中单独给String类型设置一块存储区域,我一般叫他”字符串常量池”,也就是说我们的字符串会放在那个区域中(字符串是常量大家都知道吧),当Java程序中首次出现一个字符串时,jvm会在方法区中去找,如果没有这个字符串就将它放在字符串常量池中,当我们下一次再次创建跟它相同的字符串时,这个时候jvm会先去字符串常量池中寻找,如果有就将常量池中的字符串地址返回给它返回给它。(博主这里说的观点只是为了让你先大致了解,这个观点细化后是不正确,后面我会逐步深入说的)

再来看一个例子

1
2
3
4
5
6
7
public class Test_01_String_intern {
public static void main(String[] args) {
String s3 = "world";
String s4 = new String("world");
System.out.println(s3==s4);//false
}
}

上面代码运行的结果是false

我们来分析一下:

  1. 首先声明s3,程序首次碰到”world”字符串,所以将”world”放入字符串常量池中,并返回引用(地址)给s3
  2. 定义s4,系统发现字符串常量池中有”world”所以将world给它,然后jvm发现还有一个new关键字,于是它在堆内存中给s4重新申请一块空间并存放”world”,并且将堆内存中的地址给s4,之前从字符串常中拿的字符串就不再用了,也就是说现在s4它所指向的地址是堆内存那个world的地址,而不是字符串常量池中的,所以两者相比是false

String.intern方法

先来看看1.6jdk的API如何说的大概看一下
image

通俗的来讲就是String的intern方法就是将当前字符串放入字符串常量池中,如果常量池中没有那么将该字符串放入常量池并返回常量池中的引用,如果有则直接返回常量池中的引用。

我们来看看具体的例子我们再来分析

1
2
3
4
5
6
public class Test_01_String_intern {
public static void main(String[] args) {
String s6 = new String("风清扬")+new String("独孤求败");
System.out.println(s6.intern()==s6);
}
}

这段代码的运行结果怎么样,如果我们换成jdk1.6结果又是怎么样呢???

上段代码在jdk1,6中是false

在jdk1.7中结果为true

这是什么原因呢???

jdk1.6和jdk1.7中小区别

在jdk1.6中我说过String类型的字符串常量池在方法区中,而jdk1.7的时候将字符串常量池移入到了堆内存中,这样也导致了intern方法的改变

  • 在jdk1.6中,intern()方法会把首次遇到的字符串实例复制到永久代中(也就是方法区中),返回的也是永久代中的这个字符串实例的引用,而我们
1
String s6 = new String("风清扬")+new String("独孤求败");

这段代码中创建的字符串实例在堆上,这个我之前刚刚说过。所以现在s6指向的是不是堆内存中的地址。

而s6.intern()方法是将s6这个字符串复制一份放入字符串常量池中(注意:此时字符串常量池中是没有”风清扬独孤求败”这个字符串的,为什么没有??不是说会将首次遇到的字符串放入字符串常量池中吗??为什么它没有将s6放入呢??因为你这个s6是拼接起来的啊,所以没有,自己可以百度),此时将复制到字符串常量池中的字符串的引用返回出去,也就是说s6.intern()返回的就是字符串常量池中放入的字符串的引用,所以现在s6.intern()指向的是字符串常量池中的地址,而s6指向的是堆内存中的地址,所以两者一比较肯定是false

  • 在jdk1.7中的intern()的实现不会再复制实例,只是在常量池中记录首次出现的实例引用,因此intern()返回的引用和由下面代码创建的那个字符串实例是同一个。
    1
    String s6 = new String("风清扬")+new String("独孤求败");

也就是说当我们执行s6.intern()方法的时候,jvm会将首次遇到的字符串的引用,记录下来并不是复制。于是它将s6这个引用记录下来,返回出去,也就是说s6.intern()返回的就是s6这个引用,于是当我们去比较的时候他们是相同的,也就是说字符串常量池中现在保存的是s6的引用,我们可以验证一下

1
2
3
4
5
6
7
8
public class Test_01_String_intern {
public static void main(String[] args) {
String s6 = new String("风清扬")+new String("独孤求败");
System.out.println(s6.intern()==s6);
String s11 = "风清扬独孤求败";
System.out.println(s11==s6);
}
}

输入结果是true,true

1
String s11 = "风清扬独孤求败";

这句代码就是从字符串常量池中去找,发现有”风清扬独孤求败”,然后就将字符串常量池中的”风清扬独孤求败”这个字符串的引用(就是s6)给s11,然后我们比较s11和s6他们是相等得。

但是我们删去一句代码
1
2
3
4
5
6
7
8
public class Test_01_String_intern {
public static void main(String[] args) {
String s6 = new String("风清扬")+new String("独孤求败");
<!--System.out.println(s6.intern()==s6);-->
String s11 = "风清扬独孤求败";
System.out.println(s11==s6);
}
}

注释掉:System.out.println(s6.intern()==s6);,其实就是注释了s6.intern()这个方法,当我们注释了这个方法再运行,答案又是false了

这个简单啊,你想啊你注释后intern方法没有调用就不会保存s6的引用,所以自然是false了