2010-09-27 94 views

回答

114

这将打印true(尽管我们不使用equals方法:正确的方式来比较字符串)

String s = "a" + "bc"; 
    String t = "ab" + "c"; 
    System.out.println(s == t); 

当编译器优化您的字符串常量,它认为这两个st具有相同的价值,从而你只需要一个字符串对象。这是安全的,因为String在Java中是不可变的。
因此,st指向相同的对象并保存一些小内存。

名称的'字符串池'来自这样的想法,即所有已定义的字符串都存储在某个“池”中,并且在创建新对象之前,对象编译器检查是否已经定义了这样的字符串。

+2

的Java有原始类型的包装类型,这些类也是不可变的。像Integer,Charecter和Double ....等。他们是否也有一个游泳池来节省内存?如果没有,String有什么特别之处? – 2015-02-02 09:21:05

+2

@PunithRaj我并不确定!然而,我怀疑它。例如,int只有4个字节,所以最终不会因为两个Integer指向内存中的相同位置而节省太多。相反,不得不维护一个“整数池”来发现重复值,可能会浪费更多的内存,而不是通过避免重复值来节省。 – 2015-02-03 10:49:45

+1

@PunithRaj字符串不是原始数据类型(技术上/执行方面明智)和字符串没有像char/int如何做的包装类。 – user3240361 2015-02-17 10:49:38

37

我不认为它确实有很大的作用,它看起来就像是字符串文字的缓存。如果您有多个字符串的值相同,则它们都将指向字符串池中的相同字符串字面值。

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. 

http://p2p.wrox.com/java-espanol/29312-string-pooling.html

14

当JVM装载类,或以其他方式看到文字字符串,或一些代码intern SA字符串,它添加字符串到具有每个这样的一个拷贝大多隐藏查找表串。如果添加了另一个副本,则运行时会对其进行排列,以便所有文字都引用相同的字符串对象。这被称为“实习”。如果你这样说

String s = "test"; 
return (s == "test"); 

它会返回true,因为第一和第二个“测试”实际上是同一个对象。这样比较interned字符串可能比String.equals更快,因为只有一个参考比较,而不是一堆char比较,所以多得多String.equals更快。

您可以通过调用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" 
+0

我其实并不认为这是在运行时完成的。即使使用方法构造的最简单的字符串也不会被合并。例如。,如果我使用_concat_而不是_con __ _ – 2010-09-27 06:41:07

+1

@Nikita:那么我的答案中的示例将不起作用,这是因为'concat'不能被轻松优化。用'+'拼凑在一起的字符串可能会被任何自尊的编译器预先收集,因为值永远不会改变。但是编译器无法真正猜测一个函数是否会一直返回相同的值(有些不会),所以它不会尝试。如果在你的例子中使用'concat'而不是“ab”,“c”,“a”和“bc”,但“abc”不会(因为它不是一个文字, t' intern' it)。然而,用'+'编译器会看到这两个字符串都是“abc”并编译它。 – cHao 2010-09-27 08:25:42

+1

实习将*在*运行时完成,因为(1)池总是空出来的,(2)两个不同的类每个都可以有“abc”。如果interning是编译时的事情,并且两个类最终都被加载,那么最终会在字符串池中出现两个“abc”,这会破坏字符串池的整个目的。 – cHao 2010-09-27 08:45:28

16

让我们开始从虚拟机规格报价:

加载包含文字可能会创建一个新的String对象的String类或接口(§2.4.8)来表示该文字。如果已经创建了一个String对象来表示该文本的先前出现,或者如果String.intern方法已经在表示与文字相同的字符串的String对象上被调用,则可能不会发生这种情况。

这可能不会发生 - 这是一个暗示,那有什么特殊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))可能truefalse总是使用equals比较字符串)
  • 不要使用反射来改变字符串的支持char[](因为你不知道是谁在使用该字符串actualling)
+0

如果您关心字符串池,那么在广泛使用一小组字符的应用程序中,可能会大幅提升性能,通常用作令牌或关键字。一旦字符串被执行,比较变成一个'==',而不是函数调用,两个length()调用,以及'equals'可能发生的一堆字符比较。 – cHao 2010-09-27 09:09:51

+0

@cHao为了安全性和一致性,您仍然可以对字符串使用'String.equals()',因为'String.equals()'首先执行'=='比较 – bcoughlan 2014-10-01 11:00:14

+1

@bcoughlan:'=='是安全的,一致为“平等” - 这只是误解。与物体一起使用的人分为两类。有些人不明白价值vs身份语义(并且==与引用类型比较身份) - 那些人*应该*总是使用'String.equals'。然后有些人明白,但有意识地选择身份。只要你知道你的物体来自哪里,那就可以可靠地工作。有一个原因,“==”与对象一起工作 - 特别是为什么它不只是调用equals。 – cHao 2014-10-01 14:02:00