Java 的 String.intern() 方法的目的是什么?

7

我知道在Java中有两种创建字符串的方式:

String a = "aaa";
String b = new String("bbb");

使用第一种方法,Java会在字符串池中创建一个String对象,并使“a”引用它。(假设“aaa”之前不在池中。)
使用第二种方法,将在堆中创建一个对象,但是JVM是否也会在字符串池中创建一个对象?
在这篇文章Questions about Java's String pool中,@Jesper说:

If you do this:

String s = new String("abc");

then there will be one String object in the pool, the one that represents the literal "abc", > and there will be a separate String object, not in the pool, that contains a copy of the > content of the pooled object.

如果这是真的,那么每次使用new String("bbb");都会在池中创建一个对象"bbb",也就是说无论以上哪种方式,Java 都将在池中创建一个字符串对象。那么intern()有什么用呢?在文档中写道:

当调用 intern 方法时,如果池中已经包含一个与此String对象相等(由equals(Object)方法确定)的字符串,则返回池中的字符串。否则,将此String对象添加到池中,并返回对此String对象的引用。

这意味着有些情况下字符串不在池中,这可能吗?哪个是真的?

1
把以下与编程有关的内容从英语翻译成中文。返回仅翻译的文本: - mafu
4个回答

9
你知道在Java编程语言中,String是一个不可变的对象,这意味着一旦构建就无法更改。由于这个原因,JVM有能力维护一个文字池,有助于减少内存使用和提高性能。每当使用String文字时,JVM会检查文字池。如果该文字已经存在,则返回相同的引用。如果该文字不存在,则会创建一个新的字符串对象并添加到文字池中。
当你尝试像一个原始值或一个文字/常量一样创建一个String时,这个理论被应用。
String str = "bbb";

但是当您创建一个新的字符串对象时

String str = new String("bbb");

上述规则被覆盖,总是创建一个新的实例。
但是,在String类中,intern API可以用于从文字池中选择String引用,即使您使用new操作符创建了一个String。请查看以下示例。虽然str3使用new运算符创建,但由于我们使用了intern方法,JVM从literal池中获取了引用。
public class StringInternExample {

    public static void main(final String args[]) {

        final String str = "bbb";
        final String str1 = "bbb";
        final String str2 = new String("bbb");
        final String str3 = new String("bbb").intern();

        System.out.println("str == str1 : "+(str == str1));
        System.out.println("str == str2 : "+(str == str2));
        System.out.println("str == str3 : "+(str == str3));
    }
}

上述代码的输出:
str == str1 : true
str == str2 : false
str == str3 : true

你可以看一下:关于字符串的不可变性的困惑 答案来源:http://ourownjava.com/java/java-string-immutability-and-intern-method/ Shishir

6

我们的String对象基本上有两种方式进入池中:

  • 在源代码中使用文字字面量,例如"bbb"
  • 使用intern方法。

intern方法用于将不属于池的String对象添加到池中。例如:

String bb = "bbb".substring(1); // substring creates a new object

System.out.println(bb == "bb");          // false
System.out.println(bb.intern() == "bb"); // true

稍有不同的是:
System.out.println(new String("bbb").intern() == "bbb"); // true

new String("bbb") 会创建两个对象...

String fromLiteral = "bbb";                     // in pool
String fromNewString = new String(fromLiteral); // not in pool

但这更像是一个特例。它会创建两个对象,因为"bbb"引用了一个对象

字符串字面量是对类String实例的引用[...]。

此外,一个字符串字面量总是引用类String的同一个实例。

new String(...)则会创建其副本。

然而,有许多方法可以创建String对象而不使用字面量,例如:

  • 所有执行某种突变的String方法(如substringsplitreplace等)
  • 从某种输入中读取String,例如ScannerReader
  • 至少有一个操作数不是编译时常量的拼接。

intern允许您将它们添加到池中或检索现有对象(如果有)。在大多数情况下,对字符串进行内部化是不必要的,但它可以用作优化,因为:

  • 它可以让您使用==进行比较。
  • 它可以节省内存,因为重复的字符串可以进行垃圾回收。

那么你的意思是使用 new 关键字,Java 会在池中创建一个字符串对象? - trojantale
3
使用构造函数不会添加到池中,调用 intern 方法则会添加。 - Radiodef
new String("bbb") 会创建两个对象...String fromLiteral = "bbb"; // 在常量池中 String fromNewString = new String(fromLiteral); // 不在常量池中你的意思是 new String("bbb") 等价于这两行代码吗?所以使用 new String("bbb") 会导致 "bbb" 被 interned 吗? - trojantale
清除。另外,当我说“使用new关键字时,Java会在池中创建一个字符串对象”时,我实际上是指像new String("a")这样的方式,而不是其他方式,对于造成的歧义我感到抱歉。 - trojantale
1
没错,那正是我想表达的意思。 - trojantale
显示剩余3条评论

1

是的,new String("abc")会在内存中创建一个新对象,因此建议避免使用它。请参阅Josh Bloch的《Effective Java》的第5条,“避免创建不必要的对象”,其中更好地解释了这一点:

作为不应该做的极端示例,请考虑以下语句:

String s = new String("stringette"); // 不要这样做!

该语句每次执行时都会创建一个新的String实例,并且没有必要创建这些对象。String构造函数的参数(“stringette”)本身就是一个String实例,与构造函数创建的所有对象在功能上相同。如果这种用法出现在循环中或者在频繁调用的方法中,则可能会无谓地创建数百万个String实例。改进后的版本如下所示:

String s = "stringette";

这个版本只使用一个String实例,而不是每次执行都创建一个新实例。此外,保证同一虚拟机中运行的任何其他代码都会重用包含相同字符串字面量的对象[JLS,3.10.5]。

http://uet.vnu.edu.vn/~chauttm/e-books/java/Effective.Java.2nd.Edition.May.2008.3000th.Release.pdf


1
使用第二种方法,将会在堆中创建一个对象,但是 JVM 也会在字符串池中创建一个对象吗?
是的,但是它是字符串文字 "bbb" 确保被池化的字符串。字符串构造器创建一个新的字符串对象,该对象是具有相同长度和内容的副本 - 新创建的字符串不会自动池化。
如果这是真的,那么每次使用 new String("bbb"); 都会在池中创建一个对象 "bbb",这意味着通过上述任何一种方式,Java 总是会在池中创建一个字符串对象。那么 intern() 方法用于什么?
只有字符串字面量会自动地被池化。其他字符串对象必须手动池化,如果这是所期望的行为的话。
这意味着存在一些字符串不在池中的情况,这可能吗?
除了手动调用 String.intern 之外,只有字符串字面量会导致池化的字符串。

虽然我建议在这种情况下使用专门的集合,但在可以用于避免创建额外重复对象的情况下,内部化可能是有用的。一些内部化可以带来好处的用例 - 例如,相同的字符串值可能会出现多次 - 包括JSON键和XML元素/属性名称。


1 这很容易理解,考虑:

String _b = "bbb";          // string from string literal (this is interned)
String b = new String(_b);  // create a NEW string via "copy constructor"
b == _b           // -> false (new did NOT return an interned string)
b.equals(_b)      // -> true  (but it did return an equivalent string)
b.intern() == _b  // -> true  (which interns to .. the same string object)

1
@trojantale new String(x) 只是简单地创建了一个新的字符串;新的字符串对象与 x 不是同一个字符串对象(尽管它们具有相同的长度/内容),并且也没有被内部化。 - user2864740
new String(x) 创建一个新字符串对象,它与 x 不同,x 是否被添加到字符串池中? - trojantale
我指的是使用new String("bbb"),"bbb"会被添加到池中吗?抱歉造成了误解。 - trojantale
1
@trojantale 是的,但这与 new String 没有任何关系。它被添加是因为 "bbb" 是一个字符串字面量。就像我的例子中,String _b = "bbb";。在那里,这就是它被添加到字符串池中的时间/原因 - user2864740
1
正如你所说的,在源代码中使用“bbb”时,“它引用了一个对象”,并且“它之所以添加,仅仅是因为‘bbb’是一个字符串字面量”,我知道这与 new String 没有关系,但它确实导致了“bbb”在池中被interned,对吗?因此,如果你使用String b = new String("bbb"),无论你是否调用 b.intern(),"bbb"已经被interned,对吗? - trojantale
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接