什么是Javastring池,“s”与新的String(“s”)有什么不同?
什么是string池 ? 以下声明有什么区别:
String s = "hello"; String s = new String("hello");
JVM存储这两个string有什么区别吗?
string池是JVM特定的string实现概念的实现:
在计算机科学中,string实习是一种仅存储每个不同string值的一个副本的方法,它必须是不可变的。 实习中的string使一些string处理任务的时间或空间效率更高,但需要更多的时间来创build或实施string。 不同的值存储在string实习生池中。
基本上,string内联池允许运行时通过在池中保存不可变string来节省内存,以便应用程序的区域可以重用常用string的实例,而不是创build多个实例。
作为一个有趣的方面说明,stringinterning是轻量级devise模式的一个例子:
享元是一个软件devise模式。 轻量级是通过与其他类似对象共享尽可能多的数据来最小化内存使用的对象; 当简单的重复表示会使用不可接受的内存量时,这是一种大量使用对象的方法。
string池允许重用string常量,这是可能的,因为Java中的string是不可变的。 如果在Java代码中重复使用相同的string常量,则实际上在系统中只能有一个该string的副本,这是此机制的优点之一。
当你使用String s = "string constant";
你会得到string池中的副本。 但是,当你做String s = new String("string constant");
你强制拷贝被分配。
JLS
正如安德鲁所提到的 ,这个概念被JLS称为“实习”。
JLS 7相关文章3.10.5 :
而且,一个string文字总是引用类String的同一个实例。 这是因为string文字 – 或者更一般地说,是常量expression式(§15.28)的值的string – 被“interned”,以便使用方法String.intern共享唯一的实例。
例3.10.5-1。 string文字
由编制单位(第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文字是对类String的一个实例的引用,并且是从类或接口的二进制表示中的CONSTANT_String_info结构(第4.4.3节)派生的。 CONSTANT_String_info结构给出了构成string文字的Unicode代码点序列。
Java编程语言要求相同的string文字(即包含相同的代码点序列的文字)必须引用同一类String(JLS§3.10.5)的实例。 此外,如果在任何string上调用String.intern方法,则结果是对同一个类实例的引用,如果该string显示为文字,则会返回该实例。 因此,以下expression式必须具有真值:
("a" + "b" + "c").intern() == "abc"
为了派生string文字,Java虚拟机检查由CONSTANT_String_info结构给出的代码点序列。
如果方法String.intern先前在包含与CONSTANT_String_info结构相同的Unicode代码点序列的String类实例上调用,则string文字派生的结果是对类String相同实例的引用。
否则,将创build一个包含由CONSTANT_String_info结构给出的Unicode代码点序列的类String的新实例; 该类实例的引用是string文字派生的结果。 最后,调用新的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
:创build一个新的string实例(以#2
作为参数) -
35
:将a
和c
作为常规对象与if_acmpne
进行比较
常量string的表示在字节码上非常神奇:
- 它有一个专用的CONSTANT_String_info结构,不像常规对象(例如
new String
) - 该结构指向包含数据的CONSTANT_Utf8_info结构 。 这是表示string的唯一必要数据。
而上面的JVMS报价似乎表示,只要Utf8指向的是相同的,则相同的实例由ldc
加载。
我已经做了类似的testing领域,并且:
-
static final String s = "abc"
通过ConstantValue属性指向常量表 - 非final字段没有该属性,但仍可以使用
ldc
进行初始化
结论 :对string池有直接的字节码支持,并且内存表示是有效的。
奖金:比较那整体池 ,没有直接的字节码支持(即没有CONSTANT_String_info
模拟)。
string对象基本上是string文字的包装。 独特的string对象被合并以防止不必要的对象创build,并且JVM可以决定在内部汇集string文字。 对于被多次引用的string常量,也提供直接的字节码支持,只要编译器支持。
当你使用一个文字时,说String str = "abc";
,使用池中的对象。 如果你使用String str = new String("abc");
,会创build一个新的对象,但现有的string字面值可以在JVM级别或字节码级别(在编译时)重用。
您可以通过在for循环中创build大量string并使用==
运算符来检查对象是否相等来检查这一点。 在以下示例中, string.value
对于String
是私有的,并保存使用的string字面值。 因为它是私人的,它必须通过reflection来访问。
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 ========
令人费解的是,没有人直接回答这个问题,但大多数答案都有很多赞扬。
简而言之,首先在string池中创build一个条目,这个条目可以被重用(由于上面的链接的不变性,效率更高),第二个创build一个新的String对象(成本更高)。
两个对象都住在堆中。 对两者的引用将在线程的堆栈中。
http://www.journaldev.com/797/what-is-java-string-pool清楚地表明了这是如何实现的;
JVM
在实例化string文字时执行一些技巧来提高性能并降低内存开销。 为了减less在JVM中创build的String对象的数量,String类保存了一个string池。 每次您的代码创build一个string文字,JVM首先检查string文字池。 如果该string已经存在于池中,则对池实例的引用将返回。 如果该string不存在于池中,则新的String对象实例化,然后放入池中。
public class Program { public static void main(String[] args) { String str1 = "Hello"; String str2 = "Hello"; System.out.print(str1 == str2); } }
输出:true
不幸的是,当你使用
String a=new String("Hello");
string对象是从string文字池中创build的,即使在池中已经存在相同的string。
public class Program { public static void main(String[] args) { String str1 = "Hello"; String str2 = new String("Hello"); System.out.print(str1 == str2 ); } }
输出:错误