什么时候应该在String文字上使用String的内部方法


187

根据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方法呢?换句话说,什么时候应该使用这种方法?


14
您链接的Javadoc还指出“所有文字字符串和字符串值常量表达式均已插入”。
乔恩,


1
不是确切的重复
。.– Bozho

1
@乔恩:是的。那么,为什么我们要intern使用公共方法呢?我们不应该将其intern作为私有方法,这样没人可以访问它。还是这种方法有什么目的?
Rakesh Juyal,

2
@RakeshJuyal:intern方法是在可以是字符串文字或变量的字符串类型上定义的。如果方法是私有的,您将如何实习一个变量?
bobbyalex

Answers:


230

Java自动实习字符串文字。这意味着在许多情况下,==运算符似乎适用于Strings,其处理方式与ints或其他原始值相同。

由于Interning对于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变量之外,都是使用new运算符显式创建的值,并且intern未在其结果上使用method的值,这是一个不可变的实例,正在返回JVM的字符串常量池

有关更多信息,请参考JavaTechniques“字符串相等和内联”


我假设Java为优化目的而自动插入String文字。它只能安全​​地做到这一点,因为字符串是不可变的,对吗?
styfle 2012年

Java的新手(我来自C#.NET世界),有时我会在Java遗留项目“ .intern()中看到”,因此,如果我正确理解它,那么对于空字符串也是“废话”。
hfrmobile 2013年

4
@Miguel很好的解释,我的问题是您的示例中如何在此处创建对象。这是我的假设: String s1 = "Rakesh"; 第一个OB1 String s4 = new String("Rakesh");第二个OB2因此(s2,s3,s5)的其余部分引用在“字符串池”中创建的同一对象(OB1),所以我可以说,如果If中.intern()可用相同的字符串,则该方法用于防止创建新对象string pool我的假设是错误的,所以给我指示。
HybrisHelp

1
该JavaTechniques链接被打破
SJuan76


20

在最近的一个项目中,使用从数据库中读取的数据(因此不是String常量/文字)来建立一些巨大的数据结构,但是要进行大量的重复。这是一个银行业务应用程序,到处都出现了像一家中等规模(可能是100或200)公司的名称。数据结构已经很大,如果所有这些公司名称都是唯一的对象,它们将溢出内存。相反,所有数据结构都引用相同的100或200个String对象,从而节省了大量空间。

内联字符串的另一个小优点是,==如果可以保证所有涉及的字符串都可以被内联,则可以(成功!)比较字符串。除了更精简的语法外,这还提高了性能。但是,正如其他人指出的那样,这样做有很大的风险引入编程错误,因此仅应将其作为绝望的最后手段。

不利之处在于,对String进行实习比将其简单地扔到堆上要花费更多的时间,并且取决于Java实现,实习String的空间可能会受到限制。当您处理已知数量合理且重复很多的字符串时,最好这样做。


@ 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即使您不对字符串常量使用intern方法,它也会被自动插入。
Rakesh Juyal,2009年

2
@Rakesh:在任何给定的类中通常没有那么多的String常量,因此使用常量不存在时空问题。
大卫·罗德里格斯(DavidRodríguez)-dribeas,2009年

是的,Rakesh的评论不适用,因为内部字符串仅(显式)使用以某种方式“生成”的字符串完成,无论是通过内部操纵还是通过数据库检索等。对于常量,我们别无选择。
卡尔·斯莫特里奇

2
+1。我认为这是一个有意义的实习范例。==虽然我不同意字符串。
Alexander Pogrebnyak,2009年

1
从Java 7开始,“字符串池”已实现到堆空间中,因此它具有存储实习生,垃圾回收的所有优点,并且它的大小不受限制,可以增加到堆大小。(您永远不需要那么多字符串的内存)
Anil Uttani

15

我想将我的2美分加在使用==实习弦上。

首先要做的String.equalsthis==object

因此,尽管性能提升有些微(您未调用方法),但是从维护者的角度来看,使用==是一场噩梦,因为某些被锁存的字符串倾向于不被锁存。

因此,我建议不要依赖==intern字符串的特殊情况,而应始终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使用代替,==那么hasReferenceVal仍将继续工作。再次,性能增益微不足道,但是维护成本很高。


“一些被拘禁的弦乐有变得不可被拘禁的趋势。” 哇,那太奇怪了。你能引用一下吗?
卡尔·斯莫特里兹

2
好的,我想您是在指的是由于JVM中的魔力而使字符串实际上从内部缓冲池徘徊并进入堆。您在说的是==使某些类型的程序员错误更有可能发生。
卡尔·斯莫特里奇

“因此,我建议不要将==的特殊情况用于内联字符串,而应始终按照Gosling的意图使用equals。” 您对此有直接的引用或戈斯林的评论吗?如果是这样的话,他为什么还要在语言中加上intern()和使用==呢?

1
实习生不利于直接比较(==),即使在两个字符串都已插入的情况下也可以使用。最好减少使用的总内存:在1个以上的位置使用同一字符串时。
tgkprog

12

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


4
所以,问题是我们什么时候应该使用intern
拉克什Juyal

指向stackoverflow.com/questions/1833581/when-to-use-intern,以及其他一些问题,其中一些是从昨天开始的。
09年

让我知道我对这句话的理解String literals and constants are interned by default是否正确。 new String("foo")->在这里,在字符串池中创建了一个字符串文字“ foo”,在堆中创建了一个,因此总共创建了2个对象。
dkb,

8

学习Java String Intern-一劳永逸

从设计上讲,java中的字符串是不可变的对象。因此,两个具有相同值的字符串对象默认情况下将是不同的对象。但是,如果希望节省内存,则可以通过称为字符串实习生的概念指示使用相同的内存。

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

  1. 字符串类维护一个最初为空的内部池。该池必须保证只包含唯一值的字符串对象。
  2. 具有相同值的所有字符串文字必须被视为相同的内存位置对象,因为否则它们没有区别的概念。因此,所有这些具有相同值的文字将在内部存储池中创建一个条目,并将引用相同的内存位置。
  3. 两个或多个文字的串联也是文字。(因此规则2将适用于他们)
  4. 创建为对象的每个字符串(即通过文字方法以外的任何其他方法)将具有不同的内存位置,并且不会在内部池中进行任何输入
  5. 文字与非文字的串联将构成非文字。因此,结果对象将具有新的存储位置,并且不会在内部池中进行输入。
  6. 在字符串对象上调用intern方法,要么创建一个进入内部池的新对象,要么从池中返回具有相同值的现有对象。不在内部缓冲池中的任何对象上的调用都不会将该对象移到池中。而是创建另一个进入池的对象。

例:

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

注意:这里不讨论字符串实习生的动机情况。但是,节省内存绝对是主要目标之一。


感谢您的#3,我不知道:)
kaay

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”,因此它们是不同的引用对象,因此在“ test” ==“!test” .substring(1).intern(),intern( )会将““!test” .substring(1)“放入文字字符串池中,


3

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

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


2

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

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


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

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

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.
}

1

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

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


1

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

    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

s1s2是指向字符串池“你好”,并使用两个物体"Hello".intern()会发现,s1s2。因此,"s1 == s3"返回true以及s3.intern()


这实际上并没有提供太多新信息。已经有一个例外的答案。
亚历山大

0

通过使用堆对象引用,如果我们想获取相应的字符串常量池对象引用,则应该使用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,我们尝试使用intern()获得相应的字符串常量池对象引用s2。

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

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

获得虚假的S1 S2 ==

为s2 == s3 变真实

希望对您有所帮助!!

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.