连接字符串的最有效方法?

Most efficient way to concatenate strings?

提问人:jimmij 提问时间:8/22/2008 最后编辑:Theodor Zouliasjimmij 更新时间:11/20/2022 访问量:265724

问:

连接字符串的最有效方法是什么?

C# .NET 字符串 性能 优化

评论

11赞 usr 6/23/2016
我想在这里提出一个突出的警告,即公认的答案非常不完整,因为它没有讨论所有相关案例。
1赞 Tamir Vered 1/26/2017
@usr确实......可以在此处找到有关用例的更多详细信息。StringBuilder
1赞 u8it 9/23/2017
从 C# 6 开始,我的新宠是 $“Constant text here {foo} and {bar}”...这就像服用类固醇一样。从性能上讲,在一行中比 和 慢一点,但在多次调用时比那些要好得多,尽管比 慢。实际上,性能差异是如此之大,以至于如果我只能选择一种连接方式,我会选择字符串插值,使用...如果有两种方式,那么添加到我的工具箱中。有了这两种方式,你就准备好了。String.Format+String.ConcatStringBuilder$StringBuilder
0赞 u8it 9/23/2017
下面的答案并不公正,实际上,这是一种连接字符串的糟糕方法,但它的性能速度令人惊讶。答案为什么很有趣。 两者都可以作用于数组,但实际上更快。显然,它比 非常复杂且更优化,部分原因是它的操作类似于它首先计算字符串长度,然后使用 UnSafeCharBuffer 构造受益于此知识的字符串。String.Join+String.ConcatString.JoinString.JoinString.JoinString.ConcatStringBuilder
0赞 u8it 9/23/2017
好的,所以它很快,但也需要构造一个似乎资源效率低下的数组?...事实证明,无论如何都要为他们的组成部分构建数组。因此,手动创建数组并将其馈送到数组相对较快......然而,它仍然在几乎所有实用方面都表现出色,而在长弦上只是稍微慢一点,速度要快得多......更不用说如果你必须当场为它创建一个数组,使用起来会很尴尬和丑陋。String.Join+String.ConcatString.JoinStringBuilderString.Join$String.Join

答:

2赞 TheSmurf 8/22/2008 #1

最有效的方法是使用 StringBuilder,如下所示:

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

@jonezy:如果你有几个小东西,String.Concat 很好。但是,如果你连接了兆字节的数据,你的程序可能会崩溃。

172赞 TheEmirOfGroofunkistan 8/22/2008 #2

该方法比使用运算符要好得多。但我发现,当执行 1000 个或更少的串联时,它甚至比 .StringBuilder.Append()+String.Join()StringBuilder

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

唯一的问题是您必须使用通用分隔符连接字符串。String.Join

编辑:正如@ryanversaw指出的,您可以制作 分隔符 .string.Empty

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

评论

18赞 Abel 11/4/2009
StringBuilder具有巨大的可比启动成本,只有在与非常大的字符串或非常多的串联一起使用时才有效。找出任何给定的情况都不是微不足道的。如果性能有问题,分析是你的朋友(检查 ANTS)。
39赞 csauve 1/13/2011
对于单行串联,情况并非如此。假设你执行 myString = “foo” + var1 + “bar” + var2 + “hello” + var3 + “world”,编译器会自动将其转换为 string.concat 调用,这是尽可能高效的。这个答案是不正确的,有很多更好的答案可供选择
2赞 Swanny 6/26/2011
对于琐碎的字符串连接,请使用最易读的内容。字符串 a = b + c + d;几乎总是比使用 StringBuilder 更快,但差异通常无关紧要。在重复添加到同一字符串(例如,构建报表)或处理大字符串时,请使用 StringBuilder(或您选择的其他选项)。
6赞 Venemo 9/27/2013
你为什么没有提到?string.Concat
3赞 vip007 5/14/2021
这在 2021 年也有帮助。:)
1赞 Nick 8/22/2008 #3

对于两个字符串,您肯定不想使用 StringBuilder。存在某个阈值,超过该阈值,StringBuilder 开销小于分配多个字符串的开销。

因此,对于超过 2-3 个字符串,请使用 DannySmurf 的代码。否则,只需使用 + 运算符。

68赞 palehorse 8/22/2008 #4

Chinh Do - StringBuilder 并不总是更快

经验法则

  • 连接三个或更少的动态字符串值时,请使用传统的字符串连接。

  • 连接三个以上的动态字符串值时,请使用 .StringBuilder

  • 从多个字符串文本生成大字符串时,请使用字符串文本或内联 + 运算符。@

大多数时候是你最好的选择,但有些情况如那篇文章所示,你至少应该考虑每种情况。StringBuilder

评论

9赞 abatishchev 7/19/2010
afaik @ 仅关闭转义序列处理。msdn.microsoft.com/en-us/library/362314fe.aspx 同意
0赞 Jon Dewees 8/22/2008 #5

这取决于代码。 StringBuilder 通常更有效率,但如果您只连接几个字符串并在一行中完成所有操作,则代码优化可能会为您处理它。考虑代码的外观也很重要:对于较大的集合,StringBuilder 将使其更易于阅读,对于较小的集合,StringBuilder 只会添加不必要的混乱。

13赞 Adam V 8/22/2008 #6

如果你在一个循环中操作,可能是要走的路;它为您节省了定期创建新字符串的开销。但是,在代码中,只运行一次可能没问题。StringBuilderString.Concat

然而,Rico Mariani(.NET 优化大师)编造了一个测验,他在最后表示,在大多数情况下,他建议 .String.Format

评论

0赞 Scott Lawrence 11/15/2008
多年来,我一直向与我共事过的人推荐使用 string.format 而不是 string + string。我认为可读性优势是性能优势之外的额外优势。
1赞 csauve 1/13/2011
这是实际的正确答案。目前接受的 StringBuilder 答案是不正确的,因为它没有提到单行附加 string.concat 或 + 更快。鲜为人知的事实是,编译器实际上将 + 转换为 string.concat。此外,对于循环或多行连接,我使用自定义构建的字符串构建器,该构建器仅在 .ToString 被调用 - 克服了 StringBuilder 的不确定缓冲区问题
2赞 usr 6/23/2016
字符串。在任何情况下,格式都不是最快的方法。我不知道如何设计一个它领先的情况。
0赞 Adam V 4/30/2018
@usr - 请注意,Rico 并没有明确地说它是最快的,只是说这是他的建议:“尽管它是性能最差的,而且我们提前知道了这一点,但你们的两位 CLR 性能架构师都同意 [字符串。Format] 应该是默认选项。在极不可能的情况下,如果它成为性能问题,只需进行适度的局部更改即可轻松解决该问题。通常,你只是在兑现一些不错的可维护性。
0赞 usr 5/9/2018
@AdamV问题是关于最快的方法。我不赞成它是默认选择,尽管不是出于性能原因。它可能是笨拙的语法。Resharper可以随意来回转换。
291赞 Lee 8/22/2008 #7

.NET 性能专家 Rico Mariani 有一篇关于这个主题的文章。这并不像人们想象的那么简单。基本建议是这样的:

如果您的模式如下所示:

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

这是一个concat,它很活泼,StringBuilder 可能无济于事。

如果您的模式如下所示:

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

那么你可能想要 StringBuilder。

另一篇支持这一说法的文章来自 Eric Lippert,他详细描述了对一行连接执行的优化。+

评论

1赞 IronSlug 7/22/2015
String.Format() 呢?
8赞 JohnIdol 10/3/2008 #8

从这篇 MSDN 文章中

有一些开销与 创建一个 StringBuilder 对象,两者 在时间和记忆中。在具有 快速内存,StringBuilder 成为 如果你正在做大约五个,这是值得的 操作。根据经验,我 会说 10 个或更多字符串操作 是开销的理由 任何机器,即使是较慢的机器。

因此,如果您信任 MSDN,如果您必须执行 10 个以上的字符串操作/连接,请使用 StringBuilder - 否则使用带有“+”的简单字符串连接就可以了。

1赞 Liran 5/31/2011 #9

这实际上取决于您的使用模式。 字符串之间的详细基准。Join、string、Concat 和 string。格式可以在这里找到: String.Format 不适合密集日志记录

(这其实和我给这个问题的答案是一样的)

101赞 Mr_Green 9/4/2012 #10

有 6 种类型的字符串连接:

  1. 使用加号 () 符号。+
  2. 用。string.Concat()
  3. 用。string.Join()
  4. 用。string.Format()
  5. 用。string.Append()
  6. 用。StringBuilder

在实验中,已经证明,如果单词小于 1000(大约),并且如果单词超过 1000,则应使用,这是最好的方法。string.Concat()StringBuilder

有关更多信息,请查看此网站

字符串。Join() 与字符串。Concat()

字符串。这里的 Concat 方法等效于字符串。使用空分隔符联接方法调用。追加空字符串的速度很快,但不这样做会更快,所以字符串。Concat 方法在这里会更胜一筹。

评论

10赞 brumScouse 8/7/2015
应该读取它已被证明是字符串。Concat() 或 + 是最好的方法。是的,我可以从文章中得到这个,但它为我节省了一次点击。因此,+ 和 concat 编译成相同的代码。
0赞 Gnemlock 11/9/2018
我利用这个基础来尝试使我的方法更有效率,我只需要将 3 个字符串连接起来。我发现这实际上比 快了 3 毫秒,尽管我没有研究过在出局之前所需的字符串数量。+string.Concat()string.Concat()+
1赞 John Stock 11/26/2020
7. 字符串插值。
1赞 Lauren Van Sloun 7/30/2022
5. 没有 .string.Append()
5赞 talles 10/15/2013 #11

同样重要的是要指出,如果要连接字符串文字,则应使用运算符。+

使用 + 运算符连接字符串文本或字符串常量时,编译器将创建单个字符串。不发生运行时串联。

如何:连接多个字符串(C# 编程指南)

2赞 Dhibi_Mohanned 4/24/2014 #12

System.String 是不可变的。当我们修改字符串变量的值时,会为新值分配一个新内存,并释放先前的内存分配。System.StringBuilder 被设计为具有可变字符串的概念,在该字符串中可以执行各种操作,而无需为修改后的字符串分配单独的内存位置。

评论

0赞 supi 8/30/2020
这并不完全正确。一旦字符串大小超过 StringBuilder 类的默认容量,将分配单独的内存位置。如果您可以预先预测字符串的最大大小,则可以通过预先设置适当的容量来避免内存分配。learn.microsoft.com/en-us/dotnet/api/......
5赞 DBN 11/1/2014 #13

除了其他答案之外,请记住,可以告诉 StringBuilder 要分配的初始内存量

capacity 参数定义当前实例分配的内存中可以存储的最大字符数。其值分配给 Capacity 属性。如果要在当前实例中存储的字符数超过此容量值,则 StringBuilder 对象将分配额外的内存来存储这些字符。

如果容量为零,则使用特定于实现的默认容量

重复追加到尚未预先分配的 StringBuilder 可能会导致大量不必要的分配,就像重复连接常规字符串一样。

如果您知道最终字符串的长度,可以简单地计算它,或者可以对常见情况进行有根据的猜测(分配太多不一定是坏事),则应将此信息提供给构造函数或 Capacity 属性。特别是在运行性能测试以将 StringBuilder 与 String.Concat 等其他方法进行比较时,这些方法在内部执行相同的操作。您在网上看到的任何测试,如果其比较中不包括 StringBuilder 预分配,都是错误的。

如果你不能对大小做出任何猜测,你可能正在编写一个实用函数,它应该有自己的可选参数来控制预分配。

6赞 Eduardo Mass 1/2/2016 #14

试试这 2 段代码,你会找到解决方案。

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

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

您会发现第一个代码将很快结束,并且内存将大量。

第二个代码也许内存还可以,但需要更长的时间......更长的时间。 因此,如果您有一个面向大量用户的应用程序并且需要速度,请使用第一个。如果你有一个短期单用户应用程序的应用程序,也许你可以同时使用这两个应用程序,或者第二个对开发人员来说会更“自然”。

干杯。

5赞 RP Nainwal 3/28/2017 #15

下面可能是连接多个字符串的另一种替代解决方案。

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

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

字符串插值

评论

3赞 u8it 9/23/2017
这实际上令人惊讶地好,作为一种通用的串联方法。它基本上更具可读性,更易于使用。对它进行基准标记,它比一行连接略慢,但比重复调用的两个行要好得多,因此不需要。String.Format+String.ConcatStringBuilder
2赞 Matt Parkins 7/13/2020
在后台,字符串插值使用 String.Format,而在后台,String.Format 使用缓存的 StringBuilder。字符串插值是对 C#6 语言的一个很好的补充。
12赞 Glenn Slayden 11/11/2017 #16

这是我十多年来为我的大型 NLP 应用程序发展起来的最快方法。我有 和其他输入类型的变体,有和没有不同类型的分隔符 (, ),但在这里我展示了将数组中的所有字符串连接成一个字符串的简单情况,没有分隔符。此处的最新版本是在 C# 7 和 .NET 4.7 上开发和单元测试的。IEnumerable<T>CharString

提高性能有两个关键;首先是预先计算所需的确切总大小。当输入是数组时,此步骤是微不足道的,如下所示。相反,为了进行处理,首先将字符串收集到一个临时数组中以计算该总数(该数组需要避免对每个元素调用多次,因为从技术上讲,考虑到副作用的可能性,这样做可能会改变“字符串连接”操作的预期语义)。IEnumerable<T>ToString()

接下来,给定最终字符串的总分配大小,通过就地构建结果字符串可以最大程度地提高性能。这样做需要一种(也许是有争议的)技术,即暂时暂停最初分配为零的新事物的不可变性。然而,撇开任何此类争议不谈......String

...请注意,这是本页上唯一的批量连接解决方案,它完全避免了构造函数的额外分配和复制String

完整代码:

/// <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);

我应该提一下,这段代码与我自己使用的代码略有不同。在原版中,我从 C# 调用 cpblk IL 指令来执行实际复制。为了简化此处代码的可移植性,我将其替换为 P/Invoke,如您所见。为了在 x64(但可能不是 x86)上实现最高性能,可能需要改用 cpblk 方法。memcpy

评论

0赞 Servy 11/11/2017
string.Join所有这些事情都已经为你做了。没有必要自己写。它计算最终字符串的大小,构造该大小的字符串,然后写出到基础字符数组。它甚至具有在此过程中使用可读变量名称的好处。
1赞 Glenn Slayden 11/11/2017
@Servy 感谢您的评论;确实可以很高效。正如我在简介中所暗示的,此处的代码只是我用于在早期版本的 .NET 中无法处理(例如针对分隔符进行优化)或未处理的方案的一系列函数的最简单说明。我想我不应该选择这个作为最简单的例子,因为这是一个已经处理得很好的情况,尽管处理真空分离器的“效率低下”,可能无法衡量,即。String.JoinString.JoinCharString.JoinString.Empty
0赞 Servy 11/11/2017
当然,如果您没有分隔符,那么您应该调用 ,它也可以正确地执行此操作。无论哪种方式,您都不需要自己编写代码。Concat
8赞 Glenn Slayden 11/12/2017
@Servy,我使用此测试工具比较了代码的性能。对于 1000 万次随机串联操作(每个操作最多 100 个单词大小的字符串),上面显示的代码始终比使用 .NET 4.7x64 发布版本快 34%。由于 OP 明确要求使用“最有效”的方法,因此结果表明我的答案适用。如果这解决了您的担忧,我邀请您重新考虑您的反对票。String.JoinString.Join
2赞 quentin-starin 4/23/2018
我最近在 x64 完整 CLR 4.7.1 上对此进行了基准测试,发现它的速度大约是字符串的两倍。使用 cpblk 或 github.com/JonHanna/Mnemosyne 时,分配的内存减少约 25% (i.imgur.com/SxIpEmL.png
3赞 asady 1/19/2018 #17

另一种解决方案:

在循环中,使用 List 而不是字符串。

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

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

它非常非常快。

3赞 bertasoft 11/20/2022 #18

我已经测试了本页中的所有方法,最后我开发了速度最快、内存成本更低的解决方案。

注意:在 Framework 4.8 中测试

enter image description here

 [MemoryDiagnoser]
public class StringConcatSimple
{
    private string
        title = "Mr.", firstName = "David", middleName = "Patrick", lastName = "Callan";

    [Benchmark]
    public string FastConcat()
    {
        return FastConcat(
            title, " ", 
            firstName, " ",
            middleName, " ", 
            lastName);
    }

    [Benchmark]
    public string StringBuilder()
    {
        var stringBuilder =
            new StringBuilder();

        return stringBuilder
            .Append(title).Append(' ')
            .Append(firstName).Append(' ')
            .Append(middleName).Append(' ')
            .Append(lastName).ToString();
    }

    [Benchmark]
    public string StringBuilderExact24()
    {
        var stringBuilder =
            new StringBuilder(24);

        return stringBuilder
            .Append(title).Append(' ')
            .Append(firstName).Append(' ')
            .Append(middleName).Append(' ')
            .Append(lastName).ToString();
    }

    [Benchmark]
    public string StringBuilderEstimate100()
    {
        var stringBuilder =
            new StringBuilder(100);

        return stringBuilder
            .Append(title).Append(' ')
            .Append(firstName).Append(' ')
            .Append(middleName).Append(' ')
            .Append(lastName).ToString();
    }

    [Benchmark]
    public string StringPlus()
    {
        return title + ' ' + firstName + ' ' +
            middleName + ' ' + lastName;
    }

    [Benchmark]
    public string StringFormat()
    {
        return string.Format("{0} {1} {2} {3}",
            title, firstName, middleName, lastName);
    }

    [Benchmark]
    public string StringInterpolation()
    {
        return
        $"{title} {firstName} {middleName} {lastName}";
    }

    [Benchmark]
    public string StringJoin()
    {
        return string.Join(" ", title, firstName,
            middleName, lastName);
    }

    [Benchmark]
    public string StringConcat()
    {
        return string.
            Concat(new String[]
            { title, " ", firstName, " ",
                middleName, " ", lastName });
    }
}

是的,它使用不安全

public static unsafe string FastConcat(string str1, string str2, string str3, string str4, string str5, string str6, string str7)
    {
        var capacity = 0;

        var str1Length = 0;
        var str2Length = 0;
        var str3Length = 0;
        var str4Length = 0;
        var str5Length = 0;
        var str6Length = 0;
        var str7Length = 0;

        if (str1 != null)
        {
            str1Length = str1.Length;
            capacity = str1Length;
        }

        if (str2 != null)
        {
            str2Length = str2.Length;
            capacity += str2Length;
        }

        if (str3 != null)
        {
            str3Length = str3.Length;
            capacity += str3Length;
        }

        if (str4 != null)
        {
            str4Length = str4.Length;
            capacity += str4Length;
        }

        if (str5 != null)
        {
            str5Length = str5.Length;
            capacity += str5Length;
        }

        if (str6 != null)
        {
            str6Length = str6.Length;
            capacity += str6Length;
        }

        if (str7 != null)
        {
            str7Length = str7.Length;
            capacity += str7Length;
        }


        string result = new string(' ', capacity);

        fixed (char* dest = result)
        {
            var x = dest;

            if (str1Length > 0)
            {
                fixed (char* src = str1)
                {
                    Unsafe.CopyBlock(x, src, (uint)str1Length * 2); 
                    x += str1Length;
                }
            }

            if (str2Length > 0)
            {
                fixed (char* src = str2)
                {
                    Unsafe.CopyBlock(x, src, (uint)str2Length * 2);
                    x += str2Length;
                }
            }

            if (str3Length > 0)
            {
                fixed (char* src = str3)
                {
                    Unsafe.CopyBlock(x, src, (uint)str3Length * 2);
                    x += str3Length;
                }
            }

            if (str4Length > 0)
            {
                fixed (char* src = str4)
                {
                    Unsafe.CopyBlock(x, src, (uint)str4Length * 2);
                    x += str4Length;
                }
            }

            if (str5Length > 0)
            {
                fixed (char* src = str5)
                {
                    Unsafe.CopyBlock(x, src, (uint)str5Length * 2);
                    x += str5Length;
                }
            }

            if (str6Length > 0)
            {
                fixed (char* src = str6)
                {
                    Unsafe.CopyBlock(x, src, (uint)str6Length * 2);
                    x += str6Length;
                }
            }

            if (str7Length > 0)
            {
                fixed (char* src = str7)
                {
                    Unsafe.CopyBlock(x, src, (uint)str7Length * 2);
                }
            }
        }

        return result;
    }

您可以编辑该方法并使其适应您的情况。例如,你可以把它做成这样

公共静态不安全字符串 FastConcat(string str1, string str2, string str3 = null, string str4 = null, string str5 = null, string str6 = null, string str7 = null)

评论

1赞 steven01804 12/8/2022
我还想为以后阅读本文的任何人补充一点,我在 .Net 6 和 .Net 7 中对其进行了测试,对于两者,不安全的解决方案仍然是最快的,String.Join 在速度方面排名第二。但是字符串插值的内存分配与不安全 (72 B) 相同。
0赞 Erik de Roos 5/25/2023
该问题未指定 .Net 框架版本。所以我在想......使用利用 span 和 spanaction 的 String.Create() 不会创建一个与不安全版本具有相同特征的 FastConcat 安全版本吗?
0赞 Paul Carlton 6/17/2023
你@ErikdeRoos尝试过这个想法并对其进行基准测试的?