String.intern()方法了解吗?

引言

在 JAVA 语言中有8中基本类型和一种比较特殊的类型String。这些类型为了使他们在运行过程中速度更快,更节省内存,都提供了一种常量池的概念。常量池就类似一个JAVA系统级别提供的缓存。

8种基本类型的常量池都是系统协调的,String类型的常量池比较特殊。它的主要使用方法有两种:

  • 直接使用双引号声明出来的String对象会直接存储在常量池中。
  • 如果不是用双引号声明的String对象,可以使用String提供的intern方法。intern 方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中

        在JVM运行时数据区中的方法区有一个常量池,但是发现在JDK1.6以后常量池被放置在了堆空间,因此常量池位置的不同影响到了Stringintern()方法的表现。

1. 认识intern()方法

JDK1.7后,常量池被放入到堆空间中,这导致intern()函数的功能不同

String s = new String("1");
s.intern();
String s2 = "1";
System.out.println(s == s2);

String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4);

输出结果:

JDK1.6以及以下:false false
JDK1.7以及以上:false true
1.1 JDK 1.6


JDK1.6中,常量池是在Perm区(属方法区)中的,跟堆完全分开的。使用引号声明的字符串都是会直接在字符串常量池中生成的,而 new 出来的 String 对象是放在堆空间中的。所以两者的内存地址肯定是不相同的。

intern()方法在JDK1.6中的作用是:比如String s = new String("1"),再调用s.intern(),作用是检查字符串池里是否存在"1"这么一个字符串,如果存在,就返回池里的字符串;如果不存在,该方法会把"1"添加到字符串池中,然后再返回它的引用。

1.2 JDK1.7


String s = new String("1");
s.intern();
String s2 = "1";
System.out.println(s == s2); // false
-----------------------------------------
String s = new String("1");
String s2 = "1";
System.out.println(s.intern() == s2);  //true
-----------------------------
String s3 = new String("1") + new String("1");  //常量池只有1,没有11
s3.intern();                    
String s4 = "11";
System.out.println(s3 == s4);      //true
---------------------------------

String s = newString("1"),生成了常量池中的“1” 和堆空间中的字符串对象。生成的是两个对象,常量池是个副本,但是返回的是堆的地址。

s.intern() 让s去常量池中找找发现1将其地址返回

String s2 = "1",这行代码是生成一个s2的引用指向常量池中的“1”对象,之前常量池已经有一个了,那么就可以直接拿过来。结果就是 s  s2 的引用地址明显不同。因此返回了false。

 (补充)
    String s3 = new String("1") + newString("1"),这行代码在字符串常量池中生成“1” ,并在堆空间中生成s3引用指向的对象(内容为"11")。注意此时常量池中是没有 “11”对象的。

为什么会常量池没有 “11”对象?
        常量池是方法区的一部分,是加载后的类信息,而执行了两个new String,显然就是要在运行时才能知道,那么就动态分配在堆上执行了,之前的“1”对象放在常量池,而运行在内存中才会生成“11”对象,当然,中间生成两个匿名对象“1”实现连接,s3指向的是这个运行后的对象。

        s3.intern(),这一行代码,是将 s3中的“11”字符串放入 String 常量池中,此时常量池中不存在“11”字符串,JDK1.6的做法是直接在常量池中生成一个 "11" 的对象。

那么调整顺序下:

String s = new String("1");   //常量池有1了,但是返回的是堆上的
String s2 = "1";              //返回的就是常量池上的地址
s.intern();
System.out.println(s == s2);    //false

String s3 = new String("1") + new String("1");    //常量池只有1
String s4 = "11";                        // 常量池新生成了11
s3.intern();      // 再把s3的地址放进去,因为s3已经指向了堆上的那个地址了,没有影响了,实际上返回的是s4的地址
System.out.println(s3 == s4);         //false
//System.out.println(s3.intern() == s4);  // true

输出结果:

JDK1.6以及以下:false false
JDK1.7以及以上:false false

JDK1.6/1.7: intern方法都会在常量池查一下吃否存在,如果存在,则返回常量池的引用,

        区别在于: 如果在常量池中找不到对应的字符串,则不会再对字符串拷贝了(1.6拷贝,地址和堆不一样),而只是在常量池中生成一个对原字符串的引用。

2. 对于final字段:

        常量的字符串“+”操作,编译阶段会直接合成一个字符串,如String str = "A" + "B",在编译时就直接合并成AB,于是会去常量池中查找是否由“AB”的引用,从而进行创建或引用

        对于final字段,编译期直接替换常量替换(对于非final字段的则是在运行期进行赋值处理的),final String str = "a",String str1 = str +"1";  那么编译时直接替换

        常量字符串和变量拼接时(如:String str3=baseStr + “01”;)会调用stringBuilder.append()在堆上创建新的对象。

3. 总结:

new的话会产生两个对象,与JDK变化一点关系都没有。

String str1 = new String("1")+ new String("2");      
System.out.println(str1.intern() == str1);       // true,  1.8是直接把引用拿过来,不是副本
System.out.println(str1 == "12");                // true,  有的话就找常量池里
String str2 = "12";//新加的一行代码,其余不变            //在常量池中已经创建了
String str1 = new String("1")+ new String("2");      // str1指向堆的
System.out.println(str1.intern() == str1);           //false    str1.intern是str2的地址
System.out.println(str1 == "12");                    //false    12是str2的地址

如果真懂了,那么就没问题了。

String str1 = new String("1") + new String("2");      //true
//改
String str1 = new String("1") ;                      //false ,new的话会产生两个对象
System.out.println(str1.intern() == str1);    

参考:

http://www.importnew.com/14142.html

https://blog.csdn.net/soonfly/article/details/70147205

https://blog.csdn.net/seu_calvin/article/details/52291082

猜你喜欢

转载自blog.csdn.net/Jae_Wang/article/details/80241743
今日推荐