什么是Java字符串池以及"s"与新String("s")有何不同?

sgo*_*les 111 java string

String Pool是什么意思?以下声明之间有什么区别:

String s = "hello";
String s = new String("hello");
Run Code Online (Sandbox Code Playgroud)

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

And*_*are 152

字符串池是JVM对字符串实习概念的特定实现:

在计算机科学中,字符串实习是一种只存储每个不同字符串值的一个副本的方法,该字符串值必须是不可变的.实习字符串使得一些字符串处理任务更加节省时间或空间,代价是在创建或实现字符串时需要更多时间.不同的值存储在字符串实习池中.

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

作为一个有趣的旁注,字符串实习是flyweight设计模式的一个例子:

Flyweight是一种软件设计模式.flyweight是一个通过与其他类似对象共享尽可能多的数据来最小化内存使用的对象; 当简单的重复表示将使用不可接受的内存量时,它是一种大量使用对象的方法.

  • 很好的答案,但它没有直接回答这个问题.从你的描述来看,听起来代码示例会同时引用相同的内存,对吗?也许您可以在答案中添加简单的摘要声明. (13认同)
  • 复制粘贴谷歌甚至没有回答问题的答案不应该得到这么多的赞成 (7认同)

Mic*_*yan 58

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

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

  • 第二个代码片段分配对池中现有文字的新引用,而不是副本.内存中只有一个文字副本. (4认同)

Cir*_*四事件 26

JLS

正如安德鲁所提到,这个概念被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"; }
Run Code Online (Sandbox Code Playgroud)

和编译单位:

package other;
public class Other { public static String hello = "Hello"; }
Run Code Online (Sandbox Code Playgroud)

产生输出:

true true true true false true
Run Code Online (Sandbox Code Playgroud)

JVMS

JVMS 7 5.1说:

字符串文字是对类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"
Run Code Online (Sandbox Code Playgroud)

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

  • 如果先前在类String的实例上调用了String.intern方法,该类包含与CONSTANT_String_info结构给出的Unicode代码点序列相同的Unicode代码点序列,则字符串文字派生的结果是对类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);
    }
}
Run Code Online (Sandbox Code Playgroud)

我们有恒定的池:

#2 = String             #32   // abc
[...]
#32 = Utf8               abc
Run Code Online (Sandbox Code Playgroud)

并且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
Run Code Online (Sandbox Code Playgroud)

请注意:

  • 03:ldc #2加载相同的常量(文字)
  • 12:创建一个新的字符串实例(使用#2as参数)
  • 35:ac作为常规对象进行比较if_acmpne

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

并且上面的JVMS引用似乎表明只要Utf8指向的是相同的,就会加载相同的实例ldc.

我已经对字段进行了类似的测试,并且:

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

结论:对字符串池有直接的字节码支持,并且内存表示是有效的.

额外:将其与整数池进行比较,整数池没有直接的字节码支持(即没有CONSTANT_String_info模拟).


Chr*_*ett 17

String对象基本上是字符串文字的包装器.合并唯一的字符串对象以防止不必要的对象创建,并且JVM可以决定在内部汇集字符串文字.对于常量多次引用的String常量,还有直接的字节码支持,只要编译器支持这一点.

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

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

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("========");
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:

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
========
Run Code Online (Sandbox Code Playgroud)


kil*_*joy 7

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

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

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

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


归档时间:

查看次数:

58593 次

最近记录:

7 年,1 月 前