Cách hiệu quả nhất để nối chuỗi?


285

Cách hiệu quả nhất để nối chuỗi?


9
Tôi muốn đặt một cảnh báo nổi bật ở đây rằng câu trả lời được chấp nhận là không đầy đủ đáng kể vì nó không thảo luận về tất cả các trường hợp liên quan.
usr

@usr Thật vậy ... thông tin chi tiết hơn về StringBuildercác trường hợp sử dụng có thể được tìm thấy ở đây .
Tamir Vered

Yêu thích mới của tôi kể từ C # 6 là $ "Văn bản không đổi ở đây {foo} và {bar}" ... nó giống như String.Formattrên steroid. Mà, hiệu suất khôn ngoan, chậm hơn một chút ở một lớp lót so với +String.Concat, nhưng tốt hơn nhiều so với những cái đó, mặc dù chậm hơn so với StringBuilder, ở nhiều cuộc gọi. Thực tế mà nói, sự khác biệt về hiệu năng là như vậy, nếu tôi phải chọn chỉ một cách để nối, tôi sẽ chọn phép nội suy chuỗi bằng cách sử dụng $... Nếu hai cách, sau đó thêm StringBuildervào hộp công cụ của tôi. Với hai cách bạn đã đặt.
u8it

Câu String.Jointrả lời dưới đây không thực +sự công bằng và thực tế mà nói, là một cách tồi để nối chuỗi, nhưng đó là hiệu suất nhanh đáng ngạc nhiên. Câu trả lời tại sao thú vị. String.ConcatString.Joincả hai có thể hành động trên mảng, nhưng String.Jointhực sự nhanh hơn. Rõ ràng, String.Joinkhá phức tạp và được tối ưu hóa hơn String.Concat, một phần vì nó hoạt động tương tự như StringBuilderở chỗ nó tính toán độ dài chuỗi trước và sau đó xây dựng chuỗi được hưởng lợi từ kiến ​​thức này bằng UnSafeCharBuffer.
u8it

Ok, vậy là nhanh, nhưng String.Joincũng yêu cầu xây dựng một mảng có vẻ không hiệu quả về tài nguyên phải không? ... Hóa ra điều đó +String.Concatxây dựng các mảng cho các thành phần của chúng. Do đó, tự tạo ra một mảng và nuôi nó để String.Joinlà tương đối nhanh hơn ... Tuy nhiên, StringBuildervẫn còn nhanh hơn so với String.Jointrong khoảng mọi phương diện thực tiễn trong khi $chỉ hơi chậm hơn và nhanh hơn nhiều tại các chuỗi dài ... chưa kể rằng đó là vụng về và xấu xí để sử dụng String.Joinnếu bạn có để tạo một mảng cho nó tại chỗ.
u8it

Câu trả lời:


154

Các StringBuilder.Append()phương pháp là tốt hơn nhiều so với sử dụng các +nhà điều hành. Nhưng tôi đã thấy rằng, khi thực hiện 1000 phép nối hoặc ít hơn, String.Join()thậm chí còn hiệu quả hơn StringBuilder.

StringBuilder sb = new StringBuilder();
sb.Append(someString);

Vấn đề duy nhất String.Joinlà bạn phải nối các chuỗi với một dấu phân cách chung.

Chỉnh sửa: như @ryanversaw đã chỉ ra, bạn có thể tạo dấu phân cách string.Empty.

string key = String.Join("_", new String[] 
{ "Customers_Contacts", customerID, database, SessionID });

11
StringBuildercó chi phí khởi nghiệp tương đương rất lớn, nó chỉ hiệu quả khi được sử dụng với các chuỗi rất lớn hoặc rất nhiều kết nối. Nó không phải là tầm thường để tìm ra cho bất kỳ tình huống nào. Nếu hiệu suất là vấn đề, hồ sơ là bạn của bạn (kiểm tra ANTS).
Abel

32
Điều này không đúng với nối đơn dòng. Giả sử bạn thực hiện myString = "foo" + var1 + "bar" + var2 + "hello" + var3 + "world", trình biên dịch sẽ tự động biến điều đó thành một cuộc gọi string.concat, hiệu quả như nó nhận được. Câu trả lời này không chính xác, có rất nhiều câu trả lời tốt hơn để lựa chọn
csauve

2
Đối với nối chuỗi tầm thường, sử dụng những gì dễ đọc nhất. chuỗi a = b + c + d; hầu như sẽ luôn nhanh hơn so với thực hiện với StringBuilder, nhưng sự khác biệt thường không liên quan. Sử dụng StringBuilder (hoặc tùy chọn khác bạn chọn) khi liên tục thêm vào cùng một chuỗi (ví dụ: xây dựng báo cáo) hoặc khi xử lý các chuỗi lớn.
Swanny

5
Tại sao bạn không đề cập đến string.Concat?
Venemo

271

Rico Mariani , chuyên gia về hiệu suất .NET, đã có một bài viết về chính chủ đề này. Nó không đơn giản như người ta có thể nghi ngờ. Lời khuyên cơ bản là:

Nếu mô hình của bạn trông như:

x = f1(...) + f2(...) + f3(...) + f4(...)

đó là một concat và nó rất nhanh, StringBuilder có thể sẽ không giúp đỡ.

Nếu mô hình của bạn trông như:

if (...) x += f1(...)
if (...) x += f2(...)
if (...) x += f3(...)
if (...) x += f4(...)

thì có lẽ bạn muốn StringBuilder.

Tuy nhiên, một bài viết khác để hỗ trợ cho tuyên bố này đến từ Eric Lippert, nơi ông mô tả các tối ưu hóa được thực hiện trên các đường +nối một cách chi tiết.


1
Còn String.Format () thì sao?
IronSlug

85

Có 6 loại nối chuỗi:

  1. Sử dụng ký hiệu dấu cộng ( +).
  2. Sử dụng string.Concat().
  3. Sử dụng string.Join().
  4. Sử dụng string.Format().
  5. Sử dụng string.Append().
  6. Sử dụng StringBuilder.

Trong một thí nghiệm, người ta đã chứng minh rằng đó string.Concat()là cách tốt nhất để tiếp cận nếu các từ nhỏ hơn 1000 (xấp xỉ) và nếu các từ nhiều hơn 1000 thì StringBuildernên sử dụng.

Để biết thêm thông tin, kiểm tra trang web này .

chuỗi.Join () so với chuỗi.Concat ()

Phương thức string.Concat ở đây tương đương với lời gọi phương thức string.Join với một dấu phân cách trống. Nối một chuỗi rỗng thì nhanh, nhưng không làm như vậy thậm chí còn nhanh hơn, vì vậy phương thức string.Concat sẽ tốt hơn ở đây.


4
Nên đọc nó đã được chứng minh chuỗi.Concat () hoặc + là cách tốt nhất. Có tôi có thể lấy cái này từ bài viết nhưng nó giúp tôi tiết kiệm một cú nhấp chuột. Vì vậy, + và concat biên dịch thành cùng một mã.
brumScouse

Tôi đã sử dụng cơ sở này để thử và làm cho một phương pháp của tôi hiệu quả hơn, trong đó tôi chỉ cần nối chính xác 3 chuỗi. Tôi thấy rằng nó +thực sự nhanh hơn 3 mili giây string.Concat(), mặc dù tôi chưa xem xét số lượng chuỗi cần thiết trước khi ra string.Concat()ngoài +.
Gnemlock

59

Từ Chinh Do - StringBuilder không phải lúc nào cũng nhanh hơn :

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

  • Khi nối ba giá trị chuỗi động trở xuống, hãy sử dụng nối chuỗi truyền thống.

  • Khi nối nhiều hơn ba giá trị chuỗi động, hãy sử dụng StringBuilder.

  • Khi xây dựng một chuỗi lớn từ một số chuỗi ký tự, hãy sử dụng một trong hai @ chuỗi ký tự hoặc toán tử nội tuyến +.

Hầu hết thời gian StringBuilderlà đặt cược tốt nhất của bạn, nhưng có những trường hợp như thể hiện trong bài đăng đó mà ít nhất bạn nên nghĩ về từng tình huống.


8
afaik @ chỉ tắt xử lý trình tự thoát. msdn.microsoft.com/en-us/l Library / 2002314fe.aspx đồng ý
abatishchev

12

Nếu bạn đang hoạt động trong một vòng lặp, StringBuildercó lẽ là cách để đi; nó giúp bạn tiết kiệm chi phí tạo chuỗi mới thường xuyên. Trong mã sẽ chỉ chạy một lần, tuy nhiên, String.Concatcó lẽ là tốt.

Tuy nhiên, Rico Mariani (chuyên gia tối ưu hóa .NET) đã tạo ra một bài kiểm tra mà cuối cùng ông tuyên bố rằng, trong hầu hết các trường hợp, ông khuyến nghị String.Format.


Tôi đã khuyến nghị sử dụng chuỗi.format trên chuỗi + chuỗi trong nhiều năm cho những người tôi đã làm việc cùng. Tôi nghĩ rằng những lợi thế dễ đọc là một lợi thế bổ sung ngoài lợi ích hiệu suất.
Scott Lawrence

1
Đây là câu trả lời đúng thực tế. Câu trả lời hiện được chấp nhận cho StringBuilder là không chính xác, vì nó không đề cập đến các dòng đơn bổ sung cho chuỗi.concat hoặc + nhanh hơn. Thực tế ít người biết là trình biên dịch thực sự dịch + 'thành chuỗi.concat. Ngoài ra, đối với các vòng lặp hoặc cho nhiều concat dòng, tôi sử dụng trình tạo chuỗi được xây dựng tùy chỉnh chỉ xuất hiện khi .ToString được gọi - khắc phục sự cố bộ đệm không xác định mà StringBuilder có
csauve

2
chuỗi.Format không phải là cách nhanh nhất trong mọi trường hợp. Tôi không biết làm thế nào để đưa ra một trường hợp mà nó đi trước.
usr

@usr - lưu ý rằng Rico rõ ràng không nói đó là nhanh nhất , chỉ là đó là khuyến nghị của anh ấy: "Mặc dù đó là hoạt động tồi tệ nhất và chúng tôi biết trước điều đó, cả hai Kiến trúc sư hiệu suất CLR của bạn đều đồng ý rằng [string.Format] nên là sự lựa chọn mặc định. Trong trường hợp rất khó xảy ra, nó trở thành một vấn đề hoàn hảo, vấn đề có thể giải quyết được chỉ với những thay đổi cục bộ khiêm tốn. Thông thường, bạn chỉ cần thanh toán bằng một số khả năng bảo trì tốt. "
Adam V

@AdamV câu hỏi là về cách nhanh nhất. Tôi không đồng ý về việc nó là lựa chọn mặc định mặc dù không phải vì lý do hoàn hảo. Nó có thể là cú pháp vụng về. Resharper có thể chuyển đổi qua lại theo ý muốn.
usr

10

Đây là phương pháp nhanh nhất tôi đã phát triển trong một thập kỷ cho ứng dụng NLP quy mô lớn của mình. Tôi có các biến thể cho IEnumerable<T>và các loại đầu vào khác, có và không có các dấu phân tách của các loại khác nhau ( Char, String), nhưng ở đây tôi chỉ ra trường hợp đơn giản là nối tất cả các chuỗi trong một mảng thành một chuỗi, không có dấu phân cách. Phiên bản mới nhất ở đây được phát triển và thử nghiệm đơn vị trên C # 7.NET 4.7 .

Có hai chìa khóa để hiệu suất cao hơn; đầu tiên là tính toán trước tổng kích thước chính xác cần thiết. Bước này là tầm thường khi đầu vào là một mảng như được hiển thị ở đây. Để xử lý IEnumerable<T>thay vào đó, trước tiên, đáng để tập hợp các chuỗi thành một mảng tạm thời để tính tổng đó (Mảng được yêu cầu để tránh gọi ToString()nhiều hơn một lần cho mỗi phần tử vì về mặt kỹ thuật, do khả năng tác dụng phụ, làm như vậy có thể thay đổi ngữ nghĩa dự kiến của một hoạt động 'chuỗi tham gia').

Tiếp theo, với tổng kích thước phân bổ của chuỗi cuối cùng, hiệu suất tăng mạnh nhất đạt được bằng cách xây dựng chuỗi kết quả tại chỗ . Làm điều này đòi hỏi kỹ thuật (có lẽ gây tranh cãi) tạm thời đình chỉ tính bất biến của một cái mới Stringban đầu được phân bổ đầy đủ các số không. Bất kỳ tranh cãi như vậy sang một bên, tuy nhiên ...

... Lưu ý rằng đây là giải pháp ghép nối hàng loạt duy nhất trên trang này, điều này hoàn toàn tránh được một vòng phân bổ và sao chép bổ sung của nhà Stringxây dựng.

Mã hoàn chỉnh:

/// <summary>
/// Concatenate the strings in 'rg', none of which may be null, into a single String.
/// </summary>
public static unsafe String StringJoin(this String[] rg)
{
    int i;
    if (rg == null || (i = rg.Length) == 0)
        return String.Empty;

    if (i == 1)
        return rg[0];

    String s, t;
    int cch = 0;
    do
        cch += rg[--i].Length;
    while (i > 0);
    if (cch == 0)
        return String.Empty;

    i = rg.Length;
    fixed (Char* _p = (s = new String(default(Char), cch)))
    {
        Char* pDst = _p + cch;
        do
            if ((t = rg[--i]).Length > 0)
                fixed (Char* pSrc = t)
                    memcpy(pDst -= t.Length, pSrc, (UIntPtr)(t.Length << 1));
        while (pDst > _p);
    }
    return s;
}

[DllImport("MSVCR120_CLR0400", CallingConvention = CallingConvention.Cdecl)]
static extern unsafe void* memcpy(void* dest, void* src, UIntPtr cb);

Tôi nên đề cập rằng mã này có một sửa đổi nhỏ từ những gì tôi sử dụng bản thân mình. Trong bản gốc, tôi gọi hướng dẫn cpblk IL từ C # để thực hiện sao chép thực tế. Để đơn giản và tính di động trong mã ở đây, tôi đã thay thế bằng P / Gọi memcpythay thế, như bạn có thể thấy. Để có hiệu suất cao nhất trên x64 ( nhưng có thể không phải là x86 ), bạn có thể muốn sử dụng phương thức cpblk thay thế.


string.Jointất cả những điều này đã dành cho bạn. Không cần phải tự viết. Nó tính toán kích thước của chuỗi cuối cùng, xây dựng một chuỗi có kích thước đó và sau đó ghi ra mảng ký tự bên dưới. Nó thậm chí còn có tiền thưởng khi sử dụng tên biến có thể đọc được trong quá trình.
Phục vụ

1
@Servy Cảm ơn bạn đã bình luận; thực sự String.Joincó thể hiệu quả Như tôi đã gợi ý trong phần giới thiệu, đoạn mã ở đây chỉ là minh họa đơn giản nhất cho một họ các hàm tôi sử dụng cho các tình huống String.Joinkhông xử lý (như tối ưu hóa cho Chardấu phân cách) hoặc không xử lý trong các phiên bản .NET trước. Tôi cho rằng tôi không nên chọn cái này cho ví dụ đơn giản nhất, vì đây là một trường hợp String.Joinđã xử lý tốt, mặc dù với "sự không hiệu quả", có thể không thể đo lường được, khi xử lý một dải phân cách trống, viz. String.Empty.
Glenn Slayden

Chắc chắn, trong trường hợp bạn không có dải phân cách, thì bạn nên gọi Concat, cái này cũng thực hiện điều này một cách chính xác. Dù bằng cách nào bạn cũng không cần phải tự viết mã.
Phục vụ

7
@Servy Tôi đã so sánh hiệu suất String.Joinso với mã của tôi bằng cách sử dụng khai thác thử nghiệm này . Đối với 10 triệu hoạt động nối ngẫu nhiên của tối đa 100 chuỗi có kích thước từ mỗi chuỗi, mã được hiển thị ở trên nhanh hơn 34% so với String.Jointrên bản phát hành x64 với .NET 4.7 . Vì OP yêu cầu rõ ràng phương pháp "hiệu quả nhất", kết quả cho thấy câu trả lời của tôi được áp dụng. Nếu điều này giải quyết mối quan tâm của bạn, tôi mời bạn xem xét lại downvote của bạn.
Glenn Slayden

1
Gần đây tôi đã điểm chuẩn này trên x64 full CLR 4.7.1 và thấy nó nhanh gấp khoảng hai lần chuỗi. Tôi có bộ nhớ được phân bổ ít hơn khoảng 25% ( i.imgur.com/SxIpEmL.png ) khi sử dụng cpblk hoặc github.com/ JonHanna / Mnemosyne
-starin

6

Từ bài viết MSDN này :

Có một số chi phí liên quan đến việc tạo một đối tượng StringBuilder, cả về thời gian và bộ nhớ. Trên một máy có bộ nhớ nhanh, StringBuilder trở nên đáng giá nếu bạn thực hiện khoảng năm thao tác. Theo nguyên tắc thông thường, tôi sẽ nói 10 thao tác chuỗi trở lên là sự biện minh cho chi phí hoạt động trên bất kỳ máy nào, thậm chí là chậm hơn.

Vì vậy, nếu bạn tin tưởng MSDN, hãy sử dụng StringBuilder nếu bạn phải thực hiện hơn 10 chuỗi thao tác / nối chuỗi - nếu không thì chuỗi chuỗi đơn giản với '+' là ổn.



5

Thêm vào các câu trả lời khác, xin lưu ý rằng StringBuilder có thể được cho biết một lượng bộ nhớ ban đầu để phân bổ .

Các khả năng tham số xác định số ký tự tối đa có thể được lưu trữ trong bộ nhớ được phân bổ bởi các trường hợp hiện nay. Giá trị của nó được gán cho thuộc tính Năng lực . Nếu số lượng ký tự được lưu trữ trong phiên bản hiện tại vượt quá giá trị dung lượng này , đối tượng StringBuilder sẽ cấp phát bộ nhớ bổ sung để lưu trữ chúng.

Nếu dung lượng bằng 0, dung lượng mặc định dành riêng cho triển khai được sử dụng.

Việc liên tục nối thêm vào StringBuilder chưa được phân bổ trước có thể dẫn đến rất nhiều phân bổ không cần thiết giống như liên tục nối các chuỗi thông thường.

Nếu bạn biết chuỗi cuối cùng sẽ kéo dài bao lâu, có thể tính toán nó một cách tầm thường hoặc có thể đưa ra phỏng đoán có giáo dục về trường hợp phổ biến (phân bổ quá nhiều không nhất thiết là điều xấu), bạn nên cung cấp thông tin này cho nhà xây dựng hoặc Năng lực tài sản. Đặc biệt là khi chạy các bài kiểm tra hiệu năng để so sánh StringBuilder với các phương thức khác như String.Concat, chúng thực hiện cùng một thứ trong nội bộ. Bất kỳ thử nghiệm nào bạn thấy trực tuyến không bao gồm phân bổ trước StringBuilder trong các so sánh của nó đều sai.

Nếu bạn không thể đưa ra bất kỳ loại dự đoán nào về kích thước, có lẽ bạn đang viết một hàm tiện ích sẽ có đối số tùy chọn riêng để kiểm soát phân bổ trước.


4

Sau đây có thể là một giải pháp thay thế khác để nối nhiều chuỗi.

String str1 = "sometext";
string str2 = "some other text";

string afterConcate = $"{str1}{str2}";

nội suy chuỗi


1
Điều này thực sự đáng ngạc nhiên tốt đẹp như là một phương pháp nối chung. Về cơ bản, String.Formatnó dễ đọc hơn và dễ làm việc hơn. Đánh dấu băng ghế dự bị, nó chậm hơn một chút so với +String.Concatở một đường nối nhưng tốt hơn nhiều so với cả hai cuộc gọi lặp đi lặp lại StringBuilderít cần thiết hơn.
u8it

2

Hiệu quả nhất là sử dụng StringBuilder, như vậy:

StringBuilder sb = new StringBuilder();
sb.Append("string1");
sb.Append("string2");
...etc...
String strResult = sb.ToString();

@jonezy: String.Concat vẫn ổn nếu bạn có một vài điều nhỏ nhặt. Nhưng nếu bạn đang kết hợp megabyte dữ liệu, chương trình của bạn có thể sẽ tăng.


này, giải pháp cho megabyte dữ liệu là gì?
Neel

2

Hãy thử 2 đoạn mã này và bạn sẽ tìm thấy giải pháp.

 static void Main(string[] args)
    {
        StringBuilder s = new StringBuilder();
        for (int i = 0; i < 10000000; i++)
        {
            s.Append( i.ToString());
        }
        Console.Write("End");
        Console.Read();
    }

Vs

static void Main(string[] args)
    {
        string s = "";
        for (int i = 0; i < 10000000; i++)
        {
            s += i.ToString();
        }
        Console.Write("End");
        Console.Read();
    }

Bạn sẽ thấy rằng mã thứ 1 sẽ kết thúc thực sự nhanh chóng và bộ nhớ sẽ có số lượng tốt.

Mã thứ hai có thể bộ nhớ sẽ ổn, nhưng sẽ mất nhiều thời gian hơn ... lâu hơn nhiều. Vì vậy, nếu bạn có một ứng dụng cho nhiều người dùng và bạn cần tốc độ, hãy sử dụng ứng dụng thứ nhất. Nếu bạn có một ứng dụng cho một ứng dụng người dùng ngắn hạn, có thể bạn có thể sử dụng cả hai hoặc thứ 2 sẽ "tự nhiên" hơn cho các nhà phát triển.

Chúc mừng.


1

Đối với chỉ hai chuỗi, bạn chắc chắn không muốn sử dụng StringBuilder. Có một số ngưỡng trên đó chi phí hoạt động của StringBuilder thấp hơn chi phí phân bổ nhiều chuỗi.

Vì vậy, để có thêm 2-3 chuỗi, hãy sử dụng mã của DanielSmurf . Nếu không, chỉ cần sử dụng toán tử +.


1

System.String là bất biến. Khi chúng ta sửa đổi giá trị của biến chuỗi thì bộ nhớ mới được phân bổ cho giá trị mới và phân bổ bộ nhớ trước đó được giải phóng. System.StringBuilder được thiết kế để có khái niệm về một chuỗi có thể thay đổi trong đó nhiều hoạt động có thể được thực hiện mà không cần phân bổ vị trí bộ nhớ riêng cho chuỗi đã sửa đổi.


1

Giải pháp khác:

bên trong vòng lặp, sử dụng Danh sách thay vì chuỗi.

List<string> lst= new List<string>();

for(int i=0; i<100000; i++){
    ...........
    lst.Add(...);
}
return String.Join("", lst.ToArray());;

nó rất rất nhanh



0

Nó sẽ phụ thuộc vào mã. StringBuilder thường hiệu quả hơn, nhưng nếu bạn chỉ nối một vài chuỗi và thực hiện tất cả trong một dòng, tối ưu hóa mã có thể sẽ chăm sóc nó cho bạn. Điều quan trọng là phải suy nghĩ về cách mã trông như thế nào: đối với các tập lớn hơn StringBuilder sẽ giúp dễ đọc hơn, đối với các tập nhỏ, StringBuilder sẽ chỉ thêm lộn xộn không cần thiết.

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.