技術(shù)頻道導(dǎo)航
HTML/CSS
.NET技術(shù)
IIS技術(shù)
PHP技術(shù)
Js/JQuery
Photoshop
Fireworks
服務(wù)器技術(shù)
操作系統(tǒng)
網(wǎng)站運(yùn)營(yíng)

贊助商

分類目錄

贊助商

最新文章

搜索

C#連接字符串6種方法速度比較:StringBuilder/Format/Join/Concat等

作者:admin    時(shí)間:2023-5-14 1:46:55    瀏覽:

C#連接字符串,我們通常簡(jiǎn)單地用加號(hào)“+”來實(shí)現(xiàn),這當(dāng)然是C#連接字符串的一種方法。不過,C#連接字符串的方法有很多種,本文將介紹C#連接字符串的6種方法,并通過實(shí)例比較它們性能的優(yōu)劣,比較它們速度的快慢。

  • 使用加號(hào)“+”,包括“+=”
  • String.Concat
  • String.Join
  • StringBuilder
  • String.Format
  • 使用字符串插值(例如 $”My string {variable}”)。

我想現(xiàn)在每個(gè)人都知道使用 “+” 來連接大字符串(據(jù)說)是不行的。但這讓我開始思考性能影響到底是什么?如果你有兩個(gè)要連接的字符串,是否值得啟動(dòng)一個(gè) StringBuilder 實(shí)例?為此,我想做一些基準(zhǔn)測(cè)試,比較一下C#連接字符串的各種方法的性能。

我使用帶有 .NET Core SDK的 AMD Ryzen CPU作為我的運(yùn)行時(shí)。詳細(xì)信息在這里:

BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18362
AMD Ryzen 7 2700X, 1 CPU, 16 logical and 8 physical cores
.NET Core SDK=3.1.100
  [Host]     : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT
  DefaultJob : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT

初始基準(zhǔn)

我將使用 BenchmarkDotNet  來完成我的基準(zhǔn)測(cè)試,BenchmarkDotNet可幫助你將方法轉(zhuǎn)化為基準(zhǔn)、跟蹤其性能的測(cè)量實(shí)驗(yàn)。你不需要是一位經(jīng)驗(yàn)豐富的性能工程師,你可以使用簡(jiǎn)單的 API 以聲明式的方式設(shè)計(jì)非常復(fù)雜的性能實(shí)驗(yàn)。參閱文章:

對(duì)于我的基準(zhǔn)測(cè)試,我將嘗試執(zhí)行“單行連接”。我所說的“單行連接”的意思是我說了 5 個(gè)變量,我想將它們?nèi)窟B接成一個(gè)長(zhǎng)字符串,它們之間有一個(gè)空格。我不是在循環(huán)內(nèi)執(zhí)行此操作,而且我手頭有所有 5 個(gè)變量。

我的基準(zhǔn)看起來像這樣:

public class SingleLineJoin
{
    public string string1 = "a";
    public string string2 = "b";
    public string string3 = "c";
    public string string4 = "d";
    public string string5 = "e";

    [Benchmark]
    public string Interpolation()
    {
        return $"{string1} {string2} {string3} {string4} {string5}";
    }

    [Benchmark]
    public string PlusOperator()
    {
        return string1 + " " + string2 + " " + string3 + " " + string4 + " " + string5;
    }

    [Benchmark]
    public string StringConcatenate()
    {
        return string.Concat(string1, " ", string2, " ", string3, " ", string4, " ", string5);
    }

    [Benchmark]
    public string StringJoin()
    {
        return string.Join(" ", string1, string2, string3, string4, string5);
    }

    [Benchmark]
    public string StringFormat()
    {
        return string.Format("{0} {1} {2} {3} {4}", string1, string2, string3, string4, string5);
    }

    [Benchmark]
    public string StringBuilderAppend()
    {
        StringBuilder builder = new StringBuilder();
        builder.Append(string1);
        builder.Append(" ");
        builder.Append(string2);
        builder.Append(" ");
        builder.Append(string3);
        builder.Append(" ");
        builder.Append(string4);
        builder.Append(" ");
        builder.Append(string5);
        return builder.ToString();
    }
}

結(jié)果在這里:

方法 平均 錯(cuò)誤 偏差
Interpolation 98.58 ns 1.310 ns 1.225 ns
PlusOperator 98.35 ns 0.729 ns 0.646 ns
StringConcatenate 94.65 ns 0.929 ns 0.869 ns
StringJoin 78.52 ns 0.846 ns 0.750 ns
StringFormat 233.67 ns 3.262 ns 2.892 ns
StringBuilderAppend 51.13 ns 0.237 ns 0.210 ns

可以看到,InterpolationPlusOperatorConcat 大致相同。String.Join 速度很快,StringBuilder 明顯領(lǐng)先。String.Format 最慢。這里發(fā)生了什么?我們將不得不深入挖掘幕后發(fā)生的事情。

String.Format

為什么 String.Format 這么慢?原來,String.Format 也在幕后使用 StringBuilder,但它歸結(jié)為一個(gè)名為“AppendFormatHelper”的方法:

https://github.com/microsoft/referencesource/blob/master/mscorlib/system/text/stringbuilder.cs#L1322

現(xiàn)在這有點(diǎn)說得通了,因?yàn)槟惚仨氂涀。?code>String.Format 可以做這樣的事情:

String.Format("Price : {0:C2}", 14.00M); //打印 $14.00 (貨幣格式)

因此,它必須做更多的工作來嘗試格式化字符串,同時(shí)考慮到正確格式化貨幣等問題,即使檢查這些格式類型也需要一點(diǎn)額外的時(shí)間。

String.Join

String.Join 是一個(gè)有趣的代碼,因?yàn)樵谖铱磥砟缓蟠a并沒有太大意義。如果你傳入一個(gè) IEnumerable 或一個(gè)對(duì)象的參數(shù)列表,那么它只使用一個(gè) StringBuilder 而不會(huì)做太多其他事情:

https://github.com/microsoft/referencesource/blob/master/mscorlib/system/string.cs#L161

但是,如果你傳入字符串參數(shù),它會(huì)使用一個(gè)字符數(shù)組并執(zhí)行一些非常低級(jí)的操作:

https://github.com/microsoft/referencesource/blob/master/mscorlib/system/string.cs#L204

所以我馬上想到……有區(qū)別嗎?那么這個(gè)基準(zhǔn):

public class StringJoinComparison
{
    public string string1 = "a";
    public string string2 = "b";
    public string string3 = "c";
    public string string4 = "d";
    public string string5 = "e";

    public List<string> stringList;

    [GlobalSetup]
    public void Setup()
    {
        stringList = new List<string> { string1, string2, string3, string4, string5 };
    }


    [Benchmark]
    public string StringJoin()
    {
        return string.Join(" ", string1, string2, string3, string4, string5);
    }


    [Benchmark]
    public string StringJoinList()
    {
        return string.Join(" ", stringList);
    }
}

結(jié)果

方法 平均 錯(cuò)誤 偏差
StringJoin 80.32 ns 0.730 ns 0.683 ns
StringJoinList 141.16 ns 1.109 ns 1.038 ns

巨大差距。事實(shí)上,它要慢得多。

String.Concat

ConcatJoin 非常相似。例如,如果我們傳入一個(gè) IEnumerable,它會(huì)使用一個(gè) StringBuilder

https://github.com/microsoft/referencesource/blob/master/mscorlib/system/string.cs#L3145

但是如果我們傳入字符串的參數(shù)列表,它就會(huì)下降到 ConcatArray 方法:

https://github.com/microsoft/referencesource/blob/master/mscorlib/system/string.cs#L3292

你可能會(huì)開始注意到很多方法都調(diào)用了“FastAllocateString”,從用法而非我所擁有的特殊知識(shí)推斷,這似乎為字符串的完整大小分配了內(nèi)存,然后在稍后“填充”。例如,給定一個(gè)字符串列表,你已經(jīng)提前知道該字符串的大小,因此你可以預(yù)先分配該內(nèi)存,然后在稍后簡(jiǎn)單地填充字節(jié)。 

加號(hào)(+) 運(yùn)算符

加號(hào)運(yùn)算符連接字符串,它的性能如何?我寫了一個(gè)小基準(zhǔn)測(cè)試:

[MemoryDiagnoser]
public class OperatorTest
{
    public string string1 = "a";
    public string string2 = "b";
    public string string3 = "c";
    public string string4 = "d";
    public string string5 = "e";


    [Benchmark]
    public string PlusOperatorWithResult()
    {
        var result = string1 + " ";
        result += string2 + " ";
        result += string3 + " ";
        result += string4 + " ";
        result += string5 + " ";
        return result;
    }


    [Benchmark]
    public string PlusOperator()
    {
        var result = string1 + " " + string2 + " " + string3 + " " + string4 + " " + string5;
        return result;
    }
}

每個(gè)字符串連接都在它自己的行上,理論上它應(yīng)該每次都創(chuàng)建一個(gè)新字符串。

結(jié)果:

方法 平均 錯(cuò)誤 偏差
PlusOperatorWithResult 106.52 ns 0.560 ns 0.497 ns
PlusOperator 95.10 ns 1.818 ns 1.701 ns

顯然,隨著時(shí)間的推移,隨著更大的字符串和更多的連接,這可能會(huì)變得更成問題,我認(rèn)為這是人們?cè)诩饨?ldquo;一切都使用 StringBuilder!”時(shí)試圖指出的。

另請(qǐng)注意,我將 MemoryDiagnoser 添加到該基準(zhǔn)測(cè)試中以表明是的,當(dāng)您使用 += 運(yùn)算符進(jìn)行處理時(shí)會(huì)分配更多內(nèi)存,因?yàn)樗仨氃趦?nèi)存中創(chuàng)建一個(gè)全新的字符串來處理此問題。

StringBuilder

StringBuilder 的源代碼可以在這里找到:

https://github.com/microsoft/referencesource/blob/master/mscorlib/system/text/stringbuilder.cs

它相對(duì)簡(jiǎn)單,因?yàn)樗钟幸粋€(gè) char 數(shù)組直到最后一刻,然后在最后將所有內(nèi)容連接起來。它如此之快的原因是因?yàn)樵谡嬲枰澳悴粫?huì)分配字符串。

使用 StringBuilder 最讓我感到驚訝的是,即使追加 5 個(gè)(或者如果我們計(jì)算空格我猜更多),它也比僅使用 + 運(yùn)算符快得多。

在前文,我展示了 String 如何比 StringBuilder 消耗更多的資源,參閱文章:

Interpolation(插補(bǔ))

我找不到 C# 中插值的源代碼。事實(shí)上,我甚至不確定要搜索什么。因?yàn)樗愃朴诩犹?hào)運(yùn)算符,所以我假設(shè)它可能只是同一段代碼,在代碼深處連接字符串。

總結(jié)

那么,這會(huì)告訴我們什么?通過這些基準(zhǔn)測(cè)試,我們知道了 StringBuilder 是構(gòu)建字符串的最佳實(shí)踐,我們還發(fā)現(xiàn),即使在構(gòu)建較小的字符串時(shí),StringBuilder 也能完成其余的工作。這是否意味著立即重寫你的代碼以在所有地方使用 StringBuilder?我個(gè)人對(duì)此表示懷疑。僅基于可讀性,幾納秒對(duì)你來說可能不值得。

我們還可看到,即使我們沒有進(jìn)行任何特殊格式化,String.Format 的性能也非常差。事實(shí)上,我們可以使用任何其他方法將字符串連接在一起并使其更快。

最后,我們還發(fā)現(xiàn)字符串連接仍然是一個(gè)奇怪的事情,使用 String.ConcatString.Join 之類的東西,根據(jù)你傳入的內(nèi)容做不同的事情。10 次中有 9 次你可能甚至不認(rèn)為傳入 IEnumerableParams 之間存在差異,但確實(shí)存在差異。

相關(guān)文章

標(biāo)簽: CSharp  asp.net  字符串  StringBuilder  Format  Join  Concat  連接字符串  
x
  • 站長(zhǎng)推薦
/* 左側(cè)顯示文章內(nèi)容目錄 */