ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

java 中的intern()方法

2022-02-28 17:34:40  阅读:254  来源: 互联网

标签:java String s2 s1 System intern new 方法


前言

最近遇到一个Intern()方法,代码如下,在 jdk1.8 的环境下得到如下的测试结果,给我整不会了,因此研究了一下这个方法,记录一下:

 1 package com.example.demo.test;
 2 
 3 /**
 4  * @description:
 5  * @author: luguilin
 6  * @date: 2022-02-25 11:14
 7  **/
 8 public class TestString {
 9     static void test01(){
10         String s1 = new String("1")+new String("23");
11         s1.intern();
12         String s2 = "123";
13         System.out.println( s1 == s2);//true
14     }
15 
16     static void test02(){
17         String s1 = new String("1")+new String("23");
18         String s2 = "123";
19         s1.intern();
20         System.out.println( s1 == s2); //false
21     }
22 
23     static void test03(){
24         String s1 = new String("1")+new String("23");
25         String s2 = "123";
26         System.out.println( s1 == s2);//false
27         s1.intern();
28         System.out.println( s1 == s2);//false
29         s1 = s1.intern();
30         System.out.println( s1 == s2);//true
31     }
32 
33     public static void main(String[] args) {
34         test01();
35         System.out.println("-----------------");
36         test02();
37         System.out.println("-----------------");
38         test03();
39     }
40 }

 

不说别的,上述方法中的test01(),为什么是True?

在我之前的印象里,s2指向方法区中的常量,s1应该指向的是堆上的对象,二者应该是不一样的啊?为什么是对的?

而我调换了一下s1.intern()语句的顺序以后,就又是false了。当我s1=s1.intern()以后,又相等了。这下彻底给我搞蒙了。

 

1、java的Intern()方法

在 jdk1.8 中,intern方法的定义 在Java的String类中是这样定义的,是一个本地方法,其中源码由C实现

public native String intern();

再来看一下源码的注释描述:

     * <p>
     * When the intern method is invoked, if the pool already contains a
     * string equal to this {@code String} object as determined by
     * the {@link #equals(Object)} method, then the string from the pool is
     * returned. Otherwise, this {@code String} object is added to the
     * pool and a reference to this {@code String} object is returned.
     * <p>

(直译:当调用 intern 方法时,如果池中已经包含一个与该方法确定的对象相等的字符串,则返回池中的字符串。 否则,将此对象添加到池中并返回对该对象的引用。)

翻译过来的意思就是:如果常量池中已经有了此字符串,那么将常量池中该字符串的引用返回,如果没有,那么将该字符串对象添加到常量池中,并且将引用返回。

首先要明白,这里注释的该字符串是调用此方法的字符串,返回的是引用。

代码如下:在 jdk1.8 中运行

 1 package com.example.demo.test02;
 2 
 3 
 4 public class TestIntern {
 5     void test01(){
 6         String s1 = new String("xyz");
 7         String s2 = "xyz";
 8         System.out.println(s1==s2); // false
 9     }
10 
11     void test02(){
12         String s2 = "xyz";
13         String s1 = new String("xyz");
14         System.out.println(s1==s2); // false
15     }
16 
17     public static void main(String[] args) {
18         TestIntern ins = new TestIntern();
19         ins.test01();
20         ins.test02();
21     }
22 }

 

 

2、new String("xyz")会创建几个对象?

动手实践后,发现再new String("xyz")有可能会创建一个(不好验证,但是可以通过下文的分析得出结论),也有可能会创建两个(可验证)。

结论:如果常量池中没有 xyz,那么就会创建两个,现在堆中创建一个,然后将对象copy到常量池中,也就是第二次创建,堆中和常量池中是两个对象。

事实上,在不同的jdk版本中,intern()方法的实现是不一样的,主要原因是永久代的去除和元空间的增加,见《java 内存分布》和《聊聊JVM分代模型:年轻代、老年代、永久代》。

 

Java6版本:

intern方法作用:确实如上述注释上所描述,如果常量池中没有字符串,则将该字符串对象加入常量池,并返回引用。

  ** 这里需要注意:Java6中常量池是在方法区中,而Java1.6版本hotspot采用永久带实现了方法区,永久代是和Java堆区分的,即就是常量池中没有字符串,那么将该字符串对象放入永久代的常量池中,并返回其引用。

 

Java7和Java8版本:

intern方法作用:和注释描述的并不同,

  如果常量池有,那么返回该字符串的引用。

  如果常量池没有,那么如果是"a".intern调用,那么就会把"a"放入常量池,并返回"a"在常量池中的引用。

          如果是new String("a").internal ,其中在 new String的时候上文已经说到过,会在堆和常量池各创建一个对象,那么这里返回的就是常量池的字符串a的引用。

          如果是new StringBuilder("a").internal,其中new StringBuilder会在堆中创建一个对象,常量池没有,这里调用intern方法后,**会将堆中字串a的引用放到常量池,注意这里始终只是创建了一个对象,

                返回的引用虽然是常量池的,但是常量池的引用是指向堆中字串a的引用的

 

再简单总结一下Java7和Java8的intern方法作用:

如果常量池没有,那么会将堆中的字符串的引用放到常量池,注意是引用,然后返回该引用。为什么Java7和Java8会不一样呢,原因就是 Java7之后(部分虚拟机,Hotspot,JRockit)已经将永久代的常量池、静态变量移出,放入了Java堆中,而永久代也在Java8中完全废弃,方法区改名为元空间。

既然常量池已经在Java6之后放入了堆中,那么如果堆中已经创建过此字符串的对象了,那么就没有必要在常量池中再创建一个一毛一样的对象了,直接将其引用拷贝返回就好了,因为都是处于同一个区域Java堆中。

 

 

3、实践验证

3.1 实际验证一下上述结论

  1 package com.example.demo.test02;
  2 
  3 
  4 public class TestIntern {
  5 
  6     /**
  7      * 在new的时候已经创建了两个对象,第二行,只是获取的第一行创建的常量池的对象的引用,实际的对象已经创建过了。
  8      * 这里是两个不同的对象,返回false。
  9      */
 10     void test01() {
 11         String s1 = new String("xyz");
 12         String s2 = "xyz";
 13         System.out.println(s1 == s2); // false
 14     }
 15 
 16     /**
 17      * 和上述一样,只不过这一次第一行,现在常量池创建了对象,第二行发现常量池已经有了,只在堆上创建了一次对象.
 18      * 但仍然是两个对象,引用不同,返回false。
 19      */
 20     void test02() {
 21         String s2 = "xyz";
 22         String s1 = new String("xyz");
 23         System.out.println(s1 == s2); // false
 24     }
 25 
 26     /**
 27      * 第一行,StringBuilder只会在堆中创建一个对象,第二行调用intern方法后,会将堆中的引用放到到常量池中。
 28      * 第三行发现常量池中已经有这个字符串的引用了,直接返回。
 29      * 因此是同一个引用,返回的都是第一次创建的堆中字串的引用
 30      */
 31     void test03() {
 32         StringBuilder s1 = new StringBuilder("xyz");
 33         String s2 = s1.toString().intern();
 34         String s3 = "xyz";
 35         System.out.println(s2 == s3); // true
 36     }
 37 
 38     /**
 39      * 和上述3的不同之处在于没有调用intern方法,因此结果输出不一样。
 40      */
 41     void test04() {
 42         StringBuilder s1 = new StringBuilder("xyz");
 43         String s2 = s1.toString();
 44         String s3 = "xyz";
 45         System.out.println(s2 == s3); // false
 46     }
 47 
 48     /**
 49      * new String之后使用 + 在Java中会进行编译优化,编译成字节码指令后,会将 + 优化成 先new Stringbuilder对象,然后调用append方法进行拼接。
 50      * 因此这里s1最终创建的时候,xyzz字符串并没有在常量池创建,只是在堆中创建了,因为就如同上面的test03()一样,是new Stringbuilder操作。
 51      * 所以在调用intern操作后,将其堆中的引用放入常量池并返回。
 52      * 所以后面的结果都是true,因为至始至终都是堆中的一个对象。
 53      */
 54     void test05() {
 55         String s1 = new String("xyz") + new String("z");
 56         String s2 = s1.intern();
 57         String s3 = "xyzz";
 58         System.out.println(s1 == s2); // true
 59         System.out.println(s1 == s3); // true
 60         System.out.println(s2 == s3); // true
 61     }
 62 
 63     /**
 64      * 和上述test05()是相反的,结果输出也不同。
 65      */
 66     void test06() {
 67         String s1 = new String("xyz") + new String("z");
 68         String s3 = "xyzz";
 69         System.out.println(s1 == s3); // false
 70     }
 71 
 72     /**
 73      * s1指向的对象并没有改变
 74      * s2指向常量区,s1指向堆,所以不一样
 75      */
 76     void test07() {
 77         String s1 = new String("xyz") + new String("z");
 78         String s2 = "xyzz";
 79         s1.intern();
 80         System.out.println(s1 == s2); // false
 81     }
 82 
 83     /**
 84      * s1.intern()之后,在常量区添加了堆中"xyzz"的引用,s2指向了这个常量池中"xyzz"对象
 85      * 因此二者不相等
 86      */
 87     void test08() {
 88         String s1 = new String("xyz") + new String("z");
 89         s1.intern();
 90         String s2 = "xyzz";
 91         System.out.println(s1 == s2); // false
 92     }
 93 
 94     /**
 95      * 第一个判断,
 96      * s1.intern()之后,在常量区添加了堆中"xyzz"的引用
 97      * s2也指向了常量池中这个引用,但是s1本身没有变,指的是堆中对象的引用,因此不相等
 98      * <p>
 99      * 第二个判断,
100      * s1 = s1.intern()以后,s1也指向了常量池中这个引用,因此相等
101      */
102     void test09() {
103         String s1 = new String("xyz") + new String("z");
104         s1.intern();
105         String s2 = "xyzz";
106         System.out.println(s1 == s2); // false
107         s1 = s1.intern();
108         System.out.println(s1 == s2); // true
109     }
110 
111 
112     public static void main(String[] args) {
113         TestIntern ins = new TestIntern();
114         ins.test01();
115         ins.test02();
116         ins.test03();
117         ins.test04();
118         ins.test05();
119         ins.test06();
120         ins.test07();
121         ins.test08();
122         ins.test09();
123     }
124 }

 

3.2 另一个面试题

 1 public class Test {
 2     public static void main(String[] args) {
 3         String str1 = new StringBuilder("计算机").append("软件").toString();
 4         String str2 = str1.intern();
 5         String str3 = new StringBuilder("ja").append("va").toString();
 6         String str4 = str3.intern();
 7         System.out.println(str1==str2);
 8         System.out.println(str3==str4);
 9     }
10 }

jdk1.8的输出答案是true和false。

jdk1.6的输出是两个false。

其他都好理解,为什么在1.8中,java这个变量,第二个判断,是false呢?这两代码不是一样么?理论上不应该是true么?

分析

在jdk1.6中

intern方法会把首次遇到的字符串复制到方法区中,返回的也是方法区这个字符串的引用。

而由StringBuilder创建的字符串实例在Java堆上,所以必然不是一个引用,所以返回false

 

str1指向堆,str2指向方法区,所以返回结果返回false

同理,str3和str4的返回结果也为false

  

下来我们看一看jdk1.7的intern方法

  jdk1.7的intern方法不会在复制实例,只是在常量池中记录首次出现的实例引用。

 

  因此str2指向的引用其实就是str1指向Java堆中StringBuilder创建的字符串实例。所以返回结果为true

  但是java这个字符串常量在编译期就已经在方法区的常量池中了,不符合首次出现,所以str4指向的是常量池中的java字面量

  所以返回结果为false。

  问题又来了,java这个字面量为什么在编译期就出现在了常量池。我们可以进入System类中。看看有什么东西。

  进入System类之后,我们发现这里有一个Version.init方法


再次进去查看

 

再次进去查看

 

 

 

 

哇,这么多常量,包括java,版本号,此版本号,都已经加载到常量池中,所以当我们调用str3.intern()方法时,java字面量已经存在,不符合首次出现,所以返回false,同理,我们也可以试一试这里的字面量,发现返回都是false。

String类的一个intern方法,涉及到了Java堆,java运行时常量池,涉及面很广泛,如果你不了解,是不是很吃亏。

 

 

4、最开始的问题

看了这么多以后,我以为我搞明白了最开始的问题,我突然发现,我最开始的代码和是第三节中代码是不一样的,如下,每一个代码都是静态方法:

下面一些栗子,自己思考吧,我也糊涂了

package com.example.demo.test;

/**
 * @description:
 * @author: luguilin
 * @date: 2022-02-25 11:14
 **/
public class TestString {
    static void test01(){
        String s1 = new String("1")+new String("23");
        String s2 = "123";
        s1.intern();
        System.out.println( s1 == s2); //false
    }

    static void test02(){
        String s1 = new String("1")+new String("23");
        s1.intern();
        String s2 = "123";
        System.out.println( s1 == s2);// false
    }

    void test03(){
        String s1 = new String("1")+new String("23");
        s1.intern();
        String s2 = "123";
        System.out.println( s1 == s2);// false
    }

    static void test04(){
        String s1 = new String("1")+new String("23");
        String s2 = "123";
        System.out.println( s1 == s2);//false
        s1.intern();
        System.out.println( s1 == s2);//false
        s1 = s1.intern();
        System.out.println( s1 == s2);//true
    }

    public static void main(String[] args) {
        test01();
        System.out.println("-----------------");
        test02();
        System.out.println("-----------------");
        TestString t = new TestString();
        t.test03();
        System.out.println("-----------------");
        test04();
    }
}

 

 

比较下面两个类,执行方法顺序不一样,结果不一样

 

先执行test01(),在执行test02(),结果是false  false

 1 package com.example.demo.test;
 2 
 3 /**
 4  * @description:
 5  * @author: luguilin
 6  * @date: 2022-02-25 11:14
 7  **/
 8 public class TestString {
 9     static void test01(){
10         String s1 = new String("1")+new String("23");
11         String s2 = "123";
12         s1.intern();
13         System.out.println( s1 == s2); //false
14     }
15 
16     static void test02(){
17         String s1 = new String("1")+new String("23");
18         s1.intern();
19         String s2 = "123";
20         System.out.println( s1 == s2);// false
21     }
22 
23     public static void main(String[] args) {
24         test01();
25         System.out.println("-----------------");
26         test02();
27         System.out.println("-----------------");
28     }
29 }

 

先执行test02(),在执行test01(),结果是true  false

 1 package com.example.demo.test;
 2 
 3 /**
 4  * @description:
 5  * @author: luguilin
 6  * @date: 2022-02-25 11:14
 7  **/
 8 public class TestString {
 9     static void test01(){
10         String s1 = new String("1")+new String("23");
11         String s2 = "123";
12         s1.intern();
13         System.out.println( s1 == s2); //false
14     }
15 
16     static void test02(){
17         String s1 = new String("1")+new String("23");
18         s1.intern();
19         String s2 = "123";
20         System.out.println( s1 == s2);// false
21     }
22 
23     public static void main(String[] args) {
24         test02();
25         System.out.println("-----------------");
26         test01();
27         System.out.println("-----------------");
28     }
29 }

 

标签:java,String,s2,s1,System,intern,new,方法
来源: https://www.cnblogs.com/r1-12king/p/15946648.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有