【问题标题】:Most efficient way to concatenate strings?连接字符串的最有效方法?
【发布时间】:2010-09-06 11:18:26
【问题描述】:

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

【问题讨论】:

  • 我想在这里提出一个突出的警告,即接受的答案明显不完整,因为它没有讨论所有相关案例。
  • @usr 确实...更多关于StringBuilder使用案例的详细信息可以在here找到。
  • 从 C# 6 开始,我最喜欢的新内容是 $"Constant text here {foo} and {bar}"... 这就像 String.Format 使用类固醇。在性能方面,在一个班轮上比+String.Concat 慢一点,但在多次调用时比那些要好得多,尽管比StringBuilder 慢。实际上,性能差异是这样的,如果我只能选择一种连接方式,我会选择使用$ 的字符串插值...如果有两种方式,则将StringBuilder 添加到我的工具箱中。有了这两种方式,你就做好了。
  • 下面的String.Join 答案不符合+ 的正义,实际上是一种连接字符串的坏方法,但它在性能方面却出奇地快。答案为什么很有趣。 String.ConcatString.Join 都可以作用于数组,但 String.Join 实际上更快。显然,String.JoinString.Concat 更加复杂和优化,部分原因是它的操作类似于StringBuilder,因为它首先计算字符串长度,然后使用 UnSafeCharBuffer 构造受益于该知识的字符串。
  • 好的,所以它很快,但是String.Join 还需要构造一个看起来资源效率低下的数组,对吧?...结果+String.Concat 无论如何都会为它们的成分构造数组。因此,手动创建一个数组并将其提供给String.Join 会相对更快......但是,StringBuilder 在几乎所有实际方面仍然优于String.Join,而$ 在长字符串上只是稍微慢一点,而且快得多......不是提到如果你必须在现场为它创建一个数组,那么使用String.Join 会很尴尬和丑陋。

标签: c# .net string optimization


【解决方案1】:

最高效的是使用StringBuilder,像这样:

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

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

【讨论】:

    【解决方案2】:

    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)。
    • 这不适用于单行连接。假设您执行 myString = "foo" + var1 + "bar" + var2 + "hello" + var3 + "world",编译器会自动将其转换为 string.concat 调用,这是尽可能高效的。这个答案是错误的,有很多更好的答案可供选择
    • 对于琐碎的字符串连接,请使用最易读的内容。字符串 a = b + c + d;几乎总是比使用 StringBuilder 更快,但差异通常是无关紧要的。重复添加到同一个字符串(例如构建报告)或处理大字符串时,请使用 StringBuilder(或您选择的其他选项)。
    • 你为什么没有提到string.Concat
    • 这对 2021 年也有帮助。 :)
    【解决方案3】:

    对于只有两个字符串,你肯定不想使用 StringBuilder。有一个阈值,超过该阈值 StringBuilder 开销小于分配多个字符串的开销。

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

    【讨论】:

      【解决方案4】:

      来自Chinh Do - StringBuilder is not always faster

      经验法则

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

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

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

      大多数情况下 StringBuilder 是您最好的选择,但如该帖子所示,您至少应该考虑每种情况。

      【讨论】:

      【解决方案5】:

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

      【讨论】:

        【解决方案6】:

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

        不过,Rico Mariani(.NET 优化大师)made up a quiz 最后表示,在大多数情况下,他推荐String.Format

        【讨论】:

        • 多年来,我一直向与我共事的人推荐使用 string.format 而不是 string + string。我认为可读性优势是性能优势之外的额外优势。
        • 这是实际的正确答案。目前接受的 StringBuilder 答案是不正确的,因为它没有提到 string.concat 或 + 更快的单行追加。鲜为人知的事实是编译器实际上将 +'s 转换为 string.concat's。此外,对于循环或多行连接,我使用自定义构建的字符串构建器,仅在调用 .ToString 时附加 - 克服了 StringBuilder 具有的不确定缓冲区问题
        • string.Format 在任何情况下都不是最快的方法。我不知道如何设计一个成功的案例。
        • @usr - 请注意,Rico 明确并没有说它是最快的,只是他的建议:“即使它是性能最差的,而且我们提前知道了很多, 您的两位 CLR 性能架构师都同意 [string.Format] 应该是默认选择。在极不可能的情况下,它成为性能问题,只需进行适度的本地更改即可轻松解决该问题。通常您只是兑现一些不错的可维护性。”
        • @AdamV 问题是关于最快的方法。我不同意它是默认选择,但不是出于性能原因。它可能是笨拙的语法。 Resharper 可以随意来回转换。
        【解决方案7】:

        Rico Mariani,.NET 性能专家,在这个主题上有 an article。这并不像人们想象的那么简单。基本建议是这样的:

        如果你的模式看起来像:

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

        这是一个 concat,它是 zippy,StringBuilder 可能无济于事。

        如果你的模式看起来像:

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

        那么您可能需要 StringBuilder。

        Yet another article to support this claim 来自 Eric Lippert,他在其中详细描述了在一行 + 连接上执行的优化。

        【讨论】:

        • String.Format() 怎么样?
        【解决方案8】:

        来自MSDN article

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

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

        【讨论】:

          【解决方案9】:

          这真的取决于您的使用模式。 string.Join、string、Concat 和 string.Format 之间的详细基准可以在这里找到:String.Format Isn't Suitable for Intensive Logging

          (这实际上是我给this问题的答案)

          【讨论】:

            【解决方案10】:

            字符串连接有6种类型:

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

            实验证明string.Concat()是接近1000(近似)字数的最佳方法,如果字数超过1000,则应使用StringBuilder

            有关更多信息,请查看此site

            string.Join() 与 string.Concat()

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

            【讨论】:

            • 应该读到已经证明 string.Concat() 或 + 是最好的方法。是的,我可以从文章中得到这个,但它可以让我省去一键点击。所以,+ 和 concat 编译成相同的代码。
            • 我使用这个基础来尝试使我的方法更有效,我只需要连接 3 个字符串。我发现 + 实际上比 string.Concat() 快 3 毫秒,尽管在 string.Concat() 超越 + 之前我没有研究所需的字符串数量。
            • 7.字符串插值。
            【解决方案11】:

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

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

            How to: Concatenate Multiple Strings (C# Programming Guide)

            【讨论】:

              【解决方案12】:

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

              【讨论】:

              • 这并不完全正确。一旦字符串大小超过 StringBuilder 类的默认容量,将分配单独的内存位置。鉴于您可以预先预测字符串的最大大小,您可以通过预先设置适当的容量来避免内存分配。 docs.microsoft.com/en-us/dotnet/api/…
              【解决方案13】:

              除了其他答案,请记住StringBuilder can be told an initial amount of memory to allocate

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

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

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

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

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

              【讨论】:

                【解决方案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();
                    }
                

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

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

                干杯。

                【讨论】:

                  【解决方案15】:

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

                  String str1 = "sometext";
                  string str2 = "some other text";
                  
                  string afterConcate = $"{str1}{str2}";
                  

                  string interpolation

                  【讨论】:

                  • 作为一种通用的连接方法,这实际上令人惊讶。它基本上是String.Format,但更具可读性和更易于使用。对它进行基准测试,它在一行连接中比 +String.Concat 稍慢,但比在重复调用时要好得多,这使得 StringBuilder 不太必要。
                  • 在后台,字符串插值使用 String.Format,而在后台 String.Format 使用缓存的 StringBuilder。字符串插值是对 C#6 语言的一个很好的补充。
                  【解决方案16】:

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

                  提高性能有两个关键;首先是预先计算所需的确切总大小。当输入是此处所示的数组时,此步骤很简单。为了处理IEnumerable&lt;T&gt;,值得首先将字符串收集到一个临时数组中以计算总数(该数组需要避免每个元素多次调用ToString(),因为从技术上讲,考虑到副作用的可能性,做所以可能会改变“字符串连接”操作的预期语义)。

                  接下来,考虑到最终字符串的总分配大小,就地构建结果字符串可以获得最大的性能提升。这样做需要(可能有争议的)技术来暂时暂停新的String 的不变性,该新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# 中的call the cpblk IL instruction 进行实际的复制。如您所见,为了此处代码的简单性和可移植性,我将其替换为 P/Invoke memcpy。为了在 x64 (but maybe not x86) 上获得最高性能,您可能需要改用 cpblk 方法。

                  【讨论】:

                  • string.Join 已经为您完成了所有这些事情。没必要自己写。它计算最终字符串的大小,构造该大小的字符串,然后写出到底层字符数组。它甚至还有在过程中使用可读变量名的好处。
                  • @Servy 感谢您的评论;确实String.Join 可以高效。正如我在介绍中暗示的那样,这里的代码只是我用于String.Join 无法处理(例如优化Char 分隔符)或在以前版本的。网。我想我不应该选择这个作为最简单的例子,因为String.Join 已经很好地处理了这种情况,尽管处理空分隔符的“效率低下”可能无法衡量,即。 String.Empty.
                  • 当然,如果您没有分隔符,那么您应该调用Concat,这正确执行此操作。无论哪种方式,您都不需要自己编写代码。
                  • @Servy 我比较了String.Join 和我使用this test harness 的代码的性能。对于多达 100 个字大小的字符串的 1000 万次随机连接操作,上面显示的代码始终比使用 .NET 4.7x64 版本构建的 String.Join 快 34% >。由于 OP 明确要求“最有效”的方法,因此结果表明我的回答适用。如果这解决了您的疑虑,我邀请您重新考虑您的否决票。
                  • 我最近在 x64 full CLR 4.7.1 上对此进行了基准测试,发现它的速度大约是 string 的两倍。使用 cpblk 或 @987654325 时,分配的内存减少了约 25% (i.imgur.com/SxIpEmL.png) @
                  【解决方案17】:

                  另一种解决方案:

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

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

                  非常非常快。

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 2010-11-21
                    • 1970-01-01
                    • 2013-05-17
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多