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

评论

我想在此处发出一个警告,即已接受的答案很不完整,因为它没有讨论所有相关案例。

@usr的确,有关StringBuilder用例的更多详细信息可以在这里找到。

从C#6开始,我的新宠是$“这里的常量文本{foo}和{bar}” ...就像类固醇上的String.Format一样。从性能的角度来看,在多次调用时,一个衬板比+和String.Concat稍慢一点,但比那些衬板要好得多,尽管比StringBuilder要慢。实际上,性能差异是如此之大,以至于如果我只需要选择一种连接方式,我将使用$ ...选择字符串插值。如果是两种方式,则将StringBuilder添加到我的工具箱中。通过这两种方式进行设置。

下面的String.Join答案并不能代表正义,实际上,这是连接字符串的一种不好的方法,但是令人惊讶的是,这是快速执行的明智之举。答案为什么很有趣。 String.Concat和String.Join都可以作用于数组,但是String.Join实际上更快。显然,String.Join比String.Concat相当复杂且经过了优化,部分原因是它与StringBuilder的运行方式相似,因为它首先计算字符串长度,然后利用UnSafeCharBuffer受益于这一知识来构造字符串。

好的,它很快,但是String.Join还需要构造一个似乎资源效率低的数组,对吧?...事实证明+和String.Concat仍然为它们的组成部分构造数组。因此,手动创建一个数组并将其馈送到String.Join相对较快...但是,StringBuilder的性能仍比String.Join差很多,而$在长字符串上仅稍慢一些,而且快得多……更不用说它了尴尬和丑陋地使用String.Join,如果必须立即为它创建一个数组。

#1 楼

StringBuilder.Append()方法比使用+运算符要好得多。但是我发现,执行1000个或更少的串联时,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 });


评论


StringBuilder具有相当可观的启动成本,只有在使用非常大的字符串或很多串联时才有效。找出任何给定的情况并非易事。如果性能有问题,则分析是您的朋友(请检查ANTS)。

–阿贝尔
09年11月4日在13:22

对于单行串联不是这样。假设您执行myString =“ foo” + var1 +“ bar” + var2 +“ hello” + var3 +“ world”,编译器会自动将其转换为string.concat调用,其效率与获得的效率一样。这个答案是不正确的,有很多更好的答案可供选择

–csauve
2011年1月12日17:56



对于琐碎的字符串连接,请使用最易读的字符串。字符串a = b + c + d;几乎总是比使用StringBuilder更快,但是区别通常是无关紧要的。重复添加到同一字符串(例如,建立报告)或处理大字符串时,请使用StringBuilder(或您选择的其他选项)。

– Swanny
2011年6月26日上午10:28

您为什么还没有提到string.Concat?

– Venemo
2013年9月27日在12:56

#2 楼

.NET Performance专家Rico Mariani在此主题上发表了一篇文章。这并不像人们所想的那么简单。基本建议是:


如果您的模式如下:

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

这是一个concat,它是zippy,可能是StringBuilder

如果您的模式看起来像:

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

那么您可能想要StringBuilder。


Eric Lippert还提供了另一篇文章来支持这种说法,他在其中详细描述了在一行+串联上执行的优化。

评论


那String.Format()呢?

– IronSlug
15年7月22日在8:55

#3 楼

字符串连接有6种类型:


使用加号(+)符号。
使用string.Concat()
使用string.Join()
使用string.Format()
使用string.Append()
使用StringBuilder

在实验中,已经证明,如果单词少于1000个(大约)且如果单词小于1000个,则string.Concat()是最好的处理方法。字数大于1000,则应使用StringBuilder

有关更多信息,请访问此网站。


string.Join()与string.Concat()

这里的string.Concat方法等效于string.Join方法调用,并带有一个空的分隔符。追加一个空字符串很快,但是没有这样做甚至更快,因此在这里string.Concat方法会更好。


评论


应该阅读已证明为string.Concat()或+的最佳方法。是的,我可以从文章中获得此信息,但它为我节省了一键。因此,+和concat编译为同一代码。

–brumScouse
2015年8月7日13:51

我以此为基础尝试使我的方法更有效,我只需要连接3个字符串即可。我发现+实际上比string.Concat()快3毫秒,尽管我没有研究string.Concat()超越+之前需要的字符串数量。

–死锁
18年9月9日在6:13

7.字符串插值。

–John Stock
11月25日21:48

#4 楼

从Chinh Do开始-StringBuilder并不总是更快:

经验法则


当连接三个或三个以下动态字符串值时,请使用传统的字符串连接。 >要连接三个以上的动态字符串值,请使用StringBuilder
从多个字符串文字中构建大字符串时,请使用@字符串文字或inline +运算符。

时间StringBuilder是您最好的选择,但是在该帖子中显示的情况下,您至少应该考虑每种情况。

评论


afaik @仅关闭转义序列处理。 msdn.microsoft.com/zh-CN/library/362314fe.aspx同意

– abatishchev
2010年7月18日在20:11

#5 楼

如果您是循环运行,则可能要走StringBuilder。它节省了您定期创建新字符串的开销。在只运行一次的代码中,String.Concat可能很好。

但是,Rico Mariani(.NET优化大师)做了一个测验,最后他说在大多数情况下,他建议String.Format

评论


多年来,我一直在向与我一起工作的人推荐在string + string上使用string.format。我认为可读性优势是性能优势之外的另一个优势。

–斯科特·劳伦斯(Scott Lawrence)
08年11月14日在18:38

这是实际的正确答案。当前接受的StringBuilder答案是错误的,因为它没有提到string.concat或+更快的单行追加。鲜为人知的事实是,编译器实际上将+转换为string.concat。另外,对于循环或多行连接,我使用了一个自定义的内置字符串生成器,该生成器仅在调用.ToString时才追加-克服StringBuilder具有的不确定缓冲区问题

–csauve
2011年1月12日17:53

在任何情况下,string.Format都不是最快的方法。我不知道该怎么做。

–usr
16年6月23日在13:55

@usr-请注意,Rico显然不是在说它是最快的,只是他的建议:“即使是性能最差的,而且我们已经事先知道很多,你们两个CLR Performance Architects都同意[string.Format]应该是默认选择。万一发生性能问题,这种情况极不可能发生,只需适度的本地更改即可轻松解决该问题。通常,您只是从一些不错的可维护性中获益。”

–亚当五世
18年4月30日在14:00

@AdamV问题是最快的方法。我不同意它是默认选择,尽管不是出于性能方面的原因。语法可能很笨拙。 Resharper可以随意来回转换。

–usr
18年5月8日在16:13

#6 楼

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

有两个提高性能的关键:首先是预先计算所需的确切总大小。当输入是一个数组时,此步骤很简单,如下所示。对于处理IEnumerable<T>而言,值得首先将字符串收集到一个临时数组中以计算该总数(要求该数组以避免对每个元素多次调用ToString(),因为从技术上讲,鉴于可能会产生副作用,这样做可能会改变

接下来,给定最终字符串的总分配大小,通过就地构建结果字符串可以获得最大的性能提升。为此,需要(可能引起争议的)技术来暂时中止新的String的不变性,该新String最初被分配为零。但是,除此类争议外,请注意...这是此页面上唯一的批量连接解决方​​案,完全避免了memcpy进行的额外分配和复制构造函数。


完整代码:

/// <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 q4312079q。为了在x64(但可能不是x86)上获得最高性能,则可能要改用cpblk方法。

评论


string.Join已经为您完成了所有这些事情。无需自己编写。它计算最终字符串的大小,构造该大小的字符串,然后写出到基础字符数组。它甚至具有在过程中使用可读变量名的好处。

–服务
17年11月10日在22:09

@Servy感谢您的评论;实际上,String.Join可以有效。正如我在介绍中所暗示的那样,此处的代码只是我在String.Join无法处理(例如针对Char分隔符进行优化)或未在.NET早期版本中处理的场景中使用的一系列函数的最简单说明。 。我想我不应该为最简单的示例选择它,因为在这种情况下,String.Join已经可以很好地处理,尽管处理空虚分隔符的“低效率”(可能是无法衡量的)也就是。空字符串

– Glenn Slayden
17年11月10日在22:22

当然,如果没有分隔符,则应调用Concat,它也可以正确执行此操作。无论哪种方式,您都不需要自己编写代码。

–服务
17年10月10日在22:25

@Servy我已经使用此测试工具将String.Join与代码的性能进行了比较。对于1000万个随机串接操作(最多100个字长的字符串),上面显示的代码始终比String快34%。在.NET 4.7的x64版本上加入Join。由于OP明确要求“最有效”的方法,因此结果表明我的答案适用。如果这能解决您的问题,请您重新考虑一下您的不赞成票。

– Glenn Slayden
17年11月12日在3:39



我最近在x64完整CLR 4.7.1上进行了基准测试,发现它的速度大约是字符串的两倍。使用cpblk或github.com/时,分配的内存减少了大约25%(i.imgur.com/SxIpEmL.png)乔恩·汉娜(JonHanna / Mnemosyne)

–quentin-starin
18-4-22在21:18



#7 楼

从这篇MSDN文章中:
创建StringBuilder对象有一些开销,在时间和内存上都与
相关。在具有
快速内存的机器上,如果您要执行大约五个
操作,则StringBuilder变得很有价值。根据经验,我说10个或更多的字符串操作
是任何机器上开销的证明,甚至是较慢的机器。


因此,如果您信任MSDN,并且必须执行10个以上的字符串操作/串联操作,请使用StringBuilder-否则,简单的带'+'的字符串concat即可。

#8 楼

还必须指出,如果要连接字符串文字,则应使用+运算符。


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


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

#9 楼

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

Capacity参数定义了可存储在由StringBuilder分配的内存中的最大字符数。当前实例。它的值分配给Capacity属性。如果要存储在当前实例中的字符数超过此容量值,则StringBuilder对象将分配额外的内存来存储它们。
如果容量为零,则使用特定于实现的默认容量。

反复添加未预先分配的StringBuilder可能会导致很多不必要的分配,就像重复连接常规字符串一样。
如果您知道最终字符串将持续多长时间,则可以对其进行微不足道的计算,或者可以对常见情况做出有根据的猜测(分配过多不一定是一件坏事),您应该将此信息提供给构造函数或Capacity属性。尤其是在运行性能测试以将StringBuilder与其他方法(例如,内部执行相同操作的String.Concat)进行比较时。您在网上看到的任何测试中在比较中不包含StringBuilder的预分配都是错误的。
如果您无法对大小进行任何猜测,则可能是在编写实用程序函数,该函数应具有自己的用于控制预分配的可选参数。

#10 楼

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

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

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


字符串插值

评论


作为一般的串联方法,这实际上令人惊讶。它基本上是String.Format,但更具可读性,更易于使用。对它进行基准测试,它在一行连接中比+和String.Concat稍慢,但比在重复调用时两者都好得多,这使得StringBuilder的必要性降低。

–u8it
17年9月22日在20:24

在幕后,字符串插值使用String.Format,在幕后,String.Format使用缓存的StringBuilder。字符串插值是C#6语言的重要补充。

–马特·帕金斯(Matt Parkins)
7月13日13:12

#11 楼

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


Vs

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


发现第一个代码会很快结束,并且存储空间会很大。

第二个代码也许可以存储,但是需要更长的时间...更长。
因此,如果您有很多用户的应用程序并且需要速度,请使用第一个。如果您有一个短期应用程序供一个用户应用程序使用,则也许您可以同时使用这两个应用程序,否则第二个应用程序对于开发人员将更加“自然”。

干杯。

#12 楼

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

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


@jonezy:String.Concat如果有一些小东西的话很好。但是,如果要串联兆字节的数据,则程序可能会崩溃。

评论


嘿,兆字节数据的解决方案是什么?

– Neel
16 Mar 10 '16 at 13:22

#13 楼

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

评论


这不是完全正确的。字符串大小超过StringBuilder类的默认容量后,将分配单独的内存位置。鉴于您可以预先预测字符串的最大大小,因此可以通过预先设置适当的容量来避免内存分配。 docs.microsoft.com/zh-CN/dotnet/api/…

– Supi
8月30日5:08

#14 楼

另一个解决方案:

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

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

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


它非常非常快。

#15 楼

对于仅两个字符串,您绝对不希望使用StringBuilder。在某个阈值之上,StringBuilder的开销小于分配多个字符串的开销。

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

#16 楼

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

(这实际上是我对这个问题的回答)

#17 楼

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