什么是 字符串池?以下声明之间有什么区别:
String s = "hello";
String s = new String("hello");
这两个字符串被JVM存储时有什么区别吗?
字符串池是JVM对字符串驻留概念的特定实现:
在计算机科学中,字符串驻留是一种存储每个不同字符串值的唯一副本(必须是不可变的)的方法。驻留字符串使得某些字符串处理任务更加时间或空间有效,但需要在创建或驻留字符串时花费更多时间。这些不同的值存储在字符串驻留池中。
基本上,字符串驻留池允许运行时通过保留池中的不可变字符串来节省内存,以便应用程序的某些区域可以重用常见字符串的实例,而不是创建它的多个实例。
有趣的是,字符串驻留是享元设计模式的一个例子:
享元是一种软件设计模式。享元是一种通过与其他类似对象共享尽可能多的数据来最小化内存使用的对象;当简单的重复表示将使用不可接受数量的内存时,它是在大量使用对象的一种方式。
String s = GlobalStringObjectCache.get("hello");
- Charles Goodwin字符串常量池允许重用字符串常量,这是由于Java中的字符串是不可变的。如果在Java代码中随处重复相同的字符串常量,则实际上可以只有一个该字符串的拷贝存在于您的系统中,这是该机制的优点之一。
当使用 String s = "string constant";
时,您获得的是字符串池中的拷贝。但是,当您执行 String s = new String("string constant");
时,您会强制分配一份拷贝。
根据Andrew所述,该概念被JLS称为“interning”。
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指出:
字符串字面值是对 String 类的一个实例的引用,并且是从类或接口的二进制表示中的 CONSTANT_String_info 结构(§4.4.3)派生的。CONSTANT_String_info 结构给出了组成字符串字面值的 Unicode 代码点序列。
Java 编程语言要求相同的字符串字面值(即包含相同代码点序列的字面值)必须引用 String 类的相同实例 (JLS §3.10.5)。此外,如果在任何字符串上调用 String.intern 方法,则结果是对与该字符串作为字面值出现时返回的相同类实例的引用。因此,以下表达式必须具有 true 的值:
为了得到一个字符串字面量,Java虚拟机会检查由CONSTANT_String_info结构给出的码点序列。
("a" + "b" + "c").intern() == "abc"
如果方法String.intern之前已经被调用并且包含与CONSTANT_String_info结构给出的相同的Unicode码点序列的String类实例,则字符串字面量派生的结果是对该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
0
和3
:都加载了相同的ldc #2
常量(即字面量)12
:创建了一个新的字符串实例(使用#2
作为参数)35
:将a
和c
视为常规对象进行比较,使用if_acmpne
常量字符串在字节码中的表示方式有些神奇:
- 它拥有专用的CONSTANT_String_info结构,不像普通对象(如
new String
)- 该结构指向一个CONSTANT_Utf8_info Structure,其中包含数据。这是表示该字符串所需的唯一数据。
上述JVMS引用似乎表明,每当指向的Utf8相同时,就会由
ldc
加载相同的实例。我已经对字段进行了类似的测试:
static final String s = "abc"
通过ConstantValue Attribute指向常量表- 非 final 字段没有该属性,但仍可使用
ldc
初始化结论:字符串池有直接的字节码支持,并且内存表示高效。
奖励部分:与整数池相比较,其没有直接的字节码支持(即没有
CONSTANT_String_info
类似物)。
字符串对象基本上是字符串文字的包装器。为防止不必要的对象创建,唯一的字符串对象会被池化,并且JVM可能会决定在内部池化字符串字面量。如果编译器支持,则还提供对多次引用的字符串常量的直接字节码支持。
当您使用文字表达式时,例如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("========");
}
}
}
}
输出:
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
========
new String("word")
将仅在池中创建一个新字符串。但它将创建一个新的String对象,引用池中任何现有的字面量,因此检查对象引用相等性的结果。 - Software Engineer
==
和.equals
在Java中,使用==
运算符比较两个字符串时,会检查它们是否指向同一个对象。而使用.equals
方法比较字符串时,则会检查它们所包含的字符序列是否相同。因为字符串是对象,使用==
运算符比较字符串时,实际上是比较它们在内存中的引用地址。而使用.equals
则是比较它们的值。例如,在以下情况下:String str1 = "Hello"; String str2 = new String("Hello");
str1 == str2
的结果将会是false
,但str1.equals(str2)
的结果将会是true
。因此,通常应该使用.equals
方法来比较字符串的值,而不是使用==
运算符。 - Ciro Santilli OurBigBook.com