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

jim*_*mij 270 .net c# string optimization

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

Lee*_*Lee 264

.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,他在其中描述了+以详细方式对一行连接执行的优化.


The*_*tan 148

StringBuilder.Append()方法比使用+运算符要好得多.但我发现,当执行1000个或更少的连接时,+效率甚至更高String.Join().

StringBuilder sb = new StringBuilder();
sb.Append(someString);
Run Code Online (Sandbox Code Playgroud)

唯一的问题StringBuilder是您必须使用公共分隔符连接字符串.(编辑:)正如@ryanversaw指出的那样,你可以制作分隔符string.Empty.

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

  • 对于单行连接,情况并非如此.假设您执行myString ="foo"+ var1 +"bar"+ var2 +"hello"+ var3 +"world",编译器会自动将其转换为string.concat调用,这与它获得的效率一样高.这个答案是不正确的,有很多更好的答案可供选择 (31认同)
  • `StringBuilder`具有相当大的可比启动成本,只有在使用非常大的字符串或非常多的连接时才有效.找出任何特定情况并非易事.如果性能有问题,分析是你的朋友(检查ANTS). (10认同)
  • 为什么你没有提到`string.Concat`? (5认同)
  • 对于琐碎的字符串连接,请使用最易读的字符串。字符串a = b + c + d; 几乎总是比使用StringBuilder更快,但是区别通常无关紧要。重复添加到同一字符串(例如,建立报告)或处理大字符串时,请使用StringBuilder(或您选择的其他选项)。 (2认同)
  • 这对 2021 年也有帮助。:) (2认同)

Mr_*_*een 78

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

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

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

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

string.Join()vs string.Concat()

这里的string.Concat方法相当于带有空分隔符的string.Join方法调用.附加一个空字符串很快,但不这样做更快,所以string.Concat方法在这里会更好.

  • 应该读它已经证明string.Concat()或+是最好的方法.是的我可以从文章中得到这个,但它只省了一次.所以,+和concat编译成相同的代码. (2认同)

pal*_*rse 54

来自Chinh Do - StringBuilder并不总是更快:

经验法则

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

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

  • 从多个字符串文字构建一个大字符串时,请使用@ string literal或inline +运算符.

大多数时候StringBuilder是你最好的选择,但有一些案例如该帖子所示,你至少应该考虑每种情况.

  • afaik @只关闭转义序列处理.http://msdn.microsoft.com/en-us/library/362314fe.aspx同意 (8认同)

Ada*_*m V 11

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

然而,Rico Mariani(.NET优化大师)做了一个测验,他在最后说,在大多数情况下,他推荐String.Format.

  • string.Format在任何情况下都不是最快的方法.我不知道如何设计一个案例. (2认同)

Gle*_*den 8

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

提高性能有两个关键因素; 首先是预先计算所需的确切总大小.当输入是如此处所示的数组时,此步骤是微不足道的.为了处理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);
Run Code Online (Sandbox Code Playgroud)

我应该提一下,这段代码与我自己使用的内容略有不同.在原文中,我从C#调用cpblk IL指令进行实际复制.为了简化和在这里的代码的可移植性,我用P/Invoke 代替了,正如你所看到的.为了在x64上获得最高性能(但可能不是x86),您可能需要使用cpblk方法.memcpy

  • @Servy我使用[this test harness](https://pastebin.com/28x747Hw)比较了`String.Join`与我的代码的性能.对于每个1000个单词大小的字符串的1000万个随机连接操作,上面显示的代码比使用**.NET 4.7**的**x64**发布版本上的`String.Join`快一致34%.由于OP明确要求"最有效"的方法,结果表明我的答案适用.如果这解决了您的疑虑,我邀请您重新考虑您的downvote. (5认同)
  • 我最近在 x64 full CLR 4.7.1 上对此进行了基准测试,发现它的速度大约是 string.Join 的两倍。使用 cpblk 或 时,分配的内存减少了约 25% (https://i.imgur.com/SxIpEmL.png) https://github.com/JonHanna/Mnemosyne (2认同)

Joh*_*dol 6

从这篇MSDN文章:

在时间和内存中创建StringBuilder对象都会产生一些开销.在具有快速内存的机器上,如果您正在执行大约五个操作,则StringBuilder变得值得.根据经验,我会说10个或更多字符串操作是任何机器上的开销的理由,即使是较慢的.

因此,如果您信任MSDN,请使用StringBuilder,如果您必须执行超过10个字符串操作/连接 - 否则使用'+'的简单字符串连接就可以了.


小智 6

尝试这 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();
    }
Run Code Online (Sandbox Code Playgroud)

VS

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

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

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

干杯。


tal*_*les 5

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

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

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


DBN*_*DBN 5

添加到其他答案,请记住,StringBuilder可以被告知要分配的初始内存量.

所述容量参数定义可被存储在由当前实例所分配的存储器的字符的最大数目.其值分配给Capacity属性.如果要存储在当前实例中的字符数超过此容量值,则StringBuilder对象会分配额外的内存来存储它们.

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

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

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

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


小智 5

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

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

string afterConcate = $"{str1}{str2}";
Run Code Online (Sandbox Code Playgroud)

字符串插值

  • 作为一种通用的连接方法,这实际上是令人惊讶的好。它基本上是“String.Format”,但更具可读性且更易于使用。对它进行基准测试,它在一行连接时比 `+` 和 `String.Concat` 稍慢,但在重复调用时比这两者要好得多,从而减少了 `StringBuilder` 的必要性。 (3认同)
  • 在底层,字符串插值使用 String.Format,而在底层 String.Format 使用缓存的 StringBuilder。字符串插值是对 C#6 语言的一个重要补充。 (2认同)