it-swarm-vi.tech

Tại sao nên sử dụng StringBuffer trong Java thay vì toán tử nối chuỗi

Ai đó nói với tôi rằng sử dụng StringBuffer hiệu quả hơn để nối chuỗi trong Java so với sử dụng + toán tử cho Strings. Điều gì xảy ra dưới mui xe khi bạn làm điều đó? StringBuffer làm gì khác nhau?

54
harry

Tốt hơn là sử dụng StringBuilder (đây là phiên bản không đồng bộ hóa; khi nào bạn xây dựng chuỗi song song?) Trong hầu hết mọi trường hợp, nhưng đây là điều xảy ra:

Khi bạn sử dụng + với hai chuỗi, nó sẽ biên dịch mã như thế này:

String third = first + second;

Để một cái gì đó như thế này:

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

Do đó, chỉ với một vài ví dụ, nó thường không tạo ra sự khác biệt. Nhưng khi bạn xây dựng một chuỗi phức tạp, bạn thường phải giải quyết nhiều vấn đề hơn thế này; ví dụ: bạn có thể đang sử dụng nhiều câu lệnh nối thêm khác nhau hoặc một vòng lặp như thế này:

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

Trong trường hợp này, một cá thể StringBuilder mới và một String mới (giá trị mới của out - Strings là bất biến) được yêu cầu trong mỗi lần lặp. Điều này rất lãng phí. Thay thế điều này bằng một StringBuilder có nghĩa là bạn chỉ có thể tạo một String và không lấp đầy đống với Strings mà bạn không quan tâm.

62
Calum

Đối với cách ghép đơn giản như:

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

Việc sử dụng StringBuffer - as jodonnell là khá vô nghĩa khi chỉ ra rằng nó sẽ được dịch thông minh thành:

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

NHƯNG nó rất không phù hợp để nối các chuỗi trong một vòng lặp, như:

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

Sử dụng chuỗi trong vòng lặp này sẽ tạo ra 10 đối tượng chuỗi trung gian trong bộ nhớ: "0", "01", "012", v.v. Trong khi viết tương tự bằng cách sử dụng StringBuffer bạn chỉ cần cập nhật một số bộ đệm bên trong của StringBuffer và bạn không tạo các đối tượng chuỗi trung gian mà bạn không cần:

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

Trên thực tế với ví dụ trên, bạn nên sử dụng StringBuilder (được giới thiệu trong Java 1.5) thay vì StringBuffer - StringBuffer nặng hơn tất cả phương pháp được đồng bộ hóa.

42
tkokoszka

Người này không nên nhanh hơn người kia. Điều này không đúng trước Java 1.4.2, vì khi nối nhiều hơn hai chuỗi bằng toán tử "+", các đối tượng String trung gian sẽ được tạo trong quá trình xây dựng chuỗi cuối cùng.

Tuy nhiên, vì các trạng thái JavaDoc cho StringBuffer , ít nhất là từ Java 1.4.2 bằng cách sử dụng toán tử "+" biên dịch để tạo StringBufferappend() ing nhiều chuỗi cho nó. Vì vậy, không có sự khác biệt, rõ ràng.

Tuy nhiên, hãy cẩn thận khi sử dụng thêm chuỗi vào chuỗi khác trong vòng lặp! Ví dụ:

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;
}

Tuy nhiên, hãy nhớ rằng việc nối một vài chuỗi với "+" sẽ sạch hơn append() ing tất cả các chuỗi.

20
André Chalella

Trong phần mềm này, nó thực sự tạo và gắn vào StringBuffer, gọi toString () trên kết quả. Vì vậy, nó thực sự không còn quan trọng mà bạn sử dụng nữa.

Vì thế

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

trở thành

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

Điều đó đúng với một loạt các phụ lục nội tuyến trong một câu lệnh. Nếu bạn xây dựng chuỗi của mình qua nhiều câu lệnh, thì bạn đang lãng phí bộ nhớ và StringBuffer hoặc StringBuilder là lựa chọn tốt hơn của bạn.

9
jodonnell

Tôi nghĩ rằng đã đưa ra jdk1.5 (hoặc cao hơn) và kết nối của bạn là an toàn cho chuỗi, bạn nên sử dụng StringBuilder thay vì StringBuffer http://Java4ever.blogspot.com/2007/03/opes-vs-opesbuffer-vs -opesbuilder.html Về mức tăng tốc độ: http://www.about280.com/opestest.html

Cá nhân tôi muốn mã cho khả năng đọc, vì vậy trừ khi bạn thấy rằng việc nối chuỗi làm cho mã của bạn chậm hơn đáng kể, hãy giữ nguyên phương thức nào làm cho mã của bạn dễ đọc hơn.

7
slipset

Trong một số trường hợp, điều này đã lỗi thời do tối ưu hóa được thực hiện bởi trình biên dịch, nhưng vấn đề chung là mã như:

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

sẽ hoạt động như dưới đây (mỗi bước là vòng lặp tiếp theo):

  1. xây dựng một đối tượng chuỗi có độ dài 1 và giá trị "x"
  2. Tạo một đối tượng chuỗi mới có kích thước 2, sao chép chuỗi cũ "x" vào đó, thêm "x" vào vị trí 2.
  3. Tạo một đối tượng chuỗi mới có kích thước 3, sao chép chuỗi cũ "xx" vào đó, thêm "x" vào vị trí 3.
  4. ... và cứ thế

Như bạn có thể thấy, mỗi lần lặp lại phải sao chép thêm một ký tự, dẫn đến việc chúng ta thực hiện 1 + 2 + 3 + 4 + 5 + ... + N hoạt động mỗi vòng lặp. Đây là một hoạt động O (n ^ 2). Tuy nhiên, nếu chúng tôi biết trước rằng chúng tôi chỉ cần N ký tự, chúng tôi có thể làm điều đó trong một phân bổ duy nhất, với bản sao chỉ N ký tự từ các chuỗi chúng tôi đang sử dụng - một mere O(n) hoạt động.

StringBuffer/StringBuilder tránh điều này bởi vì chúng có thể thay đổi và do đó không cần phải lặp đi lặp lại việc sao chép cùng một dữ liệu (miễn là có không gian để sao chép vào bộ đệm bên trong của chúng). Họ tránh thực hiện phân bổ và sao chép tỷ lệ thuận với số lượng phụ lục được thực hiện bằng cách phân bổ quá mức bộ đệm của họ theo tỷ lệ kích thước hiện tại của nó, cho phép khấu hao O(1).

Tuy nhiên, điều đáng chú ý là thường trình biên dịch sẽ có thể tối ưu hóa mã thành kiểu StringBuilder (hoặc tốt hơn - vì nó có thể thực hiện gập liên tục, v.v.).

5
Brian

AFAIK, nó phụ thuộc vào phiên bản JVM, trong các phiên bản trước 1.5 sử dụng "+" hoặc "+ =" thực sự sao chép toàn bộ chuỗi mỗi lần.

Coi chừng việc sử dụng + = thực sự phân bổ bản sao mới của chuỗi.

Như đã chỉ bằng cách sử dụng + trong các vòng lặp liên quan đến sao chép.

Khi các chuỗi được hợp nhất là các hằng số thời gian biên dịch ở đó được nối vào thời gian biên dịch, vì vậy

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

Đã được biên dịch thành:

String foo = "abc"; 
3
jb.

Java biến chuỗi1 + string2 thành cấu trúc StringBuffer, append () và toString (). Điều này thật ý nghĩa.

Tuy nhiên, trong Java 1.4 trở về trước, nó sẽ thực hiện điều này cho toán tử mỗi + trong câu lệnh riêng. Điều này có nghĩa là thực hiện + b + c sẽ dẫn đến hai StringBuffer xây dựng với các cuộc gọi hai toString (). Nếu bạn có một chuỗi dài các concats, nó sẽ biến thành một mớ hỗn độn thực sự. có nghĩa là bạn có thể kiểm soát điều này và làm nó đúng cách.

Java 5.0 trở lên dường như làm điều đó hợp lý hơn, do đó, nó ít gặp vấn đề hơn và chắc chắn là ít dài dòng hơn.

3
Alan Krueger

Thêm thông tin:

StringBuffer là một lớp an toàn luồng


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

Nhưng StringBuilder không an toàn cho luồng, do đó, sử dụng StringBuilder sẽ nhanh hơn nếu có thể


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

1
Eric Yung

StringBuffer có thể thay đổi. Nó thêm giá trị của chuỗi vào đối tượng tương tự mà không khởi tạo đối tượng khác. Làm một cái gì đó như:

myString = myString + "XYZ"

sẽ tạo một đối tượng new String.

1
Loren Segal

Để nối hai chuỗi bằng '+', một chuỗi mới cần được phân bổ không gian cho cả hai chuỗi và sau đó dữ liệu được sao chép từ cả hai chuỗi. StringBuffer được tối ưu hóa để ghép nối và phân bổ nhiều không gian hơn mức cần thiết ban đầu. Khi bạn nối một chuỗi mới, trong hầu hết các trường hợp, các ký tự có thể được sao chép đơn giản vào cuối bộ đệm chuỗi hiện có.
[.___.

1
Eclipse

Lớp StringBuffer duy trì một mảng các ký tự để chứa nội dung của các chuỗi bạn nối, trong khi phương thức + tạo ra một chuỗi mới mỗi lần nó được gọi và nối thêm hai tham số (param1 + param2).

StringBuffer nhanh hơn vì 1. nó có thể sử dụng mảng đã có sẵn của nó để nối/lưu trữ tất cả các chuỗi. 2. ngay cả khi chúng không vừa trong mảng, thì việc phân bổ một mảng sao lưu lớn hơn sẽ nhanh hơn để tạo các đối tượng Chuỗi mới cho mỗi lần di chuyển.

1
Matt Novinger

Vì Chuỗi là bất biến, mỗi lệnh gọi toán tử + tạo một đối tượng Chuỗi mới và sao chép dữ liệu Chuỗi sang Chuỗi mới. Do việc sao chép Chuỗi cần thời gian tuyến tính theo chiều dài của Chuỗi, nên một chuỗi các cuộc gọi N đến toán tử + dẫn đến O (N2) thời gian chạy (bậc hai).

Ngược lại, do StringBuffer có thể thay đổi, nên không cần sao chép Chuỗi mỗi khi bạn thực hiện Append (), do đó, một chuỗi các cuộc gọi N Append () sẽ mất O(N) time ( tuyến tính). Điều này chỉ tạo ra sự khác biệt đáng kể trong thời gian chạy nếu bạn nối thêm một số lượng lớn các Chuỗi với nhau.

1
Adam Rosenfield

Như đã nói, đối tượng String là ummutable, nghĩa là một khi nó được tạo ra (xem bên dưới) thì nó không thể thay đổi.

Chuỗi x = Chuỗi mới ("cái gì đó"); // hoặc là

Chuỗi x = "một cái gì đó";

Vì vậy, khi bạn cố gắng nối các đối tượng String, giá trị của các đối tượng đó được lấy và đưa vào một đối tượng String mới.

Thay vào đó, nếu bạn sử dụng StringBuffer, mà IS có thể thay đổi, bạn liên tục thêm các giá trị vào danh sách nội bộ của char (nguyên thủy), có thể được mở rộng hoặc cắt bớt để phù hợp với giá trị cần thiết. được tạo, chỉ các char mới được tạo/xóa khi cần để giữ các giá trị.

1
Christian P.

Khi bạn nối hai chuỗi, bạn thực sự tạo một đối tượng Chuỗi thứ ba trong Java. Sử dụng StringBuffer (hoặc StringBuilder trong Java 5/6), nhanh hơn vì nó sử dụng một mảng ký tự bên trong để lưu trữ chuỗi và khi bạn sử dụng một trong các phương thức add (...) của nó , nó không tạo ra một đối tượng String mới. Thay vào đó, StringBuffer/Buider nối thêm mảng bên trong.

Trong các phép nối đơn giản, thực sự không có vấn đề gì nếu bạn nối chuỗi bằng StringBuffer/Builder hoặc toán tử '+', nhưng khi thực hiện nhiều phép nối chuỗi, bạn sẽ thấy rằng sử dụng StringBuffer/Builder nhanh hơn.

1
Alexandre Brasil

Vì các chuỗi là bất biến trong Java, mỗi khi bạn kết hợp một Chuỗi, đối tượng mới được tạo trong bộ nhớ. StringBuffer sử dụng cùng một đối tượng trong bộ nhớ.

0
Ivan Bosnic

Tôi nghĩ rằng câu trả lời đơn giản nhất là: nó nhanh hơn.

Nếu bạn thực sự muốn biết tất cả những thứ không phù hợp, bạn luôn có thể tự mình xem nguồn:

http://www.Sun.com/software/opensource/Java/getinvolve.jsp

http://doad.Java.net/jdk6/latest/archive/

0
rgcb

Phần Toán tử nối chuỗi + của Java Đặc tả ngôn ngữ cung cấp cho bạn thêm một số thông tin cơ bản về lý do tại sao toán tử + có thể chậm như vậy.

0
Benedikt Waldvogel