Xóa bộ đệm / trình tạo chuỗi sau vòng lặp


122

Làm cách nào để xóa bộ đệm chuỗi trong Java sau một vòng lặp để lần lặp tiếp theo sử dụng bộ đệm chuỗi rõ ràng?


2
Một câu hỏi khác: tại sao bạn sử dụng SB chỉ cho một lần lặp lại của vòng lặp? Có sự lặp lại bên trong khác không? SB không xứng đáng nếu bạn chỉ muốn làm A + B + C + D (trình biên dịch Java bên trong sẽ sử dụng SB). Nó hữu ích nếu bạn muốn thêm các phần của chuỗi có điều kiện, nhưng nếu không thì ... chỉ cần sử dụng "+".
helios

Câu trả lời:


135

Một tùy chọn là sử dụng phương pháp xóa như sau:

StringBuffer sb = new StringBuffer();
for (int n = 0; n < 10; n++) {
   sb.append("a");

   // This will clear the buffer
   sb.delete(0, sb.length());
}

Một tùy chọn khác (dọn dẹp bit) sử dụng setLength (int len) :

sb.setLength(0);

Xem Javadoc để biết thêm thông tin:


15
Một cái gì đó ít rác hơn một chút sẽ là chỉ khai báo StringBuffer bên trong vòng lặp.
Mark Elliot

11
À, tôi nghĩ sb.setLength (0); sạch hơn và hiệu quả hơn so với việc khai báo nó bên trong vòng lặp. Giải pháp của bạn đi ngược lại lợi ích hiệu suất của việc sử dụng StringBuffer ...
Jon

6
Tôi nghĩ rằng lợi ích về hiệu suất đến từ khả năng thay đổi của chuỗi, không phải từ việc lưu phần khởi tạo. đây là một thử nghiệm nhanh của 1e8 lặp: vòng lặp bên trong (2.97s): ideone.com/uyyTL14w , vòng bên ngoài (2.87s): ideone.com/F9lgsIxh
Đánh dấu Elliot

6
Nói về hiệu suất: Trừ khi mã của bạn được truy cập trong một kịch bản đa luồng, bạn nên sử dụng StringBuilder thay vì StringBuffer - xem javadoc: "Nếu có thể, bạn nên sử dụng lớp này ưu tiên cho StringBuffer vì nó sẽ nhanh hơn. hầu hết các triển khai ".
Rahel Lüthy

4
Lợi ích duy nhất của việc tạo SB bên ngoài là không làm mất giá trị bên trong (có thể dài) [] của nó. Nếu trong vòng lặp đầu tiên nó đã đủ lớn, thì vòng lặp thứ hai sẽ không cần thay đổi kích thước bất kỳ ký tự nào []. Nhưng để có được lợi thế, "phương thức rõ ràng" sẽ phải bảo toàn kích thước của mảng bên trong. setLength thực hiện điều đó nhưng nó cũng đặt thành \ u0000 cho tất cả các ký tự không được sử dụng trong SB, vì vậy, việc tạo một SB mới với dung lượng ban đầu tốt sẽ kém hiệu quả hơn. Khai báo bên trong vòng lặp là tốt hơn.
helios

48

Cách dễ nhất để sử dụng lại StringBufferlà sử dụng phương phápsetLength()

public void setLength(int newLength)

Bạn có thể gặp trường hợp như

StringBuffer sb = new StringBuffer("HelloWorld");
// after many iterations and manipulations
sb.setLength(0);
// reuse sb

2
@MMahmoud, Trong ví dụ mã, nó sẽ đọc setLengththay vì setlength.
OnaBai

Khi tôi nhìn thấy mã nguồn setLength, nó hoạt động như thế nào ( gist.github.com/ebuildy/e91ac6af2ff8a6b1821d18abf2f8c9e1 ) ở đây số lượng sẽ luôn lớn hơn newLength (0)?
Thomas Decaux

20

Bạn có hai lựa chọn:

Sử dụng:

sb.setLength(0);  // It will just discard the previous data, which will be garbage collected later.  

Hoặc dùng:

sb.delete(0, sb.length());  // A bit slower as it is used to delete sub sequence.  

GHI CHÚ

Tránh khai báo StringBufferhoặc StringBuildercác đối tượng trong vòng lặp nếu không nó sẽ tạo các đối tượng mới với mỗi lần lặp. Việc tạo các đối tượng đòi hỏi tài nguyên hệ thống, không gian và cũng cần thời gian. Vì vậy, về lâu dài, hãy tránh khai báo chúng trong vòng lặp nếu có thể.



5

Tôi khuyên bạn nên tạo mới StringBuffer(hoặc thậm chí tốt hơn StringBuilder) cho mỗi lần lặp. Sự khác biệt về hiệu suất thực sự không đáng kể, nhưng mã của bạn sẽ ngắn hơn và đơn giản hơn.


2
Nếu bạn có bất kỳ bằng chứng nào về sự khác biệt hiệu suất như vậy, vui lòng chia sẻ nó. (Sẽ còn vui mừng để xem thống kê về nơi Java chủ yếu được sử dụng, và theo đó định nghĩa về "chủ yếu".)
Eli Acherkan

1
Ai cũng biết rằng phân bổ (từ khóa mới trong Java) đắt hơn là chỉ thay đổi một số trường của cùng một đối tượng. Đối với "hầu hết", mà bạn có thể replave cao, có hơn 1,5 tỷ thiết bị Android, tất cả đều sử dụng Java.
Louis CAD

1
Tôi đồng ý rằng trong một số trường hợp, khả năng đọc mã quan trọng hơn hiệu suất, nhưng trong trường hợp này, đó chỉ là một dòng mã! Và bạn có thể chạy một điểm chuẩn sẽ chứng minh cho bạn rằng việc phân bổ và tạo đối tượng, cộng với việc thu gom rác sẽ tốn kém hơn so với việc không làm. Vì chúng ta đang ở trong một vòng lặp, nó không chỉ có liên quan.
Louis CAD

1
Tôi cũng đăng ký theo quan điểm của Knuth, nhưng quan điểm của anh ấy không phải lúc nào cũng đúng. Chỉ thêm một dòng để tăng chu kỳ CPU và bộ nhớ hoàn toàn không phải là điều xấu. Bạn đang có ý tưởng quá thẳng. Lưu ý rằng một vòng lặp thường được lặp lại nhiều lần. Một vòng lặp có thể được lặp lại hàng nghìn lần, có thể ăn nhiều megabyte quý giá trên thiết bị di động (và cả trên máy chủ hoặc máy tính) nếu không được tối ưu hóa cẩn thận.
Louis CAD,

1
Tôi nghĩ rằng cả hai chúng tôi đã nêu quan điểm của mình đủ rõ ràng và tôi cảm thấy rằng việc thảo luận thêm sẽ không có lợi cho phần bình luận này. Nếu bạn cảm thấy rằng câu trả lời của tôi không chính xác, gây hiểu lầm hoặc không đầy đủ, thì bạn - hoặc bất kỳ ai - đều được hoan nghênh chỉnh sửa và / hoặc phản đối nó.
Eli Acherkan

5
public void clear(StringBuilder s) {
    s.setLength(0);
}

Sử dụng:

StringBuilder v = new StringBuilder();
clear(v);

để dễ đọc, tôi nghĩ đây là giải pháp tốt nhất.


2

Đã có câu trả lời tốt ở đó. Chỉ cần thêm kết quả điểm chuẩn cho sự khác biệt hiệu suất StringBuffer và StringBuild sử dụng phiên bản mới trong vòng lặp hoặc sử dụng setLength (0) trong vòng lặp.

Tóm tắt là: Trong một vòng lặp lớn

  • StringBuilder nhanh hơn nhiều so với StringBuffer
  • Tạo cá thể StringBuilder mới trong vòng lặp không có sự khác biệt với setLength (0). (setLength (0) có lợi thế rất nhỏ so với tạo phiên bản mới.)
  • StringBuffer chậm hơn StringBuilder bằng cách tạo phiên bản mới trong vòng lặp
  • setLength (0) của StringBuffer cực kỳ chậm hơn so với tạo phiên bản mới trong vòng lặp.

Điểm chuẩn rất đơn giản (tôi chỉ cần thay đổi mã theo cách thủ công và thực hiện kiểm tra khác):

public class StringBuilderSpeed {
public static final char ch[] = new char[]{'a','b','c','d','e','f','g','h','i'};

public static void main(String a[]){
    int loopTime = 99999999;
    long startTime = System.currentTimeMillis();
    StringBuilder sb = new StringBuilder();
    for(int i = 0 ; i < loopTime; i++){
        for(char c : ch){
            sb.append(c);
        }
        sb.setLength(0);
    }
    long endTime = System.currentTimeMillis();
    System.out.println("Time cost: " + (endTime - startTime));
}

}

Phiên bản StringBuilder mới trong vòng lặp: Chi phí thời gian: 3693, 3862, 3624, 3742

StringBuilder setLength: Time Cost: 3465, 3421, 3557, 3408

Phiên bản StringBuffer mới trong vòng lặp: Chi phí thời gian: 8327, 8324, 8284

StringBuffer setLength Chi phí thời gian: 22878, 23017, 22894

Một lần nữa StringBuilder setLength để đảm bảo rằng labtop của tôi không gặp vấn đề gì khi sử dụng lâu như vậy cho StringBuffer setLength :-) Chi phí thời gian: 3448


0
StringBuffer sb = new SringBuffer();
// do something wiht it
sb = new StringBuffer();

tôi nghĩ mã này nhanh hơn.


3
Điều này sẽ có vấn đề về hiệu suất. Nó tạo ra một đối tượng mới sau mỗi lần lặp đi lặp lại
Aditya Singh

@AdityaSingh Đó là một cách tiếp cận hoàn toàn hợp lý. Đừng giả định các vấn đề về hiệu suất mà không có bằng chứng.
shmosel

Giả định mọi thứ dựa trên sự hiểu biết về những gì đang diễn ra là cơ sở của trí thông minh.
rghome
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.