【发布时间】:2011-04-17 14:40:57
【问题描述】:
我对 Java 中的 StringPool 感到困惑。我在阅读 Java 中的 String 章节时遇到了这个问题。请用外行的方式帮助我理解 StringPool 的实际作用。
【问题讨论】:
标签: java
我对 Java 中的 StringPool 感到困惑。我在阅读 Java 中的 String 章节时遇到了这个问题。请用外行的方式帮助我理解 StringPool 的实际作用。
【问题讨论】:
标签: java
这将打印true(即使我们不使用equals 方法:比较字符串的正确方法)
String s = "a" + "bc";
String t = "ab" + "c";
System.out.println(s == t);
当编译器优化你的字符串字面量时,它发现s 和t 具有相同的值,因此你只需要一个字符串对象。它是安全的,因为 String 在 Java 中是不可变的。
结果,s 和 t 都指向同一个对象,并节省了一些内存。
命名“字符串池”源于所有已定义的字符串都存储在某个“池”中,并且在创建新的String 对象之前编译器会检查此类字符串是否已定义。
【讨论】:
String 不像您提供的其他类型那样原始,但通常被视为原始类型 - 因此它在 java 语言中相当“特殊”。然而,java 确实对包装类进行了类似的优化:If the value p being boxed is true, false, a byte, or a char in the range \u0000 to \u007f, or an int or short number between -128 and 127 (inclusive), then let r1 and r2 be the results of any two boxing conversions of p. It is always the case that r1 == r2. 这些公共值是“池化”的,很像 Strings。
我不认为它实际上做了很多,它看起来只是一个字符串文字的缓存。如果您有多个值相同的字符串,则它们都将指向字符串池中的相同字符串文字。
String s1 = "Arul"; //case 1
String s2 = "Arul"; //case 2
在情况 1 中,新创建的文字 s1 并保存在池中。但在情况 2 中,文字 s2 引用 s1,它不会创建新的。
if(s1 == s2) System.out.println("equal"); //Prints equal.
String n1 = new String("Arul");
String n2 = new String("Arul");
if(n1 == n2) System.out.println("equal"); //No output.
【讨论】:
当 JVM 加载类或以其他方式看到文字字符串或某些代码 interns 字符串时,它会将字符串添加到一个几乎隐藏的查找表中,该查找表中每个此类字符串都有一个副本。如果添加了另一个副本,运行时会对其进行排列,以便所有文字都引用同一个字符串对象。这被称为“实习”。如果你说类似
String s = "test";
return (s == "test");
它会返回true,因为第一个和第二个“测试”实际上是同一个对象。以这种方式比较实习字符串可能比String.equals 快得多,很多,因为只有一个引用比较,而不是一堆 char 比较。
您可以通过调用String.intern() 将字符串添加到池中,这将返回字符串的池化版本(这可能与您正在实习的字符串相同,但如果依赖它会很疯狂——你经常不能确定到底什么代码已经加载并运行到现在并实习了相同的字符串)。池化版本(从intern 返回的字符串)将等于任何相同的文字。例如:
String s1 = "test";
String s2 = new String("test"); // "new String" guarantees a different object
System.out.println(s1 == s2); // should print "false"
s2 = s2.intern();
System.out.println(s1 == s2); // should print "true"
【讨论】:
concat 不能那么容易地被优化掉。与+ 组合在一起的字符串可能会被任何自尊的编译器预先组合,因为值永远不会改变。但是编译器无法真正猜测一个函数是否会一直返回相同的值(有些不会),所以它不会尝试。如果您在示例中使用concat,则“ab”、“c”、“a”和“bc”将被实习,但“abc”不会(因为它不是文字,而且您的代码没有tintern它)。然而,使用+,一个不错的编译器会看到两个字符串都是“abc”并编译它。
让我们从虚拟机规范中的一段引述开始:
加载包含字符串文字的类或接口可能会创建一个新的字符串对象(第 2.4.8 节)来表示该文字。如果已经创建了一个 String 对象来表示该文字的先前出现,或者如果在表示与文字相同的字符串的 String 对象上调用了 String.intern 方法,则可能不会发生这种情况。
这可能不会发生 - 这是一个提示,String 对象有一些特别之处。通常,调用构造函数会总是创建一个新的类实例。字符串不是这种情况,尤其是当使用文字“创建”字符串对象时。这些字符串存储在全局存储(池)中 - 或者至少引用保存在池中,并且每当需要已知字符串的新实例时,vm 从池中返回对对象的引用。在伪代码中,它可能是这样的:
1: a := "one"
--> if(pool[hash("one")] == null) // true
pool[hash("one") --> "one"]
return pool[hash("one")]
2: b := "one"
--> if(pool[hash("one")] == null) // false, "one" already in pool
pool[hash("one") --> "one"]
return pool[hash("one")]
所以在这种情况下,变量a 和b 持有对同一个对象的引用。在这种情况下,我们有(a == b) && (a.equals(b)) == true。
如果我们使用构造函数,情况就不是这样了:
1: a := "one"
2: b := new String("one")
同样,"one" 是在池中创建的,但随后我们从相同的文字创建了一个新实例,在这种情况下,它会导致 (a == b) && (a.equals(b)) == false
那么为什么我们有一个字符串池?字符串,尤其是字符串文字在典型的 Java 代码中被广泛使用。而且它们是不可变的。并且不可变允许缓存 String 以节省内存并提高性能(创建工作量更少,收集的垃圾更少)。
作为程序员,我们不必太在意字符串池,只要牢记在心:
(a == b) && (a.equals(b)) 可能是 true 或 false(总是使用 equals 比较字符串)char[](因为您不知道谁在实际使用该字符串)【讨论】:
==,而不是函数调用、两个length()调用,以及equals可能发生的一堆char比较。
String.equals() 与实习字符串一起使用,因为String.equals() 首先进行== 比较
== 与equals 一样安全和一致——只是被误解了。将它与对象一起使用的人通常分为两类。有些人不了解价值与身份语义(并且 == 与引用类型比较身份)——那些人应该总是使用String.equals。还有一些人确实了解,但有意识地选择身份。只要你知道你的对象来自哪里,这同样可靠。 == 与对象一起工作是有原因的——尤其是为什么它不只是调用 equals。
if (s1==s2) 对大多数人来说看起来很可疑(并被 FindBugs 标记)。我只是指出,您仍然可以获得与字符串池进行比较的性能提升,而无需编写假定字符串被保留的代码