原文:使用「」還是建構子來建立Java字串?作者:X Wang 在 Java 中,可以使用兩種方法建立字串:
String x = "abc";
String y = new String("abc");
使用雙引號和使用建構函數有什麼不同?
1. 雙引號與雙引號 建構函數
這個問題可以透過兩個簡單的例子來回答。範例1:String a = "abcd";
String b = "abcd";
System.out.println(a == b); // True
System.out.println(a.equals(b)); // True
a==b
true 是因為它們a
都b
引用同一個物件 - 在方法區域中聲明為文字(下面的字串文字)的字串(我們建議讀者參考我們資源上的原始程式碼:了解 Java 的 Top 8 圖,圖 8)。當相同的字串文字被創建多次時,記憶體中僅儲存該字串的副本,即它的一個實例(在我們的例子中為「abcd」)。這稱為“字串實習”。所有在編譯時處理的字串常數都會自動駐留在 Java 中。範例2:
String c = new String("abcd");
String d = new String("abcd");
System.out.println(c == d); // False
System.out.println(c.equals(d)); // True
c==d
false,因為c
它們d
引用記憶體中(堆上)的兩個不同物件。不同的物件總是有不同的引用。此圖說明了上述兩種情況:
2. 程式執行期間插入字串
作者感謝 LukasEder(下面的評論是他的): 字串駐留也可能在程式執行期間發生,即使使用建構函式建立兩個字串:String c = new String("abcd").intern();
String d = new String("abcd").intern();
System.out.println(c == d); // Now true
System.out.println(c.equals(d)); // True
3.什麼時候使用雙引號,什麼時候使用建構函數
由於文字“abcd”始終是 String 類型,因此使用建構函數將建立額外的不必要的物件。因此,如果您只需要建立一個字串,則應使用雙引號。如果您確實需要在堆上建立一個新對象,則應該使用建構函式。此處顯示了用例(原始) 。(我在下面提供了翻譯後的文本。但我仍然強烈建議您熟悉此連結中評論者的程式碼。)JDK 6 和 JDK 7 中的 substring() 方法
JDK 6 和 JDK 7 中的 substring() 方法By X Wang JDK 6 和 JDK 7 中的方法substring(int beginIndex, int endIndex)
則有所不同。了解這些差異可以幫助您更好地使用此方法。為了方便閱讀,以下substring()
我們將指完整的文法,即 substring(int beginIndex, int endIndex)
。
1. substring() 的作用是什麼?
此方法substring(int beginIndex, int endIndex)
傳回以字元 number 開頭beginIndex
並以字元 number 結尾的字串endIndex-1
。
String x = "abcdef";
x = x.substring(1,3);
System.out.println(x);
輸出:
bc
2. 呼叫 substring() 時會發生什麼事?
您可能知道,由於不變性x
,當 的結果分配給 x 時x.substring(1,3)
,x
它指向一個全新的行(見圖): 但是,該圖並不完全正確;它沒有展示堆中實際發生的情況。substring()
在 JDK 6 和 JDK 7 中, 呼叫時實際發生的情況有所不同。
3. JDK 6中的substring()
數組類型支援字串類型char
。在 JDK 6 中,此類別String
包含 3 個欄位:char value[]
、int offset
、int count
。它們用於儲存實際的字元數組、數組中第一個字元的索引、行中的字元數。當呼叫該方法時substring()
,它會建立一個新行,但變數的值仍然指向堆上的相同數組。兩個字串之間的差異在於它們的字元數和陣列中起始字元的索引值。 下面的程式碼經過簡化,僅包含演示問題的基礎知識。
//JDK 6
String(int offset, int count, char value[]) {
this.value = value;
this.offset = offset;
this.count = count;
}
public String substring(int beginIndex, int endIndex) {
//check boundary
return new String(offset + beginIndex, endIndex - beginIndex, value);
}
4. JDK 6中substring()所引起的問題
如果你有一個非常長的字串,但你只需要它的一小部分,每次使用substring()
. 這將導致執行問題,因為您只需要一小部分,但仍然必須儲存整個字串。對於 JDK 6,解決方案是下面的程式碼,它將字串轉換為真正的子字串:
x = x.substring(x, y) + ""
用戶STepeR提出了一個問題(請參閱他的評論),似乎有必要添加第4點。「substring()
JDK 6 中引起的問題」是一個更廣泛的範例。我希望這將是答案,並將幫助其他人快速找出問題所在。這是代碼:
String a = "aLongLongString";
String b = a.substring(1, 2);
String c = a.substring(2, 6);
因此,在 JDK 7 中b
,透過呼叫 aс
類型物件的方法所建立的a 類型物件將引用堆中兩個新建立的陣列 - for 、for 。這兩個新數組將與a 引用的原始數組一起儲存在堆上。那些。原始數組不會在任何地方消失。現在讓我們回到 JDK 6。在 JDK 6 中,堆包含單一陣列。執行程式碼行後 String
substring()
String
L
b
ongL
c
aLongLongString
aLongLongString
String b = a.substring(1, 2);
String c = a.substring(2, 6);
物件b
引用c
堆中的同一個數組,對應於物件a
:b
- 從第一個索引到第二個索引的元素,c
- 從第二個索引到第六個索引的元素(編號從0開始,提醒)。那些。顯然,b
在 JDK 6 中對變數或 c 的任何進一步存取實際上都會導致原始數組的所需元素被「計數」到堆中。在 JDK 7 中,對變數b
或 c 的任何進一步存取都會導致對已在堆中建立並「存活」的必要的較小數組的存取。那些。顯然,在這種情況下,JDK 7 在物理上使用了更多記憶體。但讓我們想像一個可能的選擇:變數的某些子字串被分配給變量b
,並且將來每個人都只使用它們 - 物件和。沒有人再簡單地存取變數 a;沒有對它的引用(這就是本文作者的意思)。結果,在某個時間點垃圾收集器被觸發,並且(當然,以最一般的形式)我們得到兩種不同的情況: JDK 6:對像被銷毀(垃圾收集),但是 - 原始的巨大數組堆裡是活的;它被不斷地使用並且。 JDK 7:物件 a 與堆疊中的原始陣列一起被銷毀。這就是 JDK 6 中可能導致記憶體洩漏的地方。 c
a
b
c
a
b
c
5. JDK 7 中的 substring()
該方法在JDK 7中得到了改進。在 JDK 7 中,substring()
它實際上在堆上建立了一個新陣列。
//JDK 7
public String(char value[], int offset, int count) {
//check boundary
this.value = Arrays.copyOfRange(value, offset, offset + count);
}
public String substring(int beginIndex, int endIndex) {
//check boundary
int subLen = endIndex - beginIndex;
return new String(value, beginIndex, subLen);
}
GO TO FULL VERSION