• String的intern()方法详解



    前言

    在开发过程中很多朋友,由于不会正确使用intern(),导致开发的程序,执行效率比较差。同时最近发现一道非常有意思的关于intern()的面试题,这道面试题还是有不小的难度,相信很多朋友看到以后也不知道怎么解答,所以今天咱们深入详解下intern()。

    Returns a canonical representation for the string object.
    A pool of strings, initially empty, is maintained privately by the class String.
    When the intern method is invoked, if the pool already contains a string equal to this String object as determined by the equals(Object) method, then the string from the pool is returned. Otherwise, this String object is added to the pool and a reference to this String object is returned.
    It follows that for any two strings s and t, s.intern() == t.intern() is true if and only if s.equals(t) is true.
    All literal strings and string-valued constant expressions are interned. String literals are defined in section 3.10.5 of the The Java ™ Language Specification.


    当调用intern()时,如果池子里已经包含了一个与这个String对象相等的字符串,正如equals(Object)方法所确定的,那么池子里的字符串会被返回。否则,这个String对象被添加到池中,并返回这个String对象的引用。
    注意:这里说的池子就是字符串常量池,大白话就是,调用intern()后,如果String对象的值如果在字符串常量池中,直接返回常量池中的地址,否则这个String对象将被添加到字符串常量池中,并返回字符串常量池中的地址。
    由此可见,对于任何两个字符串s和t,当且仅当s.equals(t)为真时,s.intern() == t.intern()为真。
    所有字面字符串和以字符串为值的常量表达式都是interned。


    一、new String()创建了几个对象?

     public static void main(String[] args) {
     		 // 2个对象
     		// 对象1:new String()的时候会在字符串常量池创建一个"abc"的字符串
     		// 对象2:在堆空间创建一个a对象
                String a=new String("abc");
                
             // 1个对象
             // 在字符串常量池创建一个"ab"的字符串对象   
                String b="ab";
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    jvm内存结构分为方法区、堆、java虚拟机栈、本地方法栈、程序计数器等。当我们执行上面方法时。

    1. 首先将当前main方法压入java虚拟机栈的栈帧中。
    2. 在编译期的时候jvm会帮我们创建局部变量表,也就是我们当前方法的局部变量表。
    3. 局部变量有两个变量a和b,
      局部变量a的地址指向的是堆空间的对象a,对象a指向的是字符串常量池的“abc”
      局部变量b的地址指向的是字符串常量池的"ab";

    二、Stting a=new String(“ab”)+new String(“c”)创建了几个对象

     public static void main(String[] args) {
    			// 6个对象
    			// 对象1:new String("ab") 在堆空间创建一个对象
    			// 对象2:new String("ab") 在字符串常量池创建"ab"
    			// 对象3:new String("c") 在堆空间创建一个对象
    			// 对象4:new String("c") 在字符串常量池创建"c"
    			// 对象5:创建StringBuffer对象,在我们进行符串拼接的过程中,java底层会使用StringBuffer使用append进行拼接
    			// 对象6:最后会调用StringBuffer的tostring方法在堆空间创建abc对象
                String abc=new String("ab")+new String("c")
                        
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在上述例子当中我们一共创建了6个对象。可能大家都好奇,字符串"abc"怎么没有去创建。因为在我们StringBuffer中,它会单独维护一个char数组去创建我们拼接的字符串,所以这里就不会去字符串常量池申请"abc"
    在这里插入图片描述


    三、String的intern()方法

    当调用intern()时,如果池子里已经包含了一个与这个String对象相等的字符串,正如equals(Object)方法所确定的,那么池子里的字符串会被返回。否则,这个String对象被添加到池中,并返回这个String对象的引用。

    注意:这里说的池子就是字符串常量池,大白话就是,调用intern()后,如果String对象的值如果在字符串常量池中,直接返回常量池中的地址,否则这个String对象将被添加到字符串常量池中,并返回字符串常量池中的地址。

        public static void main(String[] args) {
            String a=new String("abc");
            String b="abc";
            System.out.println(a==b); // false
    
    
            String a1=new String("abcd").intern();
            String b1="abcd";
            System.out.println(a1==b1); // true
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    第一个案例是false,其实在上面已经和大家解释过了,第一个a指向的是堆空间的a对象(字符串"abc存储在a对象当中"),第二个b指向的是字符串常量池的"abc" 所以是false。

    第二个案例是true,因为当我们创建a1时,它首先还是会先在堆空间创建a对象,然后在字符串常量池创建"abcd"字符串,堆空间的a对象指向字符串常量池的"abcd"。因为我们调用了intern()方法,所以在这里它会直接返回的是字符串常量池的“abcd”地址。 b1则是直接指向字符串常量池的"abcd",所以返回结果是true。

        public static void main(String[] args) {
            String a = new String("ab") + new String("c");
            String b = "abc";
            System.out.println(a == b); // false
    
            String a1 = new String("ab") + new String("cd");
            a1.intern();
            String b1 = "abcd";
            System.out.println(a1 == b1); // true
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在上述这个案例中,第一个打印的是false第二个打印的是true。

    第一个案例false,其实在上面已经和大家解释过了,第一个a指向的是堆空间的a对象(字符串"abc存储在a对象当中"),第二个b指向的是字符串常量池的"abc" 所以是false。

    第二个案例是true,因为在这个案例中,我们a1指向的是堆空间的a1对象,a1对象里面存储了我们的"abcd",当我们调用intern()方法时,它会去字符串常量池创建一个对象,然后把当前对象的地址指向我们的a1的堆空间地址。(这里会有点绕,因为我们已经创建了字符串"abcd",所以为了节省空间。就不会在去字符串常量池去申请一个"abcd",而是直接创建一个对象指向我们堆空间的a1对象)


    四:面试题

    有了对以上的知识的了解,我们现在再来看常见的面试或笔试题就很简单了:

    Q:下列程序的输出结果:
    String s1 = “abc”;
    String s2 = “abc”;
    System.out.println(s1 == s2);
    A:true,均指向常量池中对象。
    
    Q:下列程序的输出结果:
    String s1 = new String(“abc”);
    String s2 = new String(“abc”);
    System.out.println(s1 == s2);
    A:false,两个引用指向堆中的不同对象。
    
    Q:下列程序的输出结果:
    String s1 = “abc”;
    String s2 = “a”;
    String s3 = “bc”;
    String s4 = s2 + s3;
    System.out.println(s1 == s4);
    A:false,因为s2+s3实际上是使用StringBuilder.append来完成,会生成不同的对象。
    
    Q:下列程序的输出结果:
    String s1 = “abc”;
    final String s2 = “a”;
    final String s3 = “bc”;
    String s4 = s2 + s3;
    System.out.println(s1 == s4);
    A:true,因为final变量在编译后会直接替换成对应的值,所以实际上等于s4=”a”+”bc”,而这种情况下,编译器会直接合并为s4=”abc”,所以最终s1==s4。
    
    Q:下列程序的输出结果:
    String s = new String(“abc”);
    String s1 = “abc”;
    String s2 = new String(“abc”);
    System.out.println(s == s1.intern());
    System.out.println(s == s2.intern());
    System.out.println(s1 == s2.intern());
    A:falsefalsetrue。
    
    Q:下列程序的输出结果:
      String a=new String("abc");
      String b="abc";
      System.out.println(a==b); 
      String a1=new String("abcd").intern();
      String b1="abcd";
      System.out.println(a1==b1); 
    A:falsetrue
    
    
    Q:下列程序的输出结果:
         String a = new String("ab") + new String("c");
         String b = "abc";
         System.out.println(a == b); 
         String a1 = new String("ab") + new String("cd");
         a1.intern();
         String b1 = "abcd";
         System.out.println(a1 == b1); 
    A:falsetrue
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    五:总结

    对于程序中大量使用存在的字符串时,尤其存在很多已经重复的字符串时,使用intern()方法能够节省内存空间。

    大的网站平台,需要内存中存储大量的字符串。比如社交网站,很多人都存储:北京市、海淀区等信息。这时候如果字符串都调用intern()方法,就会很明显降低内存的大小。

    以下是java在不同版本中String字符串的内存变化,希望可以对你有所帮助。
    在这里插入图片描述

  • 相关阅读:
    WPF自定义Panel:让拖拽变得更简单
    AEM TESTPRO K50 ROADSHOW华南区路演
    互联网医院App开发:构建医疗服务的技术指南
    网络威胁情报git【全面】
    JavaWeb 七个步骤,完成一个servlet的hello world程序
    小猴吃苹果-第12届蓝桥杯Scratch选拔赛真题精选
    港陆证券:服装家纺公司上半年投资并购力度加大
    滑动窗口最大值问题
    Python每日一练--LEETCODE--重复子字符串
    net SSH.NET 清理服务器文件
  • 原文地址:https://blog.csdn.net/qq_48157004/article/details/133861422