关于c#:连接字符串的最有效方法?

Most efficient way to concatenate strings?

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


.NET性能专家RicoMariani就这个问题发表了一篇文章。这并不像人们想象的那么简单。基本建议如下:

If your pattern looks like:

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

that's one concat and it's zippy, StringBuilder probably won't help.

If your pattern looks like:

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

then you probably want StringBuilder.

另一篇支持此声明的文章来自EricLippert,他详细描述了在一行+连接上执行的优化。


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

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

String.Join的唯一问题是必须用一个公共分隔符连接字符串。(编辑:)正如@Ryanversaw指出的那样,可以将分隔符字符串设为.empty。

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


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

  • 使用加号(+符号。
  • 使用string.Concat()
  • 使用String.Join()
  • 使用string.Format()
  • 使用string.Append()
  • 使用StringBuilder
  • 在一个实验中,已经证明,如果单词小于1000(近似值),并且单词大于1000,则使用StringBuilder是最好的方法。

    有关详细信息,请访问此网站。

    string.Join() vs string.Concat()

    The string.Concat method here is equivalent to the string.Join method invocation with an empty separator. Appending an empty string is fast, but not doing so is even faster, so the string.Concat method would be superior here.


    从chinh do-stringbuilder开始,速度并不总是更快:

    经验法则

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

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

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

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


    如果您在一个循环中操作,那么StringBuilder可能是一种方法;它可以节省定期创建新字符串的开销。不过,在只运行一次的代码中,string.concat可能很好。

    然而,RicoMariani(.net optimization guru)编写了一个测验,在最后他说,在大多数情况下,他推荐string.format。


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

    提高性能有两个关键点;第一个是预先计算所需的确切总大小。当输入是一个数组时,这个步骤很简单,如图所示。对于处理IEnumerable,首先需要将字符串收集到一个临时数组中,以计算总数(该数组需要避免对每个元素多次调用ToString(),因为在技术上,考虑到副作用的可能性,这样做可能会更改"字符串连接"操作的预期语义)。

    接下来,考虑到最终字符串的总分配大小,通过在适当的位置构建结果字符串,可以获得最大的性能提升。这样做需要(可能有争议)暂时中止新String的不可变性的技术,该新String最初被分配满零。然而,除了这些争论…

    ...note that this is the only bulk-concatenation solution on this page which entirely avoids an extra round of allocation and copying by the String constructor.

    完整代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    /// <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 memcpy,如您所见。为了在x64(但可能不是x86)上获得最高性能,您可能需要使用cpblk方法。


    来自此msdn文章:

    There is some overhead associated with
    creating a StringBuilder object, both
    in time and memory. On a machine with
    fast memory, a StringBuilder becomes
    worthwhile if you're doing about five
    operations. As a rule of thumb, I
    would say 10 or more string operations
    is a justification for the overhead on
    any machine, even a slower one.

    因此,如果您信任msdn,那么如果必须执行10个以上的字符串操作/连接,请使用StringBuilder—否则,使用"+"的简单字符串concat就可以了。


    另外,请记住,可以告诉StringBuilder分配的初始内存量。

    The capacity parameter defines the maximum number of characters that can be stored in the memory allocated by the current instance. Its value is assigned to the Capacity property. If the number of characters to be stored in the current instance exceeds this capacity value, the StringBuilder object allocates additional memory to store them.

    If capacity is zero, the implementation-specific default capacity is used.

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

    如果您知道最后一个字符串的长度,可以对其进行琐碎的计算,或者对常见情况进行有根据的猜测(分配太多并不一定是一件坏事),那么您应该向构造函数或容量属性提供此信息。尤其是在运行性能测试以将StringBuilder与其他方法(如String.Concat)进行比较时,这些方法在内部执行相同的操作。您在网上看到的任何测试,如果在比较中不包括StringBuilder预分配,都是错误的。

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


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

    When you concatenate string literals or string constants by using the + operator, the compiler creates a single string. No run time concatenation occurs.

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


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

    1
    2
    3
    4
    String str1 ="sometext";
    string str2 ="some other text";

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

    字符串插值


    最有效的方法是使用StringBuilder,比如:

    1
    2
    3
    4
    5
    StringBuilder sb = new StringBuilder();
    sb.Append("string1");
    sb.Append("string2");
    ...etc...
    String strResult = sb.ToString();

    @琼西:弦。如果你有一些小东西的话,海螺很好。但是,如果您要连接兆字节的数据,您的程序可能会存储。


    尝试这两段代码,您将找到解决方案。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    static void Main(string[] args)
        {
            string s ="";
            for (int i = 0; i < 10000000; i++)
            {
                s += i.ToString();
            }
            Console.Write("End");
            Console.Read();
        }

    您会发现第一个代码结束得非常快,内存也会很大。

    第二个代码可能是内存正常,但需要更长的时间…更长的时间。因此,如果你有一个为很多用户准备的应用程序,并且你需要速度,那么使用第一个。如果你有一个短期的单用户应用程序,也许你可以同时使用两个应用程序,或者第二个应用程序对开发人员来说更"自然"。

    干杯。


    另一个解决方案:

    在循环内部,使用list而不是string。

    1
    2
    3
    4
    5
    6
    7
    List<string> lst= new List<string>();

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

    它非常快。


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


    这取决于您的使用模式。在string.join、string、concat和string.format之间有一个详细的基准。可以在这里找到:string.format不适用于密集的日志记录。

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


    这取决于代码。一般来说,StringBuilder效率更高,但是如果您只连接几个字符串,并将它们放在一行中,那么代码优化可能会为您处理这些字符串。思考代码的外观也很重要:对于较大的集合,StringBuilder将使其更易于阅读,对于较小的集合,StringBuilder只会增加不必要的混乱。


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

    因此,对于更多的2-3个字符串,使用dannysmurf的代码。否则,只需使用+运算符。