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


285

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


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

@usr确实...有关StringBuilder使用案例的更多详细信息,请参见此处
Tamir Vered

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

String.Join下面的答案没有+道理,实际上,这是连接字符串的一种不好的方法,但这出乎意料的是快速的性能明智的选择。答案为什么很有趣。String.Concat并且String.Join都可以作用于数组,但String.Join实际上更快。显然,String.Join与相比String.Concat,它是相当复杂且更优化的,部分原因是它的操作类似于StringBuilder首先计算字符串长度,然后使用UnSafeCharBuffer受益于此知识来构造字符串。
u8it

好了,所以它的速度快,但String.Join还需要建设,这似乎资源低效权的阵列?......原来,+String.Concat他们的选民结构阵列反正。因此,手动创建数组并将其馈入数组的String.Join速度相对较快...但是,在几乎所有实际方式上,它的StringBuilder性能仍然不尽人意String.Join,而$在长字符串上则稍慢一些,并且速度要快得多。更不用说String.Join如果您使用它会很尴尬和难看当场为它创建一个数组。
u8it

Answers:


154

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

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

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

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

5
你为什么没有提到string.Concat
Venemo

271

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

如果您的模式如下所示:

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

那是一连串的东西,而且它很活泼,StringBuilder可能无济于事。

如果您的模式如下所示:

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

那么您可能需要StringBuilder。

支持此主张的另一篇文章来自埃里克·利珀特(Eric Lippert),他详细描述了在一个行+串联中执行的优化。


1
那String.Format()呢?
IronSlug

85

字符串串联有6种类型:

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

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

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

string.Join()与string.Concat()

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


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

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

59

Chinh Do-StringBuilder并不总是更快

经验法则

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

  • 当串联三个以上的动态字符串值时,请使用StringBuilder

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

大部分的时间StringBuilder是你最好的选择,但也有在该职位,你至少应该考虑一下每一种情况显示情况。


8
afaik @仅关闭转义序列处理。msdn.microsoft.com/en-us/library/362314fe.aspx同意
abatishchev 2010年

12

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

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


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

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

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

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

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

10

这是十年来我为大型NLP应用程序开发的最快方法。我有变化IEnumerable<T>和其他输入类型,有和没有不同类型(的分离器CharString),但是在这里我展示的简单情况 串联阵列中的所有串为单个串,没有隔板。这里的最新版本是在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);

我应该提到的是,此代码与我自己使用的代码略有不同。在原始版本中,我从C#调用cpblk IL指令进行实际复制。为了简化代码并使其具有可移植性,如您所见,我将其替换为P / Invoke 。为了在x64(但可能不是x86)上获得最高性能,则可能要改用cpblk方法。memcpy


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

1
@Servy感谢您的评论;确实String.Join可以有效。正如我在介绍中所暗示的那样,此处的代码仅是我String.Join在无法处理(例如针对Char分隔符进行优化)或在.NET早期版本中未处理的方案中使用的一系列功能的最简单说明。我想我不应该为最简单的例子选择这种方法,因为这种情况String.Join已经很好地处理了,尽管处理空泡分离器的“效率低下”(可能是无法衡量的)也就是。String.Empty
Glenn Slayden's

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

7
@Servy我已经String.Join使用此测试工具比较了代码和代码的性能。对于高达每100个字的字符串1000点随机级联的操作,上面显示的代码是一致的速度比34%String.Join64发布版本与.NET 4.7。由于OP明确要求“最有效”的方法,因此结果表明我的答案适用。如果这能解决您的问题,我请您重新考虑您的反对意见。
Glenn Slayden

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

6

从这篇MSDN文章中

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

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



5

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

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

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

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

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

如果您无法猜测大小,则可能是在编写实用程序函数,该函数应具有自己的可选参数来控制预分配。


4

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

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

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

字符串插值


1
作为一般的串联方法,这实际上令人惊讶。基本上,String.Format但是可读性更高,使用起来也更容易。作为基准测试,它比一行连接慢一些+String.Concat但比重复调用的两个都好得多,因此StringBuilder不必要。
u8it

2

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

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

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


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

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

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

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

干杯。


1

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

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


1

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


1

另一个解决方案:

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

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

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

它非常非常快。



0

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.