193

根据String#intern()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.
}

我期待s1 and s3 are same将在 s3 被实习时打印,并且s1 and s2 are same不会打印。但结果是:两行都被打印了。这意味着,默认情况下 String 常量是实习的。但如果是这样,那我们为什么需要这个intern方法呢?换句话说,我们什么时候应该使用这种方法?

4

14 回答 14

240

Java 自动实习生字符串文字。这意味着在许多情况下,== 运算符对字符串的工作方式与对整数或其他原始值的工作方式相同。

由于 String 文字的实习是自动的,因此该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

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

有关详细信息,请参阅JavaTechniques“字符串相等和实习”

于 2009-12-06T11:54:46.710 回答
21

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

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

不利的一面是,与简单地把它扔到堆上相比,实习 String 需要更多的时间,而且实习 String 的空间可能会受到限制,具体取决于 Java 实现。当您处理已知合理数量的具有许多重复的字符串时,最好这样做。

于 2009-12-06T11:59:40.003 回答
16

我想在使用==实习字符串时增加 2 美分。

首先要做的String.equalsthis==object

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

所以我建议不要依赖特殊情况的==实习字符串,而总是equals按照 Gosling 的意图使用。

编辑:实习变成非实习:

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使用而不是==thenhasReferenceVal将仍然继续工作。再一次,性能提升微乎其微,但维护成本很高。

于 2009-12-06T12:13:28.507 回答
12

默认情况下,字符串文字和常量是实习的。也就是说,"foo" == "foo"(由字符串文字声明),但是new String("foo") != new String("foo").

于 2009-12-06T11:53:15.467 回答
12

学习 Java String Intern - 一劳永逸

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

以下规则将帮助您清楚地理解该概念:

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

例子:

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

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

于 2017-03-31T22:24:30.840 回答
4

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

//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)" 放入文字字符串池中,

于 2014-09-01T08:40:06.770 回答
3

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

字符串实习是一种仅存储每个不同字符串值的副本的方法,该副本必须是不可变的。驻留字符串使某些字符串处理任务更节省时间或空间,但代价是创建或驻留字符串时需要更多时间。不同的值存储在字符串实习池中。

于 2013-09-12T18:47:28.137 回答
2

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

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

于 2013-08-22T04:31:00.167 回答
2
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
于 2013-11-12T10:37:18.850 回答
2
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()允许您比较它们,如果之前不存在引用,它还可以帮助您在字符串池中创建引用。

当你使用时String s = new String(hi),java会创建一个新的字符串实例,但是当你使用时String s = "hi",java会检查代码中是否存在单词“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
于 2016-07-12T08:56:08.013 回答
2
    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.
}
于 2018-02-17T19:55:18.933 回答
1

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

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

于 2015-03-31T17:30:44.543 回答
1

如您所说,该字符串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

s1ands2是两个指向字符串池“Hello”的对象,使用会"Hello".intern()发现s1and s2。所以"s1 == s3"返回真,以及s3.intern().

于 2015-04-20T13:02:47.790 回答
1

通过使用堆对象引用,如果我们想得到对应的字符串常量池对象引用,那么我们应该去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

图片视图 在此处输入图像描述

第 1 步: 在堆和字符串常量池中创建具有数据“Rakesh”的对象。s1 也总是指向堆对象。

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

第 3 步: 故意在字符串常量池中创建一个包含数据“Rakesh”的对象,由名称 s3 引用

由于“==”运算符用于参考比较。

s1 ==s2 为

对 s2==s3来说是正确的

希望这有帮助!!

于 2020-02-01T18:37:29.783 回答