【问题标题】:Replacing multiple characters in a string, the fastest way?替换字符串中的多个字符,最快的方法?
【发布时间】:2012-08-10 10:20:28
【问题描述】:

我正在将一些具有多个 string 字段的记录从旧数据库导入到新数据库。它似乎很慢,我怀疑是因为我这样做:

foreach (var oldObj in oldDB)
{
    NewObject newObj = new NewObject();
    newObj.Name = oldObj.Name.Trim().Replace('^', 'Č').Replace('@', 'Ž').Replace('[', 'Š')
        .Replace(']', 'Ć').Replace('`', 'ž').Replace('}', 'ć')
        .Replace('~', 'č').Replace('{', 'š').Replace('\\', 'Đ');
    newObj.Surname = oldObj.Surname.Trim().Replace('^', 'Č').Replace('@', 'Ž').Replace('[', 'Š')
        .Replace(']', 'Ć').Replace('`', 'ž').Replace('}', 'ć')
        .Replace('~', 'č').Replace('{', 'š').Replace('\\', 'Đ');
    newObj.Address = oldObj.Address.Trim().Replace('^', 'Č').Replace('@', 'Ž').Replace('[', 'Š')
        .Replace(']', 'Ć').Replace('`', 'ž').Replace('}', 'ć')
        .Replace('~', 'č').Replace('{', 'š').Replace('\\', 'Đ');
    newObj.Note = oldObj.Note.Trim().Replace('^', 'Č').Replace('@', 'Ž').Replace('[', 'Š')
        .Replace(']', 'Ć').Replace('`', 'ž').Replace('}', 'ć')
        .Replace('~', 'č').Replace('{', 'š').Replace('\\', 'Đ');
    /*
    ... some processing ...
    */
}

现在,我通过网络阅读了一些帖子和文章,在那里我看到了许多不同的想法。有人说如果我用MatchEvaluator 做正则表达式会更好,有人说最好保持原样。

虽然为自己做一个基准案例可能对我来说更容易,但我决定在这里提出一个问题,以防其他人一直在想同样的问题,或者有人事先知道。

那么在 C# 中最快的方法是什么?

编辑

我已经发布了基准here。乍一看,理查德的方式可能是最快的。然而,由于错误的正则表达式模式,他的方式和 Marc 的方式都会做任何事情。改正后的模式

@"\^@\[\]`\}~\{\\" 

@"\^|@|\[|\]|`|\}|~|\{|\\" 

似乎使用链式 .Replace() 调用的旧方法毕竟是最快的

【问题讨论】:

  • 我建议保持原样。也许尝试并行​​ foreach ?
  • 怀疑是这个原因?你应该知道。您需要分析应用程序以解决瓶颈问题 - 不要猜测。
  • 我曾经问过this 并接受了this,但我不确定这是否是您想要的。
  • @Oded 我是否怀疑或知道这不是一个问题,问题是如何在替换字符串中的多个字符时产生更好的性能。这无关紧要,您应该假设示例代码的/* ... some processing ... */ 部分肯定不是瓶颈,因为我以这种形式提出了问题。感谢您的建设性评论。
  • 我的观点是,如果您没有数据,您可能会进行一些微观优化,其中有可能进行宏观优化。怀疑一段代码是问题并不意味着它是 - 您可能将精力集中在错误问题上,仅此而已。

标签: c# regex string performance replace


【解决方案1】:

感谢您的投入。 我写了一个快速而肮脏的基准来测试你的输入。我已经用 500.000 次迭代测试了解析 4 个字符串并完成了 4 次传递。结果如下:

*** 通过 1 旧的 (Chained String.Replace()) 方式在 814 毫秒内完成 logicnp (ToCharArray) 方式在 916 毫秒内完成 oleksii (StringBuilder) 方式在 943 毫秒内完成 André Christoffer Andersen (Lambda w/ Aggregate) 方式在 2551 毫秒内完成 Richard(带 MatchEvaluator 的正则表达式)方式在 215 毫秒内完成 Marc Gravell(静态正则表达式)方式在 1008 毫秒内完成 *** 通过 2 旧的(Chained String.Replace())方式在 786 毫秒内完成 logicnp (ToCharArray) 方式在 920 毫秒内完成 oleksii (StringBuilder) 方式在 905 毫秒内完成 André Christoffer Andersen(Lambda w/Aggregate)方式在 2515 毫秒内完成 Richard(带 MatchEvaluator 的正则表达式)方式在 217 毫秒内完成 Marc Gravell(静态正则表达式)方式在 1025 毫秒内完成 *** 通过 3 旧的(Chained String.Replace())方式在 775 毫秒内完成 logicnp (ToCharArray) 方式在 903 毫秒内完成 oleksii (StringBuilder) 方式在 931 毫秒内完成 André Christoffer Andersen (Lambda w/ Aggregate) 方式在 2529 毫秒内完成 Richard(带 MatchEvaluator 的正则表达式)方式在 214 毫秒内完成 Marc Gravell(静态正则表达式)方式在 1022 毫秒内完成 *** 通过 4 旧的(Chained String.Replace())方式在 799 毫秒内完成 logicnp (ToCharArray) 方式在 908 毫秒内完成 oleksii (StringBuilder) 方式在 938 毫秒内完成 André Christoffer Andersen(Lambda w/Aggregate)方式在 2592 毫秒内完成 Richard(带 MatchEvaluator 的正则表达式)方式在 225 毫秒内完成 Marc Gravell(静态正则表达式)方式在 1050 毫秒内完成

此基准测试的代码如下。请查看代码并确认@Richard 获得了最快的方法。请注意,我没有检查输出是否正确,我认为它们是正确的。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Text.RegularExpressions;

namespace StringReplaceTest
{
    class Program
    {
        static string test1 = "A^@[BCD";
        static string test2 = "E]FGH\\";
        static string test3 = "ijk`l}m";
        static string test4 = "nopq~{r";

        static readonly Dictionary<char, string> repl =
            new Dictionary<char, string> 
            { 
                {'^', "Č"}, {'@', "Ž"}, {'[', "Š"}, {']', "Ć"}, {'`', "ž"}, {'}', "ć"}, {'~', "č"}, {'{', "š"}, {'\\', "Đ"} 
            };

        static readonly Regex replaceRegex;

        static Program() // static initializer 
        {
            StringBuilder pattern = new StringBuilder().Append('[');
            foreach (var key in repl.Keys)
                pattern.Append(Regex.Escape(key.ToString()));
            pattern.Append(']');
            replaceRegex = new Regex(pattern.ToString(), RegexOptions.Compiled);
        }

        public static string Sanitize(string input)
        {
            return replaceRegex.Replace(input, match =>
            {
                return repl[match.Value[0]];
            });
        } 

        static string DoGeneralReplace(string input) 
        { 
            var sb = new StringBuilder(input);
            return sb.Replace('^', 'Č').Replace('@', 'Ž').Replace('[', 'Š').Replace(']', 'Ć').Replace('`', 'ž').Replace('}', 'ć').Replace('~', 'č').Replace('{', 'š').Replace('\\', 'Đ').ToString(); 
        }

        //Method for replacing chars with a mapping 
        static string Replace(string input, IDictionary<char, char> replacementMap)
        {
            return replacementMap.Keys
                .Aggregate(input, (current, oldChar)
                    => current.Replace(oldChar, replacementMap[oldChar]));
        } 

        static void Main(string[] args)
        {
            for (int i = 1; i < 5; i++)
                DoIt(i);
        }

        static void DoIt(int n)
        {
            Stopwatch sw = new Stopwatch();
            int idx = 0;

            Console.WriteLine("*** Pass " + n.ToString());
            // old way
            sw.Start();
            for (idx = 0; idx < 500000; idx++)
            {
                string result1 = test1.Replace('^', 'Č').Replace('@', 'Ž').Replace('[', 'Š').Replace(']', 'Ć').Replace('`', 'ž').Replace('}', 'ć').Replace('~', 'č').Replace('{', 'š').Replace('\\', 'Đ');
                string result2 = test2.Replace('^', 'Č').Replace('@', 'Ž').Replace('[', 'Š').Replace(']', 'Ć').Replace('`', 'ž').Replace('}', 'ć').Replace('~', 'č').Replace('{', 'š').Replace('\\', 'Đ');
                string result3 = test3.Replace('^', 'Č').Replace('@', 'Ž').Replace('[', 'Š').Replace(']', 'Ć').Replace('`', 'ž').Replace('}', 'ć').Replace('~', 'č').Replace('{', 'š').Replace('\\', 'Đ');
                string result4 = test4.Replace('^', 'Č').Replace('@', 'Ž').Replace('[', 'Š').Replace(']', 'Ć').Replace('`', 'ž').Replace('}', 'ć').Replace('~', 'č').Replace('{', 'š').Replace('\\', 'Đ');
            }
            sw.Stop();
            Console.WriteLine("Old (Chained String.Replace()) way completed in " + sw.ElapsedMilliseconds.ToString() + " ms");

            Dictionary<char, char> replacements = new Dictionary<char, char>();
            replacements.Add('^', 'Č');
            replacements.Add('@', 'Ž');
            replacements.Add('[', 'Š');
            replacements.Add(']', 'Ć');
            replacements.Add('`', 'ž');
            replacements.Add('}', 'ć');
            replacements.Add('~', 'č');
            replacements.Add('{', 'š');
            replacements.Add('\\', 'Đ');

            // logicnp way
            sw.Reset();
            sw.Start();
            for (idx = 0; idx < 500000; idx++)
            {
                char[] charArray1 = test1.ToCharArray();
                for (int i = 0; i < charArray1.Length; i++)
                {
                    char newChar;
                    if (replacements.TryGetValue(test1[i], out newChar))
                        charArray1[i] = newChar;
                }
                string result1 = new string(charArray1);

                char[] charArray2 = test2.ToCharArray();
                for (int i = 0; i < charArray2.Length; i++)
                {
                    char newChar;
                    if (replacements.TryGetValue(test2[i], out newChar))
                        charArray2[i] = newChar;
                }
                string result2 = new string(charArray2);

                char[] charArray3 = test3.ToCharArray();
                for (int i = 0; i < charArray3.Length; i++)
                {
                    char newChar;
                    if (replacements.TryGetValue(test3[i], out newChar))
                        charArray3[i] = newChar;
                }
                string result3 = new string(charArray3);

                char[] charArray4 = test4.ToCharArray();
                for (int i = 0; i < charArray4.Length; i++)
                {
                    char newChar;
                    if (replacements.TryGetValue(test4[i], out newChar))
                        charArray4[i] = newChar;
                }
                string result4 = new string(charArray4);
            }
            sw.Stop();
            Console.WriteLine("logicnp (ToCharArray) way completed in " + sw.ElapsedMilliseconds.ToString() + " ms");

            // oleksii way
            sw.Reset();
            sw.Start();
            for (idx = 0; idx < 500000; idx++)
            {
                string result1 = DoGeneralReplace(test1);
                string result2 = DoGeneralReplace(test2);
                string result3 = DoGeneralReplace(test3);
                string result4 = DoGeneralReplace(test4);
            }
            sw.Stop();
            Console.WriteLine("oleksii (StringBuilder) way completed in " + sw.ElapsedMilliseconds.ToString() + " ms");

            // André Christoffer Andersen way
            sw.Reset();
            sw.Start();
            for (idx = 0; idx < 500000; idx++)
            {
                string result1 = Replace(test1, replacements);
                string result2 = Replace(test2, replacements);
                string result3 = Replace(test3, replacements);
                string result4 = Replace(test4, replacements);
            }
            sw.Stop();
            Console.WriteLine("André Christoffer Andersen (Lambda w/ Aggregate) way completed in " + sw.ElapsedMilliseconds.ToString() + " ms");

            // Richard way
            sw.Reset();
            sw.Start();
            Regex reg = new Regex(@"\^|@|\[|\]|`|\}|~|\{|\\");
            MatchEvaluator eval = match =>
            {
                switch (match.Value)
                {
                    case "^": return "Č";
                    case "@": return "Ž";
                    case "[": return "Š";
                    case "]": return "Ć";
                    case "`": return "ž";
                    case "}": return "ć";
                    case "~": return "č";
                    case "{": return "š";
                    case "\\": return "Đ";
                    default: throw new Exception("Unexpected match!");
                }
            };
            for (idx = 0; idx < 500000; idx++)
            {
                string result1 = reg.Replace(test1, eval);
                string result2 = reg.Replace(test2, eval);
                string result3 = reg.Replace(test3, eval);
                string result4 = reg.Replace(test4, eval);
            }
            sw.Stop();
            Console.WriteLine("Richard (Regex w/ MatchEvaluator) way completed in " + sw.ElapsedMilliseconds.ToString() + " ms");

            // Marc Gravell way
            sw.Reset();
            sw.Start();
            for (idx = 0; idx < 500000; idx++)
            {
                string result1 = Sanitize(test1);
                string result2 = Sanitize(test2);
                string result3 = Sanitize(test3);
                string result4 = Sanitize(test4);
            }
            sw.Stop();
            Console.WriteLine("Marc Gravell (Static Regex) way completed in " + sw.ElapsedMilliseconds.ToString() + " ms\n");
        }
    }
}

2020 年 6 月编辑
由于这个问答仍然很受欢迎,我想使用带有 IndexOfAny 的 StringBuilder 使用来自 user1664043 的额外输入来更新它,这次是使用 .NET Core 3.1 编译的,结果如下:

*** 通过 1 旧的 (Chained String.Replace()) 方式在 199 毫秒内完成 logicnp (ToCharArray) 方式在 296 毫秒内完成 oleksii (StringBuilder) 方式在 416 毫秒内完成 André Christoffer Andersen(Lambda w/Aggregate)方式在 870 毫秒内完成 Richard (Regex w/ MatchEvaluator) 方式在 1722 毫秒内完成 Marc Gravell(静态正则表达式)方式在 395 毫秒内完成 user1664043 (StringBuilder w/IndexOfAny) 方式在 459 毫秒内完成 *** 通过 2 旧的(Chained String.Replace())方式在 215 毫秒内完成 logicnp (ToCharArray) 方式在 239 毫秒内完成 oleksii (StringBuilder) 方式在 341 毫秒内完成 André Christoffer Andersen(Lambda w/Aggregate)方式在 758 毫秒内完成 理查德(Regex w/ MatchEvaluator)方式在 1591 毫秒内完成 Marc Gravell(静态正则表达式)方式在 354 毫秒内完成 user1664043 (StringBuilder w/IndexOfAny) 方式在 426 毫秒内完成 *** 通过 3 旧的 (Chained String.Replace()) 方式在 199 毫秒内完成 logicnp (ToCharArray) 方式在 265 毫秒内完成 oleksii (StringBuilder) 方式在 337 毫秒内完成 André Christoffer Andersen(Lambda w/Aggregate)方式在 817 毫秒内完成 Richard (Regex w/ MatchEvaluator) 方式在 1666 毫秒内完成 Marc Gravell(静态正则表达式)方式在 373 毫秒内完成 user1664043 (StringBuilder w/IndexOfAny) 方式在 412 毫秒内完成 *** 通过 4 旧的 (Chained String.Replace()) 方式在 199 毫秒内完成 logicnp (ToCharArray) 方式在 230 毫秒内完成 oleksii (StringBuilder) 方式在 324 毫秒内完成 André Christoffer Andersen(Lambda w/Aggregate)方式在 791 毫秒内完成 Richard (Regex w/ MatchEvaluator) 方式在 1699 毫秒内完成 Marc Gravell(静态正则表达式)方式在 359 毫秒内完成 user1664043 (StringBuilder w/IndexOfAny) 方式在 413 毫秒内完成

以及更新后的代码:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace Test.StringReplace
{
    class Program
    {
        static string test1 = "A^@[BCD";
        static string test2 = "E]FGH\\";
        static string test3 = "ijk`l}m";
        static string test4 = "nopq~{r";

        static readonly Dictionary<char, string> repl =
            new Dictionary<char, string>
            {
                {'^', "Č"}, {'@', "Ž"}, {'[', "Š"}, {']', "Ć"}, {'`', "ž"}, {'}', "ć"}, {'~', "č"}, {'{', "š"}, {'\\', "Đ"}
            };

        static readonly Regex replaceRegex;

        static readonly char[] badChars = new char[] { '^', '@', '[', ']', '`', '}', '~', '{', '\\' };

        static readonly char[] replacementChars = new char[] { 'Č', 'Ž', 'Š', 'Ć', 'ž', 'ć', 'č', 'š', 'Đ' };

        static Program() // static initializer 
        {
            StringBuilder pattern = new StringBuilder().Append('[');
            foreach (var key in repl.Keys)
                pattern.Append(Regex.Escape(key.ToString()));
            pattern.Append(']');
            replaceRegex = new Regex(pattern.ToString(), RegexOptions.Compiled);
        }

        public static string Sanitize(string input)
        {
            return replaceRegex.Replace(input, match =>
            {
                return repl[match.Value[0]];
            });
        }

        static string DoGeneralReplace(string input)
        {
            var sb = new StringBuilder(input);
            return sb.Replace('^', 'Č').Replace('@', 'Ž').Replace('[', 'Š').Replace(']', 'Ć').Replace('`', 'ž').Replace('}', 'ć').Replace('~', 'č').Replace('{', 'š').Replace('\\', 'Đ').ToString();
        }

        //Method for replacing chars with a mapping 
        static string Replace(string input, IDictionary<char, char> replacementMap)
        {
            return replacementMap.Keys
                .Aggregate(input, (current, oldChar)
                    => current.Replace(oldChar, replacementMap[oldChar]));
        }

        static string ReplaceCharsWithIndexOfAny(string sIn)
        {
            int replChar = sIn.IndexOfAny(badChars);
            if (replChar < 0)
                return sIn;

            // Don't even bother making a copy unless you know you have something to swap
            StringBuilder sb = new StringBuilder(sIn, 0, replChar, sIn.Length + 10);
            while (replChar >= 0 && replChar < sIn.Length)
            {
                var c = replacementChars[replChar];
                sb.Append(c);

                ////// This approach lets you swap a char for a string or to remove some
                ////// If you had a straight char for char swap, you could just have your repl chars in an array with the same ordinals and do it all in 2 lines matching the ordinals.
                ////c = c switch
                ////{
                ////    ////case "^":
                ////    ////    c = "Č";
                ////    ////    ...
                ////    '\ufeff' => null,
                ////    _ => replacementChars[replChar],
                ////};

                ////if (c != null)
                ////{
                ////    sb.Append(c);
                ////}

                replChar++; // skip over what we just replaced
                if (replChar < sIn.Length)
                {
                    int nextRepChar = sIn.IndexOfAny(badChars, replChar);
                    sb.Append(sIn, replChar, (nextRepChar > 0 ? nextRepChar : sIn.Length) - replChar);
                    replChar = nextRepChar;
                }
            }

            return sb.ToString();
        }

        static void Main(string[] args)
        {
            for (int i = 1; i < 5; i++)
                DoIt(i);
        }

        static void DoIt(int n)
        {
            Stopwatch sw = new Stopwatch();
            int idx = 0;

            Console.WriteLine("*** Pass " + n.ToString());
            // old way
            sw.Start();
            for (idx = 0; idx < 500000; idx++)
            {
                string result1 = test1.Replace('^', 'Č').Replace('@', 'Ž').Replace('[', 'Š').Replace(']', 'Ć').Replace('`', 'ž').Replace('}', 'ć').Replace('~', 'č').Replace('{', 'š').Replace('\\', 'Đ');
                string result2 = test2.Replace('^', 'Č').Replace('@', 'Ž').Replace('[', 'Š').Replace(']', 'Ć').Replace('`', 'ž').Replace('}', 'ć').Replace('~', 'č').Replace('{', 'š').Replace('\\', 'Đ');
                string result3 = test3.Replace('^', 'Č').Replace('@', 'Ž').Replace('[', 'Š').Replace(']', 'Ć').Replace('`', 'ž').Replace('}', 'ć').Replace('~', 'č').Replace('{', 'š').Replace('\\', 'Đ');
                string result4 = test4.Replace('^', 'Č').Replace('@', 'Ž').Replace('[', 'Š').Replace(']', 'Ć').Replace('`', 'ž').Replace('}', 'ć').Replace('~', 'č').Replace('{', 'š').Replace('\\', 'Đ');
            }

            sw.Stop();
            Console.WriteLine("Old (Chained String.Replace()) way completed in " + sw.ElapsedMilliseconds.ToString() + " ms");

            Dictionary<char, char> replacements = new Dictionary<char, char>();
            replacements.Add('^', 'Č');
            replacements.Add('@', 'Ž');
            replacements.Add('[', 'Š');
            replacements.Add(']', 'Ć');
            replacements.Add('`', 'ž');
            replacements.Add('}', 'ć');
            replacements.Add('~', 'č');
            replacements.Add('{', 'š');
            replacements.Add('\\', 'Đ');

            // logicnp way
            sw.Reset();
            sw.Start();
            for (idx = 0; idx < 500000; idx++)
            {
                char[] charArray1 = test1.ToCharArray();
                for (int i = 0; i < charArray1.Length; i++)
                {
                    char newChar;
                    if (replacements.TryGetValue(test1[i], out newChar))
                        charArray1[i] = newChar;
                }

                string result1 = new string(charArray1);

                char[] charArray2 = test2.ToCharArray();
                for (int i = 0; i < charArray2.Length; i++)
                {
                    char newChar;
                    if (replacements.TryGetValue(test2[i], out newChar))
                        charArray2[i] = newChar;
                }

                string result2 = new string(charArray2);

                char[] charArray3 = test3.ToCharArray();
                for (int i = 0; i < charArray3.Length; i++)
                {
                    char newChar;
                    if (replacements.TryGetValue(test3[i], out newChar))
                        charArray3[i] = newChar;
                }

                string result3 = new string(charArray3);

                char[] charArray4 = test4.ToCharArray();
                for (int i = 0; i < charArray4.Length; i++)
                {
                    char newChar;
                    if (replacements.TryGetValue(test4[i], out newChar))
                        charArray4[i] = newChar;
                }

                string result4 = new string(charArray4);
            }

            sw.Stop();
            Console.WriteLine("logicnp (ToCharArray) way completed in " + sw.ElapsedMilliseconds.ToString() + " ms");

            // oleksii way
            sw.Reset();
            sw.Start();
            for (idx = 0; idx < 500000; idx++)
            {
                string result1 = DoGeneralReplace(test1);
                string result2 = DoGeneralReplace(test2);
                string result3 = DoGeneralReplace(test3);
                string result4 = DoGeneralReplace(test4);
            }

            sw.Stop();
            Console.WriteLine("oleksii (StringBuilder) way completed in " + sw.ElapsedMilliseconds.ToString() + " ms");

            // André Christoffer Andersen way
            sw.Reset();
            sw.Start();
            for (idx = 0; idx < 500000; idx++)
            {
                string result1 = Replace(test1, replacements);
                string result2 = Replace(test2, replacements);
                string result3 = Replace(test3, replacements);
                string result4 = Replace(test4, replacements);
            }

            sw.Stop();
            Console.WriteLine("André Christoffer Andersen (Lambda w/ Aggregate) way completed in " + sw.ElapsedMilliseconds.ToString() + " ms");

            // Richard way
            sw.Reset();
            sw.Start();
            Regex reg = new Regex(@"\^|@|\[|\]|`|\}|~|\{|\\");
            MatchEvaluator eval = match =>
            {
                switch (match.Value)
                {
                    case "^": return "Č";
                    case "@": return "Ž";
                    case "[": return "Š";
                    case "]": return "Ć";
                    case "`": return "ž";
                    case "}": return "ć";
                    case "~": return "č";
                    case "{": return "š";
                    case "\\": return "Đ";
                    default: throw new Exception("Unexpected match!");
                }
            };
            for (idx = 0; idx < 500000; idx++)
            {
                string result1 = reg.Replace(test1, eval);
                string result2 = reg.Replace(test2, eval);
                string result3 = reg.Replace(test3, eval);
                string result4 = reg.Replace(test4, eval);
            }

            sw.Stop();
            Console.WriteLine("Richard (Regex w/ MatchEvaluator) way completed in " + sw.ElapsedMilliseconds.ToString() + " ms");

            // Marc Gravell way
            sw.Reset();
            sw.Start();
            for (idx = 0; idx < 500000; idx++)
            {
                string result1 = Sanitize(test1);
                string result2 = Sanitize(test2);
                string result3 = Sanitize(test3);
                string result4 = Sanitize(test4);
            }

            sw.Stop();
            Console.WriteLine("Marc Gravell (Static Regex) way completed in " + sw.ElapsedMilliseconds.ToString() + " ms");

            // user1664043 way
            sw.Reset();
            sw.Start();
            for (idx = 0; idx < 500000; idx++)
            {
                string result1 = ReplaceCharsWithIndexOfAny(test1);
                string result2 = ReplaceCharsWithIndexOfAny(test2);
                string result3 = ReplaceCharsWithIndexOfAny(test3);
                string result4 = ReplaceCharsWithIndexOfAny(test4);
            }

            sw.Stop();
            Console.WriteLine("user1664043 (StringBuilder w/ IndexOfAny) way completed in " + sw.ElapsedMilliseconds.ToString() + " ms\n");
        }
    }
}

【讨论】:

  • Regex 更快并不奇怪。它旨在以荒谬的效率搜索字符串。永远记住,工具法则是不好的——利用为你想要做的事情而构建的技术,所以不要害怕使用正则表达式。 C# 并不擅长一切,只是因为它有一个 API。好问题和好的基准@Dejan。
  • 我还要添加一件事 - 你的测试字符串很短。虽然这可能是您的真实数据的情况(在这种情况下,您的基准测试很好并且很准确),但它会扭曲较长字符串的结果,替换不同数量的字符等。我怀疑这是相对较好的原因string.Replace 的性能 - 它确实会一遍又一遍地创建字符串(尽管只有在某些情况发生变化时),但循环和字符串结果都非常小,所以它不会花费太多。这种差异在长字符串上会更加明显。
  • 我认为 Regex 方法是最快的,因为该模式在字符串的开头缺少'[',在字符串的末尾缺少']'。显示的示例没有替换,因为我们没有匹配!我认为这两种正则表达式方法在时间上的巨大差异可以简单地解释一下。
  • 我在 3 个版本中运行了这个测试(差异长度和替换),logicnp 总是最好的,其次是 oleksii。他甚至在我的原始测试中也是最好的? .NET 4.5,发布,Win7,i7
  • 总结一下:原始数字显然不能反映正则表达式被破坏的事实。我重新运行了测试(添加了我提出的方法),添加了结果正确的验证。然后我取出验证并重新运行它们。我还使用相同的静态正则表达式、在测试运行中创建的正则表达式并使用 Regex.Replace(input,pattern,eval) 运行 MatchEvaluator 方法。 MatchEvaluator 方法总是最慢的。给定测试数据,链式 StringBuilder.Replace 总是最快的,然后是 ToCharArray,string.Replace 链,我的方法,Marc 的,然后是 Richard 的
【解决方案2】:

最快的方法

唯一的方法是自己比较性能。尝试在 Q 中,使用StringBuilderRegex.Replace

但微基准测试并未考虑整个系统的范围。如果此方法仅占整个系统的一小部分,则其性能可能与整个应用程序的性能无关。

一些注意事项:

  1. 如上使用String(我假设)将创建大量中间字符串:GC 需要做更多工作。但这很简单。
  2. 使用StringBuilder 允许在每次替换时修改相同的基础数据。这会产生更少的垃圾。这几乎就像使用 String 一样简单。
  3. 使用regex 是最复杂的(因为您需要有代码来计算替换),但允许使用单个表达式。我希望这会更慢,除非替换列表非常大并且输入字符串中的替换很少(即,大多数替换方法调用什么都不替换,只是通过字符串进行搜索)。

由于 GC 负载较少,我预计 #2 在重复使用(数千次)后会稍微快一些。

对于正则表达式方法,您需要类似:

newObj.Name = Regex.Replace(oldObj.Name.Trim(), @"[@^\[\]`}~{\\]", match => {
  switch (match.Value) {
    case "^": return "Č";
    case "@": return "Ž";
    case "[": return "Š";
    case "]": return "Ć";
    case "`": return "ž";
    case "}": return "ć";
    case "~": return "č";
    case "{": return "š";
    case "\\": return "Đ";
    default: throw new Exception("Unexpected match!");
  }
});

这可以通过使用Dictionary&lt;char,char&gt; 参数化以保持替换和可重复使用的MatchEvaluator 以可重复使用的方式完成。

【讨论】:

  • 感谢您的回答。请查看我作为另一个答案发布的benchmark
  • @DejanJanjušević 哎呀,正则表达式错字...我知道我需要一个字符类(更正)。
  • 但是,当我修正错字时,结果很差……甚至比 Marc 的静态正则表达式还要慢。
  • @DejanJanjušević 值得注意的是正则表达式引擎缓存了传递给静态方法的最后几个 (15 IIRC) 正则表达式,所以在这个测试中我不希望看到显式创建正则表达式实例有任何区别(只有第一次使用静态方法会更慢编译)。
【解决方案3】:

试试这个:

Dictionary<char, char> replacements = new Dictionary<char, char>();
// populate replacements

string str = "mystring";
char []charArray = str.ToCharArray();

for (int i = 0; i < charArray.Length; i++)
{
    char newChar;
    if (replacements.TryGetValue(str[i], out newChar))
    charArray[i] = newChar;
}

string newStr = new string(charArray);

【讨论】:

  • +1 我只会尝试添加 IndexOfAny 以避免在不需要时出现字符串循环
  • @Steve - IndexOfAny 也会在内部使用循环。没有办法避免这个循环。
  • 感谢您的回答。请查看我作为另一个答案发布的benchmark
  • @logicnp 是的,但 IndexOfAny 速度非常快——如果字符串没有任何东西要替换是相对常见的,这可能意味着显着的节省(包括完全删除新 char[] 的创建- 不是很大的成本,但在事情的范围内仍然很重要)。注意ToCharArray和后面的new string(charArray)都复制字符串的char数据并分配所需的内存。
  • 大部分时间都花在字典查找上。这可以通过使用 char 数组来避免,其中键 char 作为索引,替换 char 作为值。然后检查 '\0' 以查看是否有替换字符。
【解决方案4】:

一种可能的解决方案是为此使用StringBuilder 类。

您可以先将代码重构为单个方法

public string DoGeneralReplace(string input)
{
    var sb = new StringBuilder(input);
    sb.Replace("^", "Č")
      .Replace("@", "Ž") ...;
}


//usage
foreach (var oldObj in oldDB)
{
    NewObject newObj = new NewObject();
    newObj.Name = DoGeneralReplace(oldObj.Name);
    ...
}

【讨论】:

  • 感谢您的回答。请查看我作为另一个答案发布的benchmark
【解决方案5】:

您可以在字符映射上使用 Aggregate 来使用 lambda 表达式:

  //Method for replacing chars with a mapping
  static string Replace(string input, IDictionary<char, char> replacementMap) {
      return replacementMap.Keys
          .Aggregate(input, (current, oldChar) 
              => current.Replace(oldChar, replacementMap[oldChar]));
  }

您可以按如下方式运行:

  private static void Main(string[] args) {
      //Char to char map using <oldChar, newChar>
      var charMap = new Dictionary<char, char>();
      charMap.Add('-', 'D'); charMap.Add('|', 'P'); charMap.Add('@', 'A');

      //Your input string
      string myString = "asgjk--@dfsg||jshd--f@jgsld-kj|rhgunfh-@-nsdflngs";

      //Your own replacement method
      myString = Replace(myString, charMap);

      //out: myString = "asgjkDDAdfsgPPjshdDDfAjgsldDkjPrhgunfhDADnsdflngs"
  }

【讨论】:

  • 感谢您的回答。请查看我作为另一个答案发布的benchmark
【解决方案6】:

好吧,我会尝试做一些类似的事情:

    static readonly Dictionary<char, string> replacements =
       new Dictionary<char, string>
    {
        {']',"Ć"}, {'~', "č"} // etc
    };
    static readonly Regex replaceRegex;
    static YourUtilityType() // static initializer
    {
        StringBuilder pattern = new StringBuilder().Append('[');
        foreach(var key in replacements.Keys)
            pattern.Append(Regex.Escape(key.ToString()));
        pattern.Append(']');
        replaceRegex = new Regex(pattern.ToString(), RegexOptions.Compiled);
    }
    public static string Sanitize(string input)
    {
        return replaceRegex.Replace(input, match =>
        {
            return replacements[match.Value[0]];
        });
    }

这有一个维护位置(在顶部),并构建了一个预编译的Regex 来处理替换。所有开销只完成一次(因此static)。

【讨论】:

  • 感谢您的回答。请查看我作为另一个答案发布的benchmark
【解决方案7】:

使用 IndexOfAny 的混合 StringBuilder 方法:

protected String ReplaceChars(String sIn)
{
    int replChar = sIn.IndexOfAny(badChars);
    if (replChar < 0)
        return sIn;

    // Don't even bother making a copy unless you know you have something to swap
    StringBuilder sb = new StringBuilder(sIn, 0, replChar, sIn.Length + 10);
    while (replChar >= 0 && replChar < sIn.Length)
    {
        char? c = sIn[replChar];
        string s = null;
        // This approach lets you swap a char for a string or to remove some
        // If you had a straight char for char swap, you could just have your repl chars in an array with the same ordinals and do it all in 2 lines matching the ordinals.
        switch (c)
        {
            case "^": c = "Č";
            ...
            case '\ufeff': c = null; break;
        }
        if (s != null) sb.Append(s);
        else if (c != null) sb.Append(c);

        replChar++; // skip over what we just replaced
        if (replChar < sIn.Length)
        {
            int nextRepChar = sIn.IndexOfAny(badChars, replChar);
            sb.Append(sIn, replChar, (nextRepChar > 0 ? nextRepChar : sIn.Length) - replChar);
            replChar = nextRepChar;
        }
    }
    return sb.ToString();
}

【讨论】:

  • 嗨,我对此很好奇,并将您的输入添加到基准测试中。请查看编辑后接受的结果。
猜你喜欢
  • 1970-01-01
  • 2011-03-25
  • 1970-01-01
  • 1970-01-01
  • 2013-09-21
  • 1970-01-01
  • 1970-01-01
  • 2018-03-28
相关资源
最近更新 更多