Khi nào sử dụng StringBuilder?


82

Tôi hiểu những lợi ích của StringBuilder.

Nhưng nếu tôi muốn nối 2 chuỗi, thì tôi cho rằng tốt hơn (nhanh hơn) để làm điều đó mà không có StringBuilder. Điều này có chính xác?

Tại thời điểm nào (số lượng chuỗi) sử dụng StringBuilder trở nên tốt hơn?


1
Tôi tin rằng điều này đã được đề cập trước đây.
Mark Schultheiss


Câu trả lời:


79

Tôi nhiệt liệt đề nghị bạn đọc Bi kịch buồn của nhà hát tối ưu hóa vi mô , của Jeff Atwood.

Nó xử lý Simple Concatenation so với StringBuilder và các phương thức khác.

Bây giờ, nếu bạn muốn xem một số con số và đồ thị, hãy nhấp vào liên kết;)


+1 cho ! Thời gian dành để lo lắng về điều này là thời gian dành cho việc không làm điều gì đó thực sự quan trọng.
Greg D

8
Đọc sách của bạn là sai tuy nhiên: nó không quan trọng trong rất nhiều trường hợp, khi không có vòng lặp là tham gia, trong những trường hợp khác tuy nhiên nó có thể có vấn đề A LOT
Peter

1
Tôi đã xóa nội dung chỉnh sửa vì đó chỉ là thông tin sai trong một câu trả lời được chấp nhận.
Peter

2
và chỉ để cho biết mức độ quan trọng của nó, từ bài báo bạn đang tham khảo: "Trong hầu hết các ngôn ngữ được thu thập rác, các chuỗi là bất biến: khi bạn thêm hai chuỗi, nội dung của cả hai sẽ được sao chép. Khi bạn tiếp tục thêm vào dẫn đến vòng lặp này , ngày càng nhiều bộ nhớ được cấp phát mỗi lần. Điều này dẫn trực tiếp đến hiệu suất n2 cấp bốn khủng khiếp "
Peter

1
Tại sao đây là câu trả lời được chấp nhận. Tôi không nghĩ chỉ đơn giản là thả một liên kết và nói "đi đọc" là một câu trả lời tốt
Kolob Canyon

44

Nhưng nếu tôi muốn nối 2 chuỗi, thì tôi cho rằng tốt hơn (nhanh hơn) để làm điều đó mà không có StringBuilder. Điều này có chính xác?

Điều đó thực sự chính xác, bạn có thể tìm thấy lý do tại sao được giải thích chính xác rất rõ ràng trên:

http://www.yoda.arachsys.com/csharp/stringbuilder.html

Tóm lại: nếu bạn có thể nối các chuỗi trong một lần như

var result = a + " " + b  + " " + c + ..

tốt hơn hết là bạn không có StringBuilder vì chỉ có bản sao được tạo ra (độ dài của chuỗi kết quả được tính toán trước.);

Đối với cấu trúc như

var result = a;
result  += " ";
result  += b;
result  += " ";
result  += c;
..

các đối tượng mới được tạo ra mỗi lần, vì vậy bạn nên xem xét StringBuilder.

Ở cuối bài viết tóm tắt các quy tắc ngón tay cái sau:

Quy tắc của ngón tay cái

Vì vậy, khi nào bạn nên sử dụng StringBuilder và khi nào bạn nên sử dụng các toán tử nối chuỗi?

  • Chắc chắn sử dụng StringBuilder khi bạn đang nối trong một vòng lặp không tầm thường - đặc biệt nếu bạn không biết chắc chắn (tại thời điểm biên dịch) bạn sẽ thực hiện bao nhiêu lần lặp qua vòng lặp. Ví dụ: đọc một tệp một ký tự tại một thời điểm, xây dựng một chuỗi khi bạn sử dụng toán tử + = có khả năng tự tử về hiệu suất.

  • Chắc chắn sử dụng toán tử nối khi bạn có thể (dễ đọc) chỉ định mọi thứ cần được nối trong một câu lệnh. (Nếu bạn có một mảng nhiều thứ để nối, hãy xem xét gọi String.Concat một cách rõ ràng - hoặc String.Join nếu bạn cần dấu phân cách.)

  • Đừng ngại chia nhỏ các ký tự thành nhiều bit nối - kết quả sẽ giống nhau. Ví dụ, bạn có thể hỗ trợ khả năng đọc bằng cách ngắt một từ dài thành nhiều dòng mà không gây hại cho hiệu suất.

  • Nếu bạn cần kết quả trung gian của phép nối cho một thứ khác ngoài việc cung cấp cho lần lặp tiếp theo của phép nối, thì StringBuilder sẽ không giúp bạn. Ví dụ: nếu bạn xây dựng một tên đầy đủ từ họ và tên, sau đó thêm phần thông tin thứ ba (có thể là biệt hiệu) vào cuối, bạn sẽ chỉ được lợi khi sử dụng StringBuilder nếu bạn không cần chuỗi (tên + họ) cho mục đích khác (như chúng ta làm trong ví dụ tạo đối tượng Person).

  • Nếu bạn chỉ có một vài câu lệnh cần làm và bạn thực sự muốn thực hiện chúng trong các câu lệnh riêng biệt, thì bạn đi theo cách nào không thực sự quan trọng. Cách nào hiệu quả hơn sẽ phụ thuộc vào số lần nối mà kích thước của chuỗi liên quan và thứ tự chúng được nối với nhau. Nếu bạn thực sự tin rằng đoạn mã đó là một nút thắt cổ chai về hiệu suất, hãy lập hồ sơ hoặc điểm chuẩn cho nó theo cả hai cách.


13

System.String là một đối tượng bất biến - có nghĩa là bất cứ khi nào bạn sửa đổi nội dung của nó, nó sẽ cấp phát một chuỗi mới và điều này cần thời gian (và bộ nhớ?). Sử dụng StringBuilder, bạn sửa đổi nội dung thực tế của đối tượng mà không cần phân bổ nội dung mới.

Vì vậy, hãy sử dụng StringBuilder khi bạn cần thực hiện nhiều sửa đổi trên chuỗi.


8

Không thực sự ... bạn nên sử dụng StringBuilder nếu bạn nối các chuỗi lớn hoặc bạn có nhiều chuỗi nối, như trong một vòng lặp.


1
Đó là sai lầm. Bạn chỉ nên sử dụng StringBuildernếu vòng lặp hoặc nối là một vấn đề về hiệu suất đối với thông số kỹ thuật.
Alex Bagnolini

2
@Alex: Không phải lúc nào cũng vậy sao? ;) Không, nghiêm túc mà nói, tôi luôn sử dụng StringBuilder để nối bên trong một vòng lặp ... tuy nhiên, các vòng lặp của tôi đều có hơn 1k lần lặp ... @Binary: Thông thường, điều đó nên được biên dịch string s = "abcd", ít nhất đó là điều cuối cùng Tôi nghe nói ... tuy nhiên, với các biến, rất có thể là Concat.
Bobby

1
Thực tế là: nó gần như LUÔN KHÔNG phải là trường hợp. Tôi luôn sử dụng toán tử chuỗi a + "hello" + "somethingelse"và không bao giờ phải lo lắng về điều đó. Nếu nó trở thành một vấn đề, tôi sẽ sử dụng StringBuilder. Nhưng tôi đã không lo lắng về nó ngay từ đầu, và dành ít thời gian hơn để viết nó.
Alex Bagnolini

3
Hoàn toàn không có lợi ích về hiệu suất với các chuỗi lớn - chỉ với nhiều đoạn nối.
Konrad Rudolph

1
@Konrad: Bạn có chắc là không có lợi ích về hiệu suất không? Mỗi khi bạn nối các chuỗi lớn, bạn đang sao chép một lượng lớn dữ liệu; Mỗi khi bạn nối các chuỗi nhỏ, bạn chỉ sao chép một lượng nhỏ dữ liệu.
LukeH

6
  • Nếu bạn nối các chuỗi trong một vòng lặp, bạn nên cân nhắc sử dụng StringBuilder thay vì String thông thường
  • Trong trường hợp đó là nối đơn, bạn có thể không thấy sự khác biệt về thời gian thực hiện

Đây là một ứng dụng thử nghiệm đơn giản để chứng minh quan điểm:

class Program
{
    static void Main(string[] args)
    {
        const int testLength = 30000;
        var StartTime = DateTime.Now;

        //TEST 1 - String
        StartTime = DateTime.Now;
        String tString = "test string";
        for (int i = 0; i < testLength; i++)
        {
            tString += i.ToString();
        }
        Console.WriteLine((DateTime.Now - StartTime).TotalMilliseconds.ToString());
        //result: 2000 ms

        //TEST 2 - StringBuilder
        StartTime = DateTime.Now;
        StringBuilder tSB = new StringBuilder("test string");
        for (int i = 0; i < testLength; i++)
        {
            tSB.Append(i.ToString());
        }
        Console.WriteLine((DateTime.Now - StartTime).TotalMilliseconds.ToString());
        //result: 4 ms

        Console.ReadLine();
    }
}

Các kết quả:

  • 30'000 lần lặp

    • Chuỗi - 2000 ms
    • StringBuilder - 4 ms
  • 1000 lần lặp

    • Chuỗi - 2 mili giây
    • StringBuilder - 1 ms
  • 500 lần lặp

    • Chuỗi - 0 ms
    • StringBuilder - 0 ms

5

Để diễn dải

Vậy thì ngươi sẽ đếm đến ba, không hơn, không kém. Ba sẽ là số bạn sẽ đếm, và số đếm sẽ là ba. Ngươi không tính bốn, ngươi cũng không tính hai, ngoại trừ ngươi sau đó tiến hành ba. Một khi đạt đến số ba, là số thứ ba, thì hãy vận động Lựu đạn Bàn tay Thánh của Antioch của ngươi

Tôi thường sử dụng trình tạo chuỗi cho bất kỳ khối mã nào có thể dẫn đến việc nối ba hoặc nhiều chuỗi.


Nó phụ thuộc: Concetanation chỉ tạo một bản sao: "Russell" + "" + Steen + ".", Sẽ chỉ tạo một bản sao vì nó tính trước độ dài của chuỗi. Chỉ khi bạn phải chia nối của bạn, bạn nên bắt đầu suy nghĩ về một người thợ xây
Peter

4

Không có câu trả lời chắc chắn, chỉ có các quy tắc ngón tay cái. Các quy tắc cá nhân của riêng tôi diễn ra như sau:

  • Nếu nối trong một vòng lặp, hãy luôn sử dụng StringBuilder .
  • Nếu các chuỗi lớn, luôn sử dụng StringBuilder .
  • Nếu mã nối gọn gàng và có thể đọc được trên màn hình thì nó có thể ổn.
    Nếu không, hãy sử dụng a StringBuilder.

Tôi biết đây là một chủ đề cũ, nhưng tôi chỉ biết tìm hiểu và muốn biết những gì bạn coi là "Chuỗi lớn"?
MatthewD

4

Nhưng nếu tôi muốn nối 2 chuỗi, thì tôi cho rằng làm như vậy tốt hơn và nhanh hơn mà không có StringBuilder. Điều này có chính xác?

Đúng. Nhưng quan trọng hơn, sử dụng vani trong những tình huống như vậy sẽ dễ hiểu hơn rất nhiều String. Mặt khác, sử dụng nó trong một vòng lặp có ý nghĩa và cũng có thể dễ đọc như nối.

Tôi sẽ cảnh giác với các quy tắc ngón tay cái trích dẫn các số lượng cụ thể của phép nối làm ngưỡng. Sử dụng nó trong vòng lặp (và chỉ vòng lặp) có lẽ hữu ích, dễ nhớ hơn và có ý nghĩa hơn.


"Tôi sẽ cảnh giác với các quy tắc ngón tay cái trích dẫn các số lượng cụ thể của phép nối làm ngưỡng" <this. Ngoài ra, sau khi áp dụng cách hiểu thông thường, hãy nghĩ về việc người đó sẽ quay lại mã của bạn trong thời gian 6 tháng.
Phil Cooper

3

Miễn là bạn có thể nhập số lượng các nối (a + b + c ...) về mặt vật lý, nó sẽ không tạo ra sự khác biệt lớn. N bình phương (tại N = 10) là giảm tốc 100X, điều này không quá tệ.

Vấn đề lớn là khi bạn nối hàng trăm chuỗi. Tại N = 100, bạn sẽ bị chậm lại 10000 lần. Điều đó là khá tệ.


3

Vì rất khó để tìm ra lời giải thích cho điều này mà không bị ảnh hưởng bởi các ý kiến ​​hoặc tiếp theo là một cuộc chiến của các niềm tự hào, tôi nghĩ phải viết một chút mã trên LINQpad để tự kiểm tra điều này.

Tôi thấy rằng việc sử dụng các chuỗi có kích thước nhỏ thay vì sử dụng i.ToString () sẽ thay đổi thời gian phản hồi (hiển thị trong các vòng lặp nhỏ).

Thử nghiệm sử dụng các chuỗi lặp lại khác nhau để giữ các phép đo thời gian trong phạm vi có thể so sánh hợp lý.

Tôi sẽ sao chép mã ở cuối để bạn có thể tự thử (kết quả. Sơ đồ ... Dump () sẽ không hoạt động bên ngoài LINQPad).

Đầu ra (Trục X: Số lần lặp được thử nghiệm, Trục Y: Thời gian tính bằng tích tắc):

Trình tự lặp lại: 2, 3, 4, 5, 6, 7, 8, 9, 10 Trình tự lặp lại: 2, 3, 4, 5, 6, 7, 8, 9, 10

Trình tự lặp lại: 10, 20, 30, 40, 50, 60, 70, 80 Trình tự lặp lại: 10, 20, 30, 40, 50, 60, 70, 80

Trình tự lặp lại: 100, 200, 300, 400, 500 Trình tự lặp lại: 100, 200, 300, 400, 500

Mã (Được viết bằng LINQPad 5):

void Main()
{
    Test(2, 3, 4, 5, 6, 7, 8, 9, 10);
    Test(10, 20, 30, 40, 50, 60, 70, 80);
    Test(100, 200, 300, 400, 500);
}

void Test(params int[] iterationsCounts)
{
    $"Iterations sequence: {string.Join(", ", iterationsCounts)}".Dump();

    int testStringLength = 10;
    RandomStringGenerator.Setup(testStringLength);
    var sw = new System.Diagnostics.Stopwatch();
    var results = new Dictionary<int, TimeSpan[]>();

    // This call before starting to measure time removes initial overhead from first measurement
    RandomStringGenerator.GetRandomString(); 

    foreach (var iterationsCount in iterationsCounts)
    {
        TimeSpan elapsedForString, elapsedForSb;

        // string
        sw.Restart();
        var str = string.Empty;

        for (int i = 0; i < iterationsCount; i++)
        {
            str += RandomStringGenerator.GetRandomString();
        }

        sw.Stop();
        elapsedForString = sw.Elapsed;


        // string builder
        sw.Restart();
        var sb = new StringBuilder(string.Empty);

        for (int i = 0; i < iterationsCount; i++)
        {
            sb.Append(RandomStringGenerator.GetRandomString());
        }

        sw.Stop();
        elapsedForSb = sw.Elapsed;

        results.Add(iterationsCount, new TimeSpan[] { elapsedForString, elapsedForSb });
    }


    // Results
    results.Chart(r => r.Key)
    .AddYSeries(r => r.Value[0].Ticks, LINQPad.Util.SeriesType.Line, "String")
    .AddYSeries(r => r.Value[1].Ticks, LINQPad.Util.SeriesType.Line, "String Builder")
    .DumpInline();
}

static class RandomStringGenerator
{
    static Random r;
    static string[] strings;

    public static void Setup(int testStringLength)
    {
        r = new Random(DateTime.Now.Millisecond);

        strings = new string[10];
        for (int i = 0; i < strings.Length; i++)
        {
            strings[i] = Guid.NewGuid().ToString().Substring(0, testStringLength);
        }
    }

    public static string GetRandomString()
    {
        var indx = r.Next(0, strings.Length);
        return strings[indx];
    }
}

2

Tôi không nghĩ rằng có một ranh giới tốt giữa khi nào nên sử dụng hoặc khi nào không. Tất nhiên, trừ khi ai đó thực hiện một số thử nghiệm rộng rãi để đưa ra các điều kiện vàng.

Đối với tôi, tôi sẽ không sử dụng StringBuilder nếu chỉ nối 2 chuỗi lớn. Nếu có vòng lặp với số lượng không xác định, tôi có khả năng làm như vậy, ngay cả khi vòng lặp có thể là số lượng nhỏ.


Thật vậy, sẽ hoàn toàn sai nếu bạn đặt StringBuilder để nối 2 chuỗi, nhưng điều đó không liên quan gì đến perf. thử nghiệm - đó chỉ đơn giản là sử dụng nó cho một điều sai lầm.
Marc Gravell

1

Một đoạn nối đơn không có giá trị sử dụng một StringBuilder. Tôi thường sử dụng 5 phép nối như một quy tắc ngón tay cái.

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.