2011-09-10 52 views
6

我有以下的Java代碼:多少String對象將被創建

public String makinStrings() { 
    String s = "Fred"; 
    s = s + "47"; 
    s = s.substring(2, 5); 
    s = s.toUpperCase(); 
    return s.toString(); 
} 

的問題是有點簡單:有多少String對象調用此方法時會產生的呢?

一開始,我回答說,5個String對象被創建,但我書中的回答說,僅創建3個對象,並沒有給出解釋(這是一個SCJP問題)。

從我的角度來看,有5個對象: 「Fred」,「47」,「Fred47」,「ed4」,「ED4」。

我還發現一個SCJP考試模擬這個問題,用同樣的答案3.

+3

我想象中的編譯器將內聯前兩個語句和刪除最後一個(冗餘)方法調用。這給你留下了「Fred47」,「ed4」,「ED4」。 –

+1

@Jared's'不是*編譯時常量表達式*,因此不會發生。 (編譯代碼並使用'javap -c',甚至'strings'。) –

+0

可能重複的[Java - 多少個字符串對象?](http://stackoverflow.com/questions/17898236/java-how-many -string-objects) –

回答

15

「弗雷德」和「47」會從字符串文字池。因此,他們不會當方法被調用時被創建。相反,當類加載時(或者更早,如果其他類使用具有相同值的常量),它們將放在那裏。

「Fred47」,「ED4」和「ED4」是將在每個方法調用創建的3個String對象。

+0

在第一次方法調用之後,「Fred47」,「ed4」和「ED4」不會在字符串文字池中出現嗎?我相信Jared的回答/評論是正確的(+1),因爲我所知道的所有編譯器都做了這些簡單的內聯。這就是爲什麼連接字符串不再非常昂貴(儘管仍然在某種程度上)。 – DaveFar

+1

它是[顯然是合法的](http://java.sun.com/docs/books/vmspec/2nd-edition/html/Concepts.doc.html#19124)讓JVM在第一次加載時實例化字符串文字來自常量池,而不是在上課時輕鬆加載它們。我不認爲這個實現細節與原始問題真正相關,但這是一個有趣的小問題。 –

+0

注意,如果前兩行寫成單個語句,那麼將直接使用「Fred47」,因爲「Fred」+「47」是*編譯時常量表達式*。此外,可能會對庫進行不同的實現,並生成不同數量的字符串對象。 –

2

方案往往含有大量的字符串文字在他們的代碼。在Java中,爲了提高效率,這些常量被收集在一個稱爲字符串表的東西中。例如,如果在十個不同的地方使用字符串"Name: ",則JVM(通常)只有該String的一個實例,並且在使用該字符串的所有十個位置中,引用都指向該實例。這節省了內存。

這種優化是可能的,因爲String不可變的。如果可以更改字符串,將其更改爲一個地方意味着它會改變其他九個字符串。這就是爲什麼任何改變String的操作都會返回一個新的實例。這就是爲什麼如果你這樣做:

String s = "boink"; 
s.toUpperCase(); 
System.out.println(s); 

它打印boink,不BOINK

現在有一個更靠譜一點:在相同的基礎char[]他們的字符數據java.lang.String可能點的多個實例,換句話說,也可以是不同的同char[]意見,只用一個片的陣列。再次,對效率進行優化。 substring()方法是發生這種情況的一種情況。

s1 = "Fred47"; 

//String s1: data=[ 'F', 'r', 'e', 'd', '4', '7'], offset=0, length=6 
//     ^........................^ 

s2 = s1.substring(2, 5); 

//String s2: data=[ 'F', 'r', 'e', 'd', '4', '7'], offset=2, length=3 
//        ^.........^ 
// the two strings are sharing the same char[]! 

在你的SCJP的問題,這一切都歸結爲:

  • 字符串"Fred"從字符串表中獲得。
  • 字符串"47"從字符串表中獲得。
  • 字符串"Fred47"在方法調用期間創建的。// 1
  • 字符串"ed4"在方法調用期間創建,共享相同的背襯陣列"Fred47" // 2
  • 方法調用過程中創建"ED4"的字符串。 // 3
  • s.toString()不會創建一個新的,它只會返回this

之一的這一切有趣的邊緣情況:考慮,如果你有一個很長的字符串會發生什麼,例如,從網上取一個網頁,讓我們假設char[]的長度爲2兆字節。如果你拿這substring(0, 4),你會得到一個新的字符串,看起來像它只有四個字符長,但它仍然共享這兩兆字節的支持數據。這在現實世界中並不常見,但它可能會造成巨大的內存浪費!在(罕見)情況下,如果遇到此問題,可以使用new String(hugeString.substring(0, 4))創建一個帶有新的小型後備陣列的字符串。

最後,可以在運行時通過調用intern()來強制字符串進入字符串表。這種情況下的基本規則:不要這樣做。擴展規則:除非您使用內存分析器來確定它是一種有用的優化,否則不要這樣做。

+0

這是不正確的。沒有一個字符串常量是從「字符串池」來的,它們都來自類常量池。如果在幾個類中使用像「Fred47」這樣的相同字面常量,則它們中的每一個在其常量池中都具有相同的常量。查看Chip McCormick答案的字節碼。 –

2

基於javap輸出,它看起來像在concocuation期間創建了一個StringBuilder,而不是一個String。然後有三個字符串調用substring(),toUpperCase()和toString()。

最後一次調用不是多餘的,因爲它將StringBuilder轉換爲字符串。

>javap -c Test 
Compiled from "Test.java" 

public java.lang.String makinStrings(); 
Code: 
0: ldc  #5; //String Fred 
2: astore_1 
3: new  #6; //class java/lang/StringBuilder 
6: dup 
7: invokespecial #7; //Method java/lang/StringBuilder."<init>":()V 
10: aload_1 
11: invokevirtual #8; //Method java/lang/StringBuilder.append: (Ljava/lang/String;)Ljava/lang/StringBuilder; 
14: ldc  #9; //String 47 
16: invokevirtual #8; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 
19: invokevirtual #10; //Method java/lang/StringBuilder.toString:()Ljava/lang/String; 
22: astore_1 
23: aload_1 
24: iconst_2 
25: iconst_5 
26: invokevirtual #11; //Method java/lang/String.substring:(II)Ljava/lang/String; 
29: astore_1 
30: aload_1 
31: invokevirtual #12; //Method java/lang/String.toUpperCase:()Ljava/lang/String; 
34: astore_1 
35: aload_1 
36: invokevirtual #13; //Method java/lang/String.toString:()Ljava/lang/String; 
39: areturn 

}

+0

所以正確的答案是:3.還是我錯了? –