String.Replace() vs. StringBuilder.Replace()

本文关键字:Replace StringBuilder vs String | 更新日期: 2023-09-27 18:00:54

我有一个字符串,其中需要用字典中的值替换标记。它必须尽可能高效。用字符串进行循环。replace只会消耗内存(记住,字符串是不可变的)。既然StringBuilder.Replace()是为处理字符串操作而设计的,那么它会更好吗?

我希望避免RegEx的费用,但如果这将是一个更有效的,那就顺其自然吧。

注意:我不关心代码的复杂性,只关心它运行的速度和消耗的内存。

平均统计数据:长度为255-1024个字符,字典中有15-30个键。

String.Replace() vs. StringBuilder.Replace()

使用以下代码使用RedGate Profiler

class Program
    {
        static string data = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz";
        static Dictionary<string, string> values;
        static void Main(string[] args)
        {
            Console.WriteLine("Data length: " + data.Length);
            values = new Dictionary<string, string>()
            {
                { "ab", "aa" },
                { "jk", "jj" },
                { "lm", "ll" },
                { "yz", "zz" },
                { "ef", "ff" },
                { "st", "uu" },
                { "op", "pp" },
                { "x", "y" }
            };
            StringReplace(data);
            StringBuilderReplace1(data);
            StringBuilderReplace2(new StringBuilder(data, data.Length * 2));
            Console.ReadKey();
        }
        private static void StringReplace(string data)
        {
            foreach(string k in values.Keys)
            {
                data = data.Replace(k, values[k]);
            }
        }
        private static void StringBuilderReplace1(string data)
        {
            StringBuilder sb = new StringBuilder(data, data.Length * 2);
            foreach (string k in values.Keys)
            {
                sb.Replace(k, values[k]);
            }
        }
        private static void StringBuilderReplace2(StringBuilder data)
        {
            foreach (string k in values.Keys)
            {
                data.Replace(k, values[k]);
            }
        }
    }
  • String.Replace=5.843ms
  • StringBuilder.Replace#1=4.059ms
  • Stringbuilder.Replace#2=0.461ms

字符串长度=1456

stringbuilder#1在方法中创建stringuilder,而#2则不创建,因此性能差异最终很可能是相同的,因为您只是将这些工作从方法中移出。如果您从字符串生成器而不是字符串开始,那么#2可能是替代方法。

就内存而言,使用RedGateMemory探查器,在进入字符串生成器将全面获胜的MANY替换操作之前,没有什么可担心的。

这可能会有所帮助:https://learn.microsoft.com/en-us/archive/blogs/debuggingtoolbox/comparing-regex-replace-string-replace-and-stringbuilder-replace-which-has-better-performance

简单的答案似乎是String.Replace更快,尽管它可能会对内存占用/垃圾收集开销产生更大的影响。

stringbuilder.replace会比String.replace更好吗?

是的,好多了。如果你能估计出新字符串的上限(看起来你能),那么它可能足够快了。

当你创建它像:

  var sb = new StringBuilder(inputString, pessimisticEstimate);

那么StringBuilder将不必重新分配其缓冲区。

是的,StringBuilder将在速度和内存方面都有所提高(基本上是因为它不会在每次操作时创建字符串的实例-StringBuilder总是使用同一对象操作)。这里有一个MSDN链接,包含一些详细信息。

将数据从字符串转换为StringBuilder并返回需要一些时间。如果只执行一次替换操作,那么StringBuilder中固有的效率提高可能无法弥补这一时间。另一方面,如果将一个字符串转换为StringBuilder,然后对其执行许多Replace操作,并在最后将其转换回来,那么StringBuilder方法会更快。

与其在整个字符串上运行15-30个替换操作,不如使用类似trie数据结构的东西来保存字典。然后,您可以在输入字符串中循环一次,以完成所有搜索/替换。

这在很大程度上取决于给定字符串中平均存在的标记数量。

StringBuilder和String搜索键的性能可能相似,但如果您必须替换单个字符串中的许多标记,StringBuilder将获胜。

如果你平均每个字符串只有一到两个标记,并且你的字典很小,我会选择string.Replace.

如果有许多标记,您可能需要定义一种自定义语法来识别标记——例如,用适当的转义规则将文本大括号括在大括号中。然后,您可以实现一种解析算法,该算法对字符串的字符进行一次迭代,识别并替换它找到的每个标记。或者使用正则表达式。

我在这里花了两分钱,我只写了几行代码来测试每个方法的执行情况,正如预期的那样,结果是"取决于"。

对于较长的字符串,Regex似乎表现得更好,而对于较短的字符串,则是String.Replace。我可以看到StringBuilder.Replace的使用不是很有用,如果使用错误,从GC的角度来看,它可能是致命的(我试图共享一个StringBuilder的实例)。

查看我的StringReplaceTests GitHub repo。

@DustinDavis的答案的问题在于它递归地操作同一个字符串。除非你计划进行一种来回类型的操作,否则在这种测试中,你真的应该为每个操作案例都有单独的对象。

我决定创建自己的测试,因为我在网上发现了一些相互矛盾的答案,我想完全确定。我正在开发的程序处理大量文本(在某些情况下,文件中有数万行)。

因此,这里有一个快速的方法,你可以复制和粘贴,自己看看,哪个更快。你可能需要创建自己的文本文件来进行测试,但你可以很容易地从任何地方复制和粘贴文本,并为自己制作一个足够大的文件:

using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Windows;
void StringReplace_vs_StringBuilderReplace( string file, string word1, string word2 )
{
    using( FileStream fileStream = new FileStream( file, FileMode.Open, FileAccess.Read ) )
    using( StreamReader streamReader = new StreamReader( fileStream, Encoding.UTF8 ) )
    {
        string text = streamReader.ReadToEnd(),
               @string = text;
        StringBuilder @StringBuilder = new StringBuilder( text );
        int iterations = 10000;
        Stopwatch watch1 = new Stopwatch.StartNew();
        for( int i = 0; i < iterations; i++ )
            if( i % 2 == 0 ) @string = @string.Replace( word1, word2 );
            else @string = @string.Replace( word2, word1 );
        watch1.Stop();
        double stringMilliseconds = watch1.ElapsedMilliseconds;
        Stopwatch watch2 = new Stopwatch.StartNew();
        for( int i = 0; i < iterations; i++ )
            if( i % 2 == 0 ) @StringBuilder = @StringBuilder .Replace( word1, word2 );
            else @StringBuilder = @StringBuilder .Replace( word2, word1 );
        watch2.Stop();
        double StringBuilderMilliseconds = watch1.ElapsedMilliseconds;
        MessageBox.Show( string.Format( "string.Replace: {0}'nStringBuilder.Replace: {1}",
                                        stringMilliseconds, StringBuilderMilliseconds ) );
    }
}

我得到了那根绳子。每次换掉8-10个字母的单词,Replace()的速度会快20%左右。如果你想要自己的经验证据,就自己试试吧。