【问题标题】:What is the Java string pool and how is "s" different from new String("s")? [duplicate]什么是 Java 字符串池,“s”与 new String(“s”) 有何不同? [复制]
【发布时间】:2011-01-29 23:33:02
【问题描述】:

字符串池是什么意思?以下声明之间有什么区别:

String s = "hello";
String s = new String("hello");

JVM对这两个字符串的存储有什么区别吗?

【问题讨论】:

标签: java string


【解决方案1】:

令人费解的是,没有人直接回答这个问题,但大多数答案都有很多赞成票。

简而言之,第一个在字符串池中创建一个条目,可以重复使用(由于上述关于不变性的链接更有效,基本上,实习),第二个创建一个新的字符串对象(成本更高) .

这两个对象都存在于堆中。 对两者的引用都将在线程的堆栈中。

http://www.journaldev.com/797/what-is-java-string-pool 清楚地了解了这是如何实现的

【讨论】:

    【解决方案2】:

    JLS

    正如by Andrew 所述,JLS 将这个概念称为“实习”。

    来自JLS 7 3.10.5的相关段落:

    此外,字符串字面量总是引用 String 类的同一个实例。这是因为字符串字面量 - 或者更一般地说,作为常量表达式值的字符串(第 15.28 节) - 是“内部的”,以便使用 String.intern 方法共享唯一实例。

    示例 3.10.5-1。字符串字面量

    由编译单元组成的程序(§7.3):

    package testPackage;
    class Test {
        public static void main(String[] args) {
            String hello = "Hello", lo = "lo";
            System.out.print((hello == "Hello") + " ");
            System.out.print((Other.hello == hello) + " ");
            System.out.print((other.Other.hello == hello) + " ");
            System.out.print((hello == ("Hel"+"lo")) + " ");
            System.out.print((hello == ("Hel"+lo)) + " ");
            System.out.println(hello == ("Hel"+lo).intern());
        }
    }
    class Other { static String hello = "Hello"; }
    

    和编译单元:

    package other;
    public class Other { public static String hello = "Hello"; }
    

    产生输出:

    true true true true false true
    

    JVMS

    JVMS 7 5.1 says:

    字符串字面量是对 String 类实例的引用,它派生自类或接口的二进制表示形式的 CONSTANT_String_info 结构(第 4.4.3 节)。 CONSTANT_String_info 结构给出了构成字符串文字的 Unicode 代码点序列。

    Java 编程语言要求相同的字符串文字(即包含相同代码点序列的文字)必须引用 String 类的相同实例(JLS §3.10.5)。此外,如果对任何字符串调用 String.intern 方法,则结果是对同一类实例的引用,如果该字符串以文字形式出现,则会返回该类实例。因此,以下表达式的值必须为 true:

    ("a" + "b" + "c").intern() == "abc"
    

    为了派生字符串文字,Java 虚拟机检查 CONSTANT_String_info 结构给出的代码点序列。

    • 如果先前已在包含与 CONSTANT_String_info 结构给出的相同的 Unicode 代码点序列的类 String 的实例上调用方法 String.intern,则字符串文字派生的结果是对该类的引用String 类的相同实例。

    • 否则,将创建一个 String 类的新实例,其中包含 CONSTANT_String_info 结构给出的 Unicode 代码点序列;对该类实例的引用是字符串文字派生的结果。最后调用新String实例的intern方法。

    字节码

    查看 OpenJDK 7 上的字节码实现也很有启发性。

    如果我们反编译:

    public class StringPool {
        public static void main(String[] args) {
            String a = "abc";
            String b = "abc";
            String c = new String("abc");
            System.out.println(a);
            System.out.println(b);
            System.out.println(a == c);
        }
    }
    

    我们在常量池上:

    #2 = String             #32   // abc
    [...]
    #32 = Utf8               abc
    

    main:

     0: ldc           #2          // String abc
     2: astore_1
     3: ldc           #2          // String abc
     5: astore_2
     6: new           #3          // class java/lang/String
     9: dup
    10: ldc           #2          // String abc
    12: invokespecial #4          // Method java/lang/String."<init>":(Ljava/lang/String;)V
    15: astore_3
    16: getstatic     #5          // Field java/lang/System.out:Ljava/io/PrintStream;
    19: aload_1
    20: invokevirtual #6          // Method java/io/PrintStream.println:(Ljava/lang/String;)V
    23: getstatic     #5          // Field java/lang/System.out:Ljava/io/PrintStream;
    26: aload_2
    27: invokevirtual #6          // Method java/io/PrintStream.println:(Ljava/lang/String;)V
    30: getstatic     #5          // Field java/lang/System.out:Ljava/io/PrintStream;
    33: aload_1
    34: aload_3
    35: if_acmpne     42
    38: iconst_1
    39: goto          43
    42: iconst_0
    43: invokevirtual #7          // Method java/io/PrintStream.println:(Z)V
    

    注意方法:

    • 03:加载相同的 ldc #2 常量(文字)
    • 12:创建了一个新的字符串实例(以#2 作为参数)
    • 35: acif_acmpne 比较为常规对象

    常量字符串的表示在字节码上相当神奇:

    上面的 JVMS 引用似乎是说,每当指向的 Utf8 相同时,ldc 就会加载相同的实例。

    我对字段做了类似的测试,并且:

    • static final String s = "abc" 通过ConstantValue Attribute 指向常量表
    • 非最终字段没有该属性,但仍可以使用ldc 进行初始化

    结论:字符串池有直接字节码支持,内存表示效率高。

    奖励:将其与不支持直接字节码的Integer pool 进行比较(即没有CONSTANT_String_info 模拟)。

    【讨论】:

    • 2 个不同的对象,一个在字符串池中,abc 有两个引用,即 a 和 b。另一个在 abc 堆中有一个引用,即 c。
    【解决方案3】:

    字符串对象基本上是字符串文字的包装器。唯一的字符串对象被池化以防止不必要的对象创建,并且 JVM 可能决定在内部池化字符串文字。对多次引用的字符串常量也有直接的字节码支持,前提是编译器支持这一点。

    当您使用文字时,例如String str = "abc";,将使用池中的对象。如果您使用String str = new String("abc");,则会创建一个新对象,但现有的字符串文字可以在 JVM 级别或字节码级别(在编译时)重用。

    您可以通过在 for 循环中创建大量字符串并使用 == 运算符来检查对象是否相等来自行检查。在以下示例中,string.valueString 私有的,并保存使用的字符串文字。因为它是私有的,所以必须通过反射来访问。

    public class InternTest {
        public static void main(String[] args) {
            String rehi = "rehi";
            String rehi2 = "rehi";
            String rehi2a = "not rehi";
            String rehi3 = new String("rehi");
            String rehi3a = new String("not rehi");
            String rehi4 = new String(rehi);
            String rehi5 = new String(rehi2);
            String rehi6 = new String(rehi2a);
    
            String[] arr  = new String[] { rehi, rehi2, rehi2a, rehi3, rehi3a, rehi4, rehi5, rehi6 };
            String[] arr2 = new String[] { "rehi", "rehi (2)", "not rehi", "new String(\"rehi\")", "new String(\"not rehi\")", "new String(rehi)", "new String(rehi (2))", "new String(not rehi)" };
    
            Field f;
            try {
                f = String.class.getDeclaredField("value");
                f.setAccessible(true);
            } catch (NoSuchFieldException | SecurityException e) {
                throw new IllegalStateException(e);
            }
    
            for (int i = 0; i < arr.length; i++) {
                for (int j = 0; j < arr.length; j++) {
                    System.out.println("i: " +arr2[i]+", j: " +arr2[j]);
                    System.out.println("i==j: " + (arr[i] == arr[j]));
                    System.out.println("i equals j: " + (arr[i].equals(arr[j])));
                    try {
                        System.out.println("i.value==j.value: " + (f.get(arr[i]) == f.get(arr[j])));
                    } catch (IllegalArgumentException | IllegalAccessException e) {
                        throw new IllegalStateException(e);
                    }
                    System.out.println("========");
                }
            }
        }
    }
    

    输出:

    i: rehi, j: rehi
    i==j: true
    i equals j: true
    i.value==j.value: true
    ========
    i: rehi, j: rehi (2)
    i==j: true
    i equals j: true
    i.value==j.value: true
    ========
    i: rehi, j: not rehi
    i==j: false
    i equals j: false
    i.value==j.value: false
    ========
    i: rehi, j: new String("rehi")
    i==j: false
    i equals j: true
    i.value==j.value: true
    ========
    i: rehi, j: new String("not rehi")
    i==j: false
    i equals j: false
    i.value==j.value: false
    ========
    i: rehi, j: new String(rehi)
    i==j: false
    i equals j: true
    i.value==j.value: true
    ========
    i: rehi, j: new String(rehi (2))
    i==j: false
    i equals j: true
    i.value==j.value: true
    ========
    i: rehi, j: new String(not rehi)
    i==j: false
    i equals j: false
    i.value==j.value: false
    ========
    i: rehi (2), j: rehi
    i==j: true
    i equals j: true
    i.value==j.value: true
    ========
    i: rehi (2), j: rehi (2)
    i==j: true
    i equals j: true
    i.value==j.value: true
    ========
    i: rehi (2), j: not rehi
    i==j: false
    i equals j: false
    i.value==j.value: false
    ========
    i: rehi (2), j: new String("rehi")
    i==j: false
    i equals j: true
    i.value==j.value: true
    ========
    i: rehi (2), j: new String("not rehi")
    i==j: false
    i equals j: false
    i.value==j.value: false
    ========
    i: rehi (2), j: new String(rehi)
    i==j: false
    i equals j: true
    i.value==j.value: true
    ========
    i: rehi (2), j: new String(rehi (2))
    i==j: false
    i equals j: true
    i.value==j.value: true
    ========
    i: rehi (2), j: new String(not rehi)
    i==j: false
    i equals j: false
    i.value==j.value: false
    ========
    i: not rehi, j: rehi
    i==j: false
    i equals j: false
    i.value==j.value: false
    ========
    i: not rehi, j: rehi (2)
    i==j: false
    i equals j: false
    i.value==j.value: false
    ========
    i: not rehi, j: not rehi
    i==j: true
    i equals j: true
    i.value==j.value: true
    ========
    i: not rehi, j: new String("rehi")
    i==j: false
    i equals j: false
    i.value==j.value: false
    ========
    i: not rehi, j: new String("not rehi")
    i==j: false
    i equals j: true
    i.value==j.value: true
    ========
    i: not rehi, j: new String(rehi)
    i==j: false
    i equals j: false
    i.value==j.value: false
    ========
    i: not rehi, j: new String(rehi (2))
    i==j: false
    i equals j: false
    i.value==j.value: false
    ========
    i: not rehi, j: new String(not rehi)
    i==j: false
    i equals j: true
    i.value==j.value: true
    ========
    i: new String("rehi"), j: rehi
    i==j: false
    i equals j: true
    i.value==j.value: true
    ========
    i: new String("rehi"), j: rehi (2)
    i==j: false
    i equals j: true
    i.value==j.value: true
    ========
    i: new String("rehi"), j: not rehi
    i==j: false
    i equals j: false
    i.value==j.value: false
    ========
    i: new String("rehi"), j: new String("rehi")
    i==j: true
    i equals j: true
    i.value==j.value: true
    ========
    i: new String("rehi"), j: new String("not rehi")
    i==j: false
    i equals j: false
    i.value==j.value: false
    ========
    i: new String("rehi"), j: new String(rehi)
    i==j: false
    i equals j: true
    i.value==j.value: true
    ========
    i: new String("rehi"), j: new String(rehi (2))
    i==j: false
    i equals j: true
    i.value==j.value: true
    ========
    i: new String("rehi"), j: new String(not rehi)
    i==j: false
    i equals j: false
    i.value==j.value: false
    ========
    i: new String("not rehi"), j: rehi
    i==j: false
    i equals j: false
    i.value==j.value: false
    ========
    i: new String("not rehi"), j: rehi (2)
    i==j: false
    i equals j: false
    i.value==j.value: false
    ========
    i: new String("not rehi"), j: not rehi
    i==j: false
    i equals j: true
    i.value==j.value: true
    ========
    i: new String("not rehi"), j: new String("rehi")
    i==j: false
    i equals j: false
    i.value==j.value: false
    ========
    i: new String("not rehi"), j: new String("not rehi")
    i==j: true
    i equals j: true
    i.value==j.value: true
    ========
    i: new String("not rehi"), j: new String(rehi)
    i==j: false
    i equals j: false
    i.value==j.value: false
    ========
    i: new String("not rehi"), j: new String(rehi (2))
    i==j: false
    i equals j: false
    i.value==j.value: false
    ========
    i: new String("not rehi"), j: new String(not rehi)
    i==j: false
    i equals j: true
    i.value==j.value: true
    ========
    i: new String(rehi), j: rehi
    i==j: false
    i equals j: true
    i.value==j.value: true
    ========
    i: new String(rehi), j: rehi (2)
    i==j: false
    i equals j: true
    i.value==j.value: true
    ========
    i: new String(rehi), j: not rehi
    i==j: false
    i equals j: false
    i.value==j.value: false
    ========
    i: new String(rehi), j: new String("rehi")
    i==j: false
    i equals j: true
    i.value==j.value: true
    ========
    i: new String(rehi), j: new String("not rehi")
    i==j: false
    i equals j: false
    i.value==j.value: false
    ========
    i: new String(rehi), j: new String(rehi)
    i==j: true
    i equals j: true
    i.value==j.value: true
    ========
    i: new String(rehi), j: new String(rehi (2))
    i==j: false
    i equals j: true
    i.value==j.value: true
    ========
    i: new String(rehi), j: new String(not rehi)
    i==j: false
    i equals j: false
    i.value==j.value: false
    ========
    i: new String(rehi (2)), j: rehi
    i==j: false
    i equals j: true
    i.value==j.value: true
    ========
    i: new String(rehi (2)), j: rehi (2)
    i==j: false
    i equals j: true
    i.value==j.value: true
    ========
    i: new String(rehi (2)), j: not rehi
    i==j: false
    i equals j: false
    i.value==j.value: false
    ========
    i: new String(rehi (2)), j: new String("rehi")
    i==j: false
    i equals j: true
    i.value==j.value: true
    ========
    i: new String(rehi (2)), j: new String("not rehi")
    i==j: false
    i equals j: false
    i.value==j.value: false
    ========
    i: new String(rehi (2)), j: new String(rehi)
    i==j: false
    i equals j: true
    i.value==j.value: true
    ========
    i: new String(rehi (2)), j: new String(rehi (2))
    i==j: true
    i equals j: true
    i.value==j.value: true
    ========
    i: new String(rehi (2)), j: new String(not rehi)
    i==j: false
    i equals j: false
    i.value==j.value: false
    ========
    i: new String(not rehi), j: rehi
    i==j: false
    i equals j: false
    i.value==j.value: false
    ========
    i: new String(not rehi), j: rehi (2)
    i==j: false
    i equals j: false
    i.value==j.value: false
    ========
    i: new String(not rehi), j: not rehi
    i==j: false
    i equals j: true
    i.value==j.value: true
    ========
    i: new String(not rehi), j: new String("rehi")
    i==j: false
    i equals j: false
    i.value==j.value: false
    ========
    i: new String(not rehi), j: new String("not rehi")
    i==j: false
    i equals j: true
    i.value==j.value: true
    ========
    i: new String(not rehi), j: new String(rehi)
    i==j: false
    i equals j: false
    i.value==j.value: false
    ========
    i: new String(not rehi), j: new String(rehi (2))
    i==j: false
    i equals j: false
    i.value==j.value: false
    ========
    i: new String(not rehi), j: new String(not rehi)
    i==j: true
    i equals j: true
    i.value==j.value: true
    ========
    

    【讨论】:

    • 字符串 s1 = 新字符串(“abc”),字符串 s2 = 新字符串(“abc”)。 s1 != s2,这是因为两个对象不同。但是在内存中只有一个或两个'abc'副本?剂量 jvm 在构造函数创建时分配“abc”。
    • 在大多数情况下(当 String 和底层 char 数组的大小相等时),新的 String 对象将具有与传递的 String 对象相同的底层 char 数组。所以内存中有一个 'abc' 的副本(表示为一个 char 数组),但有两个字符串使用这个。
    • 这个答案是完全错误的,所以应该删除赞成票。如果池中没有具有相同值的字符串文字,则构造 new String("word") 只会在池中创建一个新字符串。然而,它将创建一个新的 String 对象,该对象引用池中的任何现有文字,因此是检查对象引用相等性的结果。
    • 我澄清了答案。之前是对的,你看错了。
    【解决方案4】:

    字符串池允许重复使用字符串常量,这是可能的,因为 Java 中的字符串是不可变的。如果您在 Java 代码中的所有位置重复相同的字符串常量,您实际上只能在系统中拥有该字符串的一个副本,这是这种机制的优势之一。

    当您使用String s = "string constant"; 时,您将获得字符串池中的副本。但是,当您执行 String s = new String("string constant"); 时,您会强制分配一个副本。

    【讨论】:

    • 你的意思是这样在内存中有两个“字符串常量”的副本?我知道 String s = "string constant" 会在字符串池中分配它。 String s = new String("string constant") 会将字符串分配给?
    • 第二个代码片段分配对池中现有文字的新引用,而不是副本。内存中只有一个文本副本。
    • "当你做 String s = new String("string constant"); 你强制分配一个副本",你能解释得更详细吗?什么是“副本”?
    【解决方案5】:

    字符串池是JVM对string interning概念的具体实现:

    在计算机科学中,字符串实习 是一种只存储一个副本的方法 每个不同的字符串值,其中 必须是不可变的。实习字符串 做一些字符串处理任务 更节省时间或空间 需要更多时间的成本 字符串被创建或实习。这 不同的值存储在字符串中 实习生池。

    基本上,字符串实习池允许运行时通过在池中保存不可变字符串来节省内存,以便应用程序的区域可以重用公共字符串的实例,而不是创建多个实例。

    作为一个有趣的旁注,字符串实习是 flyweight design pattern 的一个示例:

    享元是一种软件设计 图案。享元是一种物体 通过共享来最小化内存使用 尽可能多的数据与其他 类似的物体;这是一种使用方式 大量对象时,一个简单的 重复表示将使用 不可接受的内存量。

    【讨论】:

    • 很好的答案,但它不能直接回答问题。根据您的描述,听起来代码示例都会引用相同的内存,对吗?也许您可以在答案中添加一个简单的总结性陈述。
    • 不正确。代码示例在这两种情况下都使用相同的内部字符串文字,但第二行创建了一个新对象。如果有助于将其概念化,请将第一行视为:String s = GlobalStringObjectCache.get("hello");
    • 从谷歌复制粘贴一个甚至没有回答问题的答案不应该得到这么多的赞成
    猜你喜欢
    • 2010-09-24
    • 2014-05-01
    • 1970-01-01
    • 2012-03-27
    • 1970-01-01
    • 2017-02-06
    • 2012-06-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多