簡體   English   中英

為什么在 Java 中使用 StringBuffer 而不是字符串連接運算符

[英]Why to use StringBuffer in Java instead of the string concatenation operator

有人告訴我,在 Java 中使用StringBuffer連接字符串比對String使用+運算符更有效。 當你這樣做時,引擎蓋下會發生什么? StringBuffer有什么不同?

現在,幾乎在所有情況下,最好使用 StringBuilder (它是一個不同步的版本;你什么時候並行構建字符串?),但會發生以下情況:

當您將 + 與兩個字符串一起使用時,它會編譯如下代碼:

String third = first + second;

對於這樣的事情:

StringBuilder builder = new StringBuilder( first );
builder.append( second );
third = builder.toString();

因此,對於一些小例子,它通常沒有什么區別。 但是,當您構建一個復雜的字符串時,您通常需要處理的事情遠不止這些; 例如,您可能正在使用許多不同的附加語句,或者像這樣的循環:

for( String str : strings ) {
  out += str;
}

在這種情況下,每次迭代都需要一個新的StringBuilder實例和一個新的Stringout - String的新值是不可變的)。 這是非常浪費的。 用單個StringBuilder替換它意味着您可以只生成一個String而不會用您不關心的String填充堆。

對於簡單的連接,例如:

String s = "a" + "b" + "c";

使用StringBuffer是毫無意義的——正如jodonnell指出的那樣,它將被巧妙地翻譯成:

String s = new StringBuffer().append("a").append("b").append("c").toString();

但是在循環中連接字符串是非常糟糕的,例如:

String s = "";
for (int i = 0; i < 10; i++) {
    s = s + Integer.toString(i);
}

在此循環中使用字符串會在 memory 中生成 10 個中間字符串對象:“0”、“01”、“012”等。 在使用StringBuffer編寫相同的內容時,您只需更新StringBuffer的一些內部緩沖區,並且您不需要創建那些不需要的中間字符串對象:

StringBuffer sb = new StringBuffer();
for (int i = 0; i < 10; i++) {
    sb.append(i);
}

實際上,對於上面的示例,您應該使用StringBuilder (在 Java 1.5 中引入)而不是StringBuffer - StringBuffer稍微重一些,因為它的所有方法都是同步的。

一個不應該比另一個快。 在 Java 1.4.2 之前,情況並非如此,因為當使用“+”運算符連接兩個以上的字符串時,會在構建最終字符串的過程中創建中間String對象。

然而,正如StringBuffer 的 JavaDoc所述,至少從 Java 1.4.2 開始,使用“+”運算符編譯為創建一個StringBuffer append()許多字符串添加到它。 所以顯然沒有區別。

但是,在循環中使用將字符串添加到另一個字符串時要小心:例如:

String myString = "";

for (String s : listOfStrings) {
  // Be careful! You're creating one intermediate String object
  // for every iteration on the list (this is costly!)
  myString += s;
}

但是請記住,通常用“+”連接幾個字符串比append()更干凈。

在后台,它實際上創建並附加到一個 StringBuffer,在結果上調用 toString()。 因此,實際上您不再使用哪個並不重要。

所以

String s = "a" + "b" + "c";

變成

String s = new StringBuffer().append("a").append("b").append("c").toString();

這對於單個語句中的一堆內聯追加是正確的。 如果您在多個語句的過程中構建字符串,那么您就是在浪費 memory,而 StringBuffer 或 StringBuilder 是您更好的選擇。

我認為給定 jdk1.5(或更高版本)並且您的連接是線程安全的,您應該使用 StringBuilder 而不是 StringBuffer http://java4ever.blogspot.com/2007/03/string-vs-stringbuffer-vs-stringbuilder.ZFC35FDC70D5FC69D2698Z83A822C7A53E至於速度上的提升: http://www.about280.com/stringtest.html

就我個人而言,我會編寫代碼以提高可讀性,因此除非您發現字符串連接會使您的代碼變得相當慢,否則請使用使您的代碼更具可讀性的任何方法。

在某些情況下,由於編譯器執行的優化,這已過時,但一般問題是代碼如下:

string myString="";
for(int i=0;i<x;i++)
{
    myString += "x";
}

將如下所示(每個步驟都是下一個循環迭代):

  1. 構造一個長度為 1 且值為“x”的字符串 object
  2. 創建一個大小為 2 的新字符串 object,將舊字符串“x”復制到其中,在 position 2 中添加“x”。
  3. 創建一個大小為 3 的新字符串 object,將舊字符串“xx”復制到其中,在 position 3 中添加“x”。
  4. ... 等等

如您所見,每次迭代都必須再復制一個字符,導致我們在每個循環中執行 1+2+3+4+5+...+N 次操作。 這是一個 O(n^2) 操作。 但是,如果我們事先知道我們只需要 N 個字符,我們可以在一次分配中完成,只復制我們正在使用的字符串中的 N 個字符 - 只需 O(n) 操作。

StringBuffer/StringBuilder 避免了這種情況,因為它們是可變的,因此不需要一遍又一遍地復制相同的數據(只要在它們的內部緩沖區中有空間可以復制)。 他們避免執行與附加數量成正比的分配和復制,方法是按照其當前大小的比例過度分配緩沖區,從而提供攤銷的 O(1) 附加。

然而值得注意的是,編譯器通常能夠自動將代碼優化為 StringBuilder 樣式(或者更好——因為它可以執行常量折疊等)。

Java 將 string1 + string2 轉換為 StringBuffer 構造、append() 和 toString()。 這是有道理的。

但是,在 Java 1.4 及更早版本中,它將分別為語句中的每個+ 運算符執行此操作。 這意味着執行 a + b + c 將導致兩個帶有兩個toString() 調用的 StringBuffer 構造。 如果你有一長串 concats,它會變成一團糟。 自己做意味着你可以控制它並正確地做到這一點。

Java 5.0 及更高版本似乎做得更明智,因此問題較少,而且肯定不那么冗長。

AFAIK 它取決於 JVM 的版本,在 1.5 之前的版本中,使用“+”或“+=”實際上每次都復制整個字符串。

請注意,使用 += 實際上會分配字符串的新副本。

正如在循環中使用 + 所指出的那樣,涉及復制。

當連接的字符串是編譯時常量時,在編譯時連接,所以

String foo = "a" + "b" + "c";

已編譯為:

String foo = "abc"; 

更多信息:

StringBuffer 是一個線程安全的 class


public final class StringBuffer extends AbstractStringBuilder
    implements Serializable, CharSequence
{
// .. skip ..
     public synchronized StringBuffer append(StringBuffer stringbuffer)
    {
        super.append(stringbuffer);
        return this;
    }
// .. skip ..
}

但是 StringBuilder 不是線程安全的,因此如果可能的話使用 StringBuilder 會更快


public final class StringBuilder extends AbstractStringBuilder
    implements Serializable, CharSequence
{
// .. skip ..
    public StringBuilder append(String s)
    {
        super.append(s);
        return this;
    }
// .. skip ..
}

StringBuffer class 維護一個字符數組來保存您連接的字符串的內容,而 + 方法每次調用時都會創建一個新字符串並附加兩個參數(param1 + param2)。

StringBuffer 更快,因為 1. 它可能能夠使用其已經存在的數組來連接/存儲所有字符串。 2. 即使它們不適合數組,分配更大的后備數組比為每次調用生成新的 String 對象更快。

原因是字符串不可變。 它不是修改字符串,而是創建一個新字符串。 字符串池存儲所有字符串值,直到垃圾收集器對其進行處理。 想想有兩個字符串作為Hellohow are you 如果我們考慮字符串池,它有兩個字符串。

在此處輸入圖像描述

如果您嘗試將這兩個字符串連接為,

字符串 1 = 字符串 1+字符串 2

現在創建一個新的字符串 object 並將其存儲在字符串池中。

在此處輸入圖像描述

如果我們嘗試連接數千個單詞,它會得到更多的 memory。 解決方案是 StringBuilder 或 StringBuffer。 只能創建一個 Object 並且可以修改。 因為兩者都是可變的。那么不需要更多的 memory。 如果您認為線程安全,則使用 StringBuffer,否則使用 StringBuilder。

public class StringExample {
   public static void main(String args[]) {
      String arr[] = {"private", "default", "protected", "public"};
      StringBuilder sb= new StringBuilder();
      for (String value : arr) {
         sb.append(value).append(" ");
      }
      System.out.println(sb);
   }
}

output:私有默認受保護公共

StringBuffer 是可變的。 它將字符串的值添加到同一個object 而不實例化另一個 object。 做類似的事情:

myString = myString + "XYZ"

將創建一個的字符串 object。

要使用“+”連接兩個字符串,需要為兩個字符串分配空間,然后從兩個字符串復制數據。 StringBuffer 針對連接進行了優化,並分配了比最初所需更多的空間。 在連接新字符串時,在大多數情況下,字符可以簡單地復制到現有字符串緩沖區的末尾。
對於連接兩個字符串,'+' 運算符的開銷可能會更少,但是當你連接更多的字符串時,StringBuffer 會領先,使用更少的 memory 分配和更少的數據復制。

因為字符串是不可變的,所以每次調用 + 運算符都會創建一個新字符串 object 並將字符串數據復制到新字符串。 由於復制字符串所需的時間與字符串的長度呈線性關系,因此對 + 運算符的 N 次調用序列會導致 O(N 2 ) 運行時間(二次)。

相反,由於 StringBuffer 是可變的,它不需要每次執行 Append() 時都復制 String,因此 N Append() 調用的序列需要 O(N) 時間(線性)。 如果您將大量字符串附加在一起,這只會在運行時產生顯着差異。

如前所述,字符串 object 是不可變的,這意味着一旦創建(見下文)就無法更改。

String x = new String("something"); // 或者

字符串 x = "某事";

因此,當您嘗試連接 String 對象時,會獲取這些對象的值並將其放入新的 String object。

如果您改為使用可變的 StringBuffer,您會不斷地將值添加到 char(基元)的內部列表中,該列表可以擴展或截斷以適應所需的值。 不創建新對象,僅在需要保存值時創建/刪除新字符。

連接兩個字符串時,實際上是在 Java 中創建了第三個字符串 object。 使用 StringBuffer(或 Java 5/6 中的 StringBuilder)更快,因為它使用內部字符數組來存儲字符串,並且當您使用其中一個 add(...) 方法時,它不會創建新的 String object。 相反,StringBuffer/Buider 附加了內部數組。

在簡單的連接中,使用 StringBuffer/Builder 或 '+' 運算符連接字符串並不是真正的問題,但是在進行大量字符串連接時,您會發現使用 StringBuffer/Builder 更快。

因為字符串在 Java 中是不可變的,所以每次連接字符串時,都會在 memory 中創建新的 object。 SpringBuffer 在 memory 中使用相同的 object。

我認為最簡單的答案是:它更快。

如果您真的想了解所有底層知識,您可以隨時查看源代碼:

http://www.sun.com/software/opensource/java/getinvolved.jsp

http://download.java.net/jdk6/latest/archive/

Java 語言規范的字符串連接運算符 +部分為您提供了有關 + 運算符為何如此緩慢的更多背景信息。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM