2010-09-27 100 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