我们什么时候应该在 String 字面量上使用 String 的 intern 方法

When should we use intern method of String on String literals

提问人:Rakesh Juyal 提问时间:12/6/2009 最后编辑:RaviRakesh Juyal 更新时间:11/17/2023 访问量:118522

问:

根据 String#intern() 的说法,如果在 String 池中找到 String,则该方法应该从 String 池中返回 String,否则将在 String 池中添加一个新的字符串对象并返回该 String 的引用。intern

所以我试了一下:

String s1 = "Rakesh";
String s2 = "Rakesh";
String s3 = "Rakesh".intern();

if ( s1 == s2 ){
    System.out.println("s1 and s2 are same");  // 1.
}

if ( s1 == s3 ){
    System.out.println("s1 and s3 are same" );  // 2.
}

我本来以为它会在 s3 被拘禁时被打印出来,并且不会被打印出来。但结果是:两条线都被打印出来了。这意味着,默认情况下,字符串常量是内部的。但如果是这样,那我们为什么需要这个方法呢?换句话说,我们什么时候应该使用这种方法?s1 and s3 are sames1 and s2 are sameintern

java 字符串 string-interning

评论

17赞 Jorn 12/6/2009
您链接的 Javadoc 还指出“所有文本字符串和字符串值常量表达式都已嵌入”。
2赞 Gregory Pakosz 12/6/2009
欺骗 stackoverflow.com/questions/1833581/when-to-use-intern
1赞 Bozho 12/6/2009
不是完全重复的..
1赞 Rakesh Juyal 12/6/2009
@Jorn:没错。那么,为什么我们要有公共方法呢?我们难道不应该有私人方法吗,这样没有人可以访问它。或者这种方法有什么目的吗?internintern
2赞 bobbyalex 8/22/2013
@RakeshJuyal:intern 方法是在字符串类型上定义的,字符串类型可以是字符串文字或变量。如果方法是私有的,您将如何实习变量?

答:

11赞 Bozho 12/6/2009 #1

默认情况下,字符串文本和常量处于内部状态。 也就是说,(由 String 文字声明),但是 ."foo" == "foo"new String("foo") != new String("foo")

评论

5赞 Rakesh Juyal 12/6/2009
所以,问题是我们什么时候应该使用 ,intern
0赞 Bozho 12/6/2009
有人指出 stackoverflow.com/questions/1833581/when-to-use-intern,以及其他一些问题,其中一些是昨天提出的。
1赞 dkb 7/5/2019
让我知道我对这句话的理解是否正确:,是否正确。 --> 在这里,一个字符串字面量“foo”在字符串池中创建,一个在堆中创建,因此总共创建了 2 个对象。String literals and constants are interned by defaultnew String("foo")
249赞 Filipe Miguel Fonseca 12/6/2009 #2

Java 会自动实习 String 字面量。这意味着在许多情况下,== 运算符对 String 的作用方式似乎与对 ints 或其他基元值的作用相同。

由于 String 文字的 interning 是自动的,因此该方法将用于使用intern()new String()

以您的示例为例:

String s1 = "Rakesh";
String s2 = "Rakesh";
String s3 = "Rakesh".intern();
String s4 = new String("Rakesh");
String s5 = new String("Rakesh").intern();

if ( s1 == s2 ){
    System.out.println("s1 and s2 are same");  // 1.
}

if ( s1 == s3 ){
    System.out.println("s1 and s3 are same" );  // 2.
}

if ( s1 == s4 ){
    System.out.println("s1 and s4 are same" );  // 3.
}

if ( s1 == s5 ){
    System.out.println("s1 and s5 are same" );  // 4.
}

将返回:

s1 and s2 are same
s1 and s3 are same
s1 and s5 are same

在所有情况下,除了变量之外,使用运算符显式创建了一个值,并且在其结果中没有使用方法,它是一个返回JVM的字符串常量池的不可变实例。s4newintern

有关更多信息,请参见 JavaTechniques“字符串相等和实习”。

评论

0赞 styfle 1/4/2012
我假设 Java 会自动实习 String literals 以进行优化。它之所以能安全地做到这一点,只是因为字符串是不可变的,对吗?
0赞 hfrmobile 4/11/2013
Java 新手(我来自 C#.NET 世界),我有时会在 Java 遗留项目中看到“”.intern(),所以如果我理解正确的话,这对于空字符串也是“无稽之谈”。
4赞 HybrisHelp 7/26/2013
@Miguel很好的解释,我的问题是如何在您的示例中创建对象。这是我的假设:第一个 OB1 第二个 OB2 所以其余的 (s2,s3,s5) 引用在“字符串池”中创建的相同对象 (OB1) 所以我可以说用于防止创建新对象的方法,如果我的假设是错误的,那么给我方向。String s1 = "Rakesh";String s4 = new String("Rakesh");.intern()string pool
1赞 SJuan76 3/10/2014
JavaTechniques 链接已损坏
22赞 Carl Smotricz 12/6/2009 #3

在最近的一个项目中,一些巨大的数据结构是用从数据库读入的数据(因此不是字符串常量/文字)建立的,但有大量的重复。这是一个银行应用程序,像一个适度的(可能是 100 或 200 个)公司的名称之类的东西到处都是。数据结构已经很大了,如果所有这些公司名称都是唯一的对象,它们就会溢出内存。相反,所有数据结构都引用了相同的 100 或 200 个 String 对象,从而节省了大量空间。

interned Strings 的另一个小优点是,如果保证所有涉及的字符串都被 interned ,则可以(成功!)比较 Strings。除了更精简的语法外,这也是一种性能增强。正如其他人所指出的那样,这样做会带来引入编程错误的巨大风险,因此这应该只是作为最后的手段。==

缺点是,嵌套 String 比简单地将其扔到堆上需要更多的时间,并且嵌套 String 的空间可能会受到限制,具体取决于 Java 实现。最好在处理已知合理数量的字符串时完成,其中包含许多重复项。

评论

1赞 Rakesh Juyal 12/6/2009
@The downside is that interning a String takes more time than simply throwing it on the heap, and that the space for interned Strings may be limited即使您不对 String 常量使用 intern 方法,它也会自动被 intern 使用。
3赞 David Rodríguez - dribeas 12/6/2009
@Rakesh:在任何给定的类中,通常没有那么多的字符串常量,因此这不是常量的空间/时间问题。
1赞 Carl Smotricz 12/6/2009
是的,Rakesh 的评论不适用,因为实习字符串只是(明确地)使用以某种方式“生成”的字符串完成的,无论是通过内部操作还是从数据库等方式检索。对于常量,我们别无选择。
2赞 Alexander Pogrebnyak 12/6/2009
+1.我认为这是一个很好的例子,说明实习是有意义的。不过,我不同意字符串。==
2赞 Anil Uttani 12/1/2017
从 Java 7 开始,“字符串池”被实现到堆空间中,因此它获得了存储实习生、垃圾回收的所有优点,并且它的大小不受限制,它可以增加到堆大小。(你永远不需要那么多内存来存储字符串)
19赞 Alexander Pogrebnyak 12/6/2009 #4

首选String.equalsthis==object

我想在与interned strings一起使用时增加我的2美分。==

做的第一件事是.请参阅 OpenJDK 上的源代码String.equalsthis==object

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    …

因此,尽管有一些微不足道的性能提升(您不是在调用方法),但从维护者的角度来看,使用是一场噩梦,因为一些被隔离的字符串有成为非被隔离的趋势。==

因此,我建议不要依赖特殊情况来表示内部字符串,而是始终按照 Gosling 的意图使用。==equals

编辑:实习变成非实习:

V1.0
public class MyClass
{
  private String reference_val;

  ...

  private boolean hasReferenceVal ( final String[] strings )
  {
    for ( String s : strings )
    {
      if ( s == reference_val )
      {
        return true;
      }
    }

    return false;
  }

  private void makeCall ( )
  {
     final String[] interned_strings =  { ... init with interned values ... };

     if ( hasReference( interned_strings ) )
     {
        ...
     }
  }
}

在 2.0 版本中,维护者决定公开,但没有详细说明它需要一组内部字符串。hasReferenceVal

V2.0
public class MyClass
{
  private String reference_val;

  ...

  public boolean hasReferenceVal ( final String[] strings )
  {
    for ( String s : strings )
    {
      if ( s == reference_val )
      {
        return true;
      }
    }

    return false;
  }

  private void makeCall ( )
  {
     final String[] interned_strings =  { ... init with interned values ... };

     if ( hasReference( interned_strings ) )
     {
        ...
     }
  }
}

现在你有一个可能很难找到的错误,因为在大多数情况下,数组包含文本值,有时使用非文本字符串。如果被使用,那么仍然会继续工作。同样,性能提升微乎其微,但维护成本很高。equals==hasReferenceVal

评论

0赞 Carl Smotricz 12/6/2009
“一些被拘禁的琴弦有变成非拘禁的倾向。” 哇,那将是......奇怪。你能引用一个参考资料吗?
2赞 Carl Smotricz 12/6/2009
好吧,我以为你指的是 Strings 实际上由于 JVM 中的魔法而从实习生池中徘徊到堆上。你说的是 == 使某些类程序员更容易出错。
0赞 9/16/2012
“因此,我建议不要依赖 == 的特殊情况来表示被隔离的字符串,而是始终按照 Gosling 的意图使用等号。”你有高斯林的直接引用或评论吗?如果是这样的话,他为什么要费心在语言中加入 intern() 和 == 的使用?
2赞 tgkprog 5/1/2013
intern 不适合直接比较 (==),即使如果两个字符串都处于 inter 状态,它也能正常工作。降低使用的总内存是很好的:当同一字符串在 1 个以上的地方使用时。
2赞 inter18099 8/22/2013 #5

Interned Strings 避免重复的字符串。实习节省了 RAM,但代价是需要更多的 CPU 时间来检测和替换重复的字符串。每个 String 只有一个被保留的副本,无论有多少引用指向它。由于 String 是不可变的,因此如果两个不同的方法偶然使用相同的 String,它们可以共享同一 String 的副本。将重复的字符串转换为共享字符串的过程称为实习。String.intern() 为您提供规范主字符串的地址。您可以将 interned Strings 与 simple ==(用于比较指针)进行比较,而不是 equals(逐个比较 String 的字符)。由于 String 是不可变的,因此 intern 进程可以自由地进一步节省空间,例如,当 “pot” 作为其他文本(如“hippopotamus”)的子字符串存在时,不为它创建单独的 String 文本。

查看更多 http://mindprod.com/jgloss/interned.html

3赞 bbcbcgb 9/13/2013 #6

http://en.wikipedia.org/wiki/String_interning

字符串插入是一种仅存储每个不同字符串值的一个副本的方法,该副本必须是不可变的。插入字符串使某些字符串处理任务更具时间或空间效率,但代价是在创建或插入字符串时需要更多时间。非重复值存储在字符串实习生池中。

2赞 anish 11/12/2013 #7
String s1 = "Anish";
        String s2 = "Anish";

        String s3 = new String("Anish");

        /*
         * When the intern method is invoked, if the pool already contains a
         * string equal to this String object as determined by the
         * method, then the string from the pool is
         * returned. Otherwise, this String object is added to the
         * pool and a reference to this String object is returned.
         */
        String s4 = new String("Anish").intern();
        if (s1 == s2) {
            System.out.println("s1 and s2 are same");
        }

        if (s1 == s3) {
            System.out.println("s1 and s3 are same");
        }

        if (s1 == s4) {
            System.out.println("s1 and s4 are same");
        }

输出

s1 and s2 are same
s1 and s4 are same
4赞 thinkinjava 9/1/2014 #8

您应该确定两个时间段,即编译时间和运行时。例如:

//example 1 
"test" == "test" // --> true 
"test" == "te" + "st" // --> true

//example 2 
"test" == "!test".substring(1) // --> false
"test" == "!test".substring(1).intern() // --> true

一方面,在示例 1 中,我们发现结果都是返回 true,因为在编译时,JVM 会把 “test” 放到文字字符串池中,如果 JVM 发现 “test” 存在,那么就会使用存在的字符串,在示例 1 中,“test”字符串都指向同一个内存地址,因此,示例 1 将返回 true。 另一方面,在示例 2 中,substring() 的方法在运行时执行, 在 “test” == “!test”.substring(1) 的情况下,矿池将创建两个字符串对象,“test” 和 “!test”,所以它们是不同的引用对象,所以这种情况会返回 false,在 “test” == “!test”.substring(1).intern() 的情况下,intern() 的方法会把 “”!test“.substring(1)” 放到文字字符串池中,所以在这种情况下,它们是相同的引用对象,所以会返回 true。

1赞 user2485429 4/1/2015 #9

string intern() 方法用于在字符串常量池中创建堆字符串对象的精确副本。字符串常量池中的字符串对象会自动被隔离,但堆中的字符串对象不会被隔离。创建实习生的主要用途是节省内存空间并更快地比较字符串对象。

来源:什么是java中的字符串实习生?

1赞 WendellWu 4/20/2015 #10

正如您所说,该字符串方法将首先从 String 池中找到,如果找到,它将返回指向该对象的对象,或者将新的 String 添加到池中。intern()

    String s1 = "Hello";
    String s2 = "Hello";
    String s3 = "Hello".intern();
    String s4 = new String("Hello");

    System.out.println(s1 == s2);//true
    System.out.println(s1 == s3);//true
    System.out.println(s1 == s4.intern());//true

和是指向字符串池“Hello”的两个对象,使用 将找到 和 。因此返回 true,以及 .s1s2"Hello".intern()s1s2"s1 == s3"s3.intern()

评论

0赞 Alexander 4/20/2015
这并没有真正提供太多新信息。已经有一个例外的答案。
2赞 raja emani 7/12/2016 #11
String p1 = "example";
String p2 = "example";
String p3 = "example".intern();
String p4 = p2.intern();
String p5 = new String(p3);
String p6 = new String("example");
String p7 = p6.intern();

if (p1 == p2)
    System.out.println("p1 and p2 are the same");
if (p1 == p3)
    System.out.println("p1 and p3 are the same");
if (p1 == p4)
    System.out.println("p1 and p4 are the same");
if (p1 == p5)
    System.out.println("p1 and p5 are the same");
if (p1 == p6)
    System.out.println("p1 and p6 are the same");
if (p1 == p6.intern())
    System.out.println("p1 and p6 are the same when intern is used");
if (p1 == p7)
    System.out.println("p1 and p7 are the same");

当两个字符串独立创建时,允许您比较它们,并且它还可以帮助您在字符串池中创建引用(如果引用以前不存在)。intern()

当您使用 时,java 会创建一个字符串的新实例,但是当您使用 时,java 会检查代码中是否存在单词“hi”的实例,如果存在,它只会返回引用。String s = new String(hi)String s = "hi"

由于比较字符串是基于引用的,因此有助于创建引用并允许您比较字符串的内容。intern()

在代码中使用时,它会清除引用同一对象的字符串使用的空间,并仅返回内存中已存在的相同对象的引用。intern()

但是在 p5 的情况下,当您使用:

String p5 = new String(p3);

仅复制 p3 的内容,并新建 p5。所以它没有被拘禁

因此,输出将是:

p1 and p2 are the same
p1 and p3 are the same
p1 and p4 are the same
p1 and p6 are the same when intern is used
p1 and p7 are the same
17赞 KGhatak 4/1/2017 #12

学习 Java String Intern - 一劳永逸

Java 中的字符串在设计上是不可变的对象。因此,默认情况下,即使具有相同值的两个字符串对象也将是不同的对象。但是,如果我们希望节省内存,我们可以通过称为字符串 intern 的概念来指示使用相同的内存。

以下规则将帮助您清晰地理解这个概念:

  1. String 类维护一个 intern-pool,该池最初是空的。此池必须保证包含仅具有唯一值的字符串对象。
  2. 所有具有相同值的字符串文本都必须被视为相同的内存位置对象,因为它们没有区别的概念。因此,所有具有相同值的此类文本都将在 intern-pool 中形成一个条目,并将引用相同的内存位置。
  3. 两个或多个文本的串联也是一个文本。(因此,规则 #2 将适用于他们)
  4. 作为对象创建的每个字符串(即通过除文字之外的任何其他方法)将具有不同的内存位置,并且不会在 intern-pool 中生成任何条目
  5. 文字与非文字的连接将形成非文字。因此,生成的对象将具有新的内存位置,并且不会在 intern-pool 中生成条目。
  6. 在字符串对象上调用 intern 方法,要么创建一个进入 intern-pool 的新对象,要么从池中返回具有相同值的现有对象。对不在 intern-pool 中的任何对象的调用不会将对象移动到池中。而是创建另一个进入池的对象。

例:

String s1=new String ("abc");
String s2=new String ("abc");
If (s1==s2)  //would return false  by rule #4
If ("abc" == "a"+"bc" )  //would return true by rules #2 and #3
If ("abc" == s1 )  //would return false  by rules #1,2 and #4
If ("abc" == s1.intern() )  //would return true  by rules #1,2,4 and #6
If ( s1 == s2.intern() )      //wound return false by rules #1,4, and #6

注意:这里不讨论字符串实习生的激励案例。但是,节省内存肯定是主要目标之一。

评论

2赞 kaay 10/16/2019
谢谢你的#3,我不知道:)
1赞 Anurag_BEHS 2/18/2018 #13
    public static void main(String[] args) {
    // TODO Auto-generated method stub
    String s1 = "test";
    String s2 = new String("test");
    System.out.println(s1==s2);              //false
    System.out.println(s1==s2.intern());    //true --> because this time compiler is checking from string constant pool.
}
1赞 Kms 2/2/2020 #14

通过使用堆对象引用,如果我们想获得相应的字符串常量池对象引用,那么我们应该使用 intern()

String s1 = new String("Rakesh");
String s2 = s1.intern();
String s3 = "Rakesh";

System.out.println(s1 == s2); // false
System.out.println(s2 == s3); // true

图片视图 enter image description here

步骤1:带有数据“Rakesh”的对象在堆和字符串常量池中创建。此外,s1 始终指向堆对象。

步骤2:通过使用堆对象引用 s1,我们尝试使用 intern() 获取相应的字符串常量池对象引用 s2

步骤3:有意在字符串常量池中使用数据“Rakesh”创建一个对象,由名称 s3 引用

作为“==”运算符,用于参考比较。

s1==s2 为 false

s2==s3 为真

希望这有帮助!!