【问题标题】:Replace multiple characters in a C# string替换 C# 字符串中的多个字符
【发布时间】:2011-09-01 01:40:27
【问题描述】:

有没有更好的方法来替换字符串?

我很惊讶 Replace 不接受字符数组或字符串数​​组。我想我可以编写自己的扩展,但我很好奇是否有更好的内置方法来执行以下操作?注意最后一个 Replace 是一个字符串而不是一个字符。

myString.Replace(';', '\n').Replace(',', '\n').Replace('\r', '\n').Replace('\t', '\n').Replace(' ', '\n').Replace("\n\n", "\n");

【问题讨论】:

    标签: c# .net string


    【解决方案1】:

    您可以使用替换正则表达式。

    s/[;,\t\r ]|[\n]{2}/\n/g
    
    • s/开头的意思是搜索
    • [] 之间的字符是要搜索的字符(按任意顺序)
    • 第二个/ 分隔搜索文本和替换文本

    英文是这样写的:

    “搜索;,\t\r(空格)或恰好两个连续的\n并将其替换为\n

    在 C# 中,您可以执行以下操作:(在导入 System.Text.RegularExpressions 之后)

    Regex pattern = new Regex("[;,\t\r ]|[\n]{2}");
    pattern.Replace(myString, "\n");
    

    【讨论】:

    • \t\r 包含在 \s 中。所以你的正则表达式相当于[;,\s]
    • \s 实际上等同于[ \f\n\r\t\v],因此您在其中包含了一些原始问题中没有的内容。此外,原始问题要求您的正则表达式无法处理 Replace("\n\n", "\n")
    • 请考虑,根据我在搜索时发现的第一篇基准文章,对于用户无法配置的简单替换操作,使用正则表达式并不是最佳选择,因为它与常规字符串操作相比非常慢c# regex performance replace" 大约慢了 13 倍。
    • 啊正则表达式,权力的象形文字!我在这里看到的唯一问题是正则表达式的人类可读性;许多人拒绝理解他们。我最近在下面为那些寻找不太复杂的替代方案的人添加了一个解决方案。
    • 那么如果想用多个字符替换多个字符怎么写呢?
    【解决方案2】:

    如果你觉得自己特别聪明,不想使用 Regex:

    char[] separators = new char[]{' ',';',',','\r','\t','\n'};
    
    string s = "this;is,\ra\t\n\n\ntest";
    string[] temp = s.Split(separators, StringSplitOptions.RemoveEmptyEntries);
    s = String.Join("\n", temp);
    

    您也可以毫不费力地将其包装在扩展方法中。

    编辑:或者等待 2 分钟,我还是会写完 :)

    public static class ExtensionMethods
    {
       public static string Replace(this string s, char[] separators, string newVal)
       {
           string[] temp;
    
           temp = s.Split(separators, StringSplitOptions.RemoveEmptyEntries);
           return String.Join( newVal, temp );
       }
    }
    

    然后瞧……

    char[] separators = new char[]{' ',';',',','\r','\t','\n'};
    string s = "this;is,\ra\t\n\n\ntest";
    
    s = s.Replace(separators, "\n");
    

    【讨论】:

    • 内存效率非常低,尤其是对于较大的字符串。
    • @MarcinJuraszek Lol... 这可能是我第一次听到有人声称内置字符串方法的内存效率低于正则表达式。
    • 你是对的。我应该在发布之前测量一下。我运行基准测试,Regex.Replace 比连续多次调用string.Replace 慢 8 倍以上。并且比 Split+Join 慢 4 倍。见gist.github.com/MarcinJuraszek/c1437d925548561ba210a1c6ed144452
    • 不错的解决方案!只是一个小插件。不幸的是,如果您还想替换第一个字符,这将不起作用。假设您要替换示例字符串中的 't' 字符。 Split 方法只会删除第一个单词“this”的那个“t”,因为它是一个 EmptyEntry。如果您使用 StringSplitOptions.None 而不是 RemoveEmptyEntries,Split 将离开条目,而 Join 方法将添加分隔符。希望这会有所帮助
    【解决方案3】:

    你可以使用 Linq 的聚合函数:

    string s = "the\nquick\tbrown\rdog,jumped;over the lazy fox.";
    char[] chars = new char[] { ' ', ';', ',', '\r', '\t', '\n' };
    string snew = chars.Aggregate(s, (c1, c2) => c1.Replace(c2, '\n'));
    

    扩展方法如下:

    public static string ReplaceAll(this string seed, char[] chars, char replacementCharacter)
    {
        return chars.Aggregate(seed, (str, cItem) => str.Replace(cItem, replacementCharacter));
    }
    

    扩展方法使用示例:

    string snew = s.ReplaceAll(chars, '\n');
    

    【讨论】:

      【解决方案4】:

      这是最短的方法:

      myString = Regex.Replace(myString, @"[;,\t\r ]|[\n]{2}", "\n");
      

      【讨论】:

      • 当您在初始化程序中需要它时,这一行也有帮助。
      【解决方案5】:

      哦,表演恐怖! 答案有点过时了,但还是……

      public static class StringUtils
      {
          #region Private members
      
          [ThreadStatic]
          private static StringBuilder m_ReplaceSB;
      
          private static StringBuilder GetReplaceSB(int capacity)
          {
              var result = m_ReplaceSB;
      
              if (null == result)
              {
                  result = new StringBuilder(capacity);
                  m_ReplaceSB = result;
              }
              else
              {
                  result.Clear();
                  result.EnsureCapacity(capacity);
              }
      
              return result;
          }
      
      
          public static string ReplaceAny(this string s, char replaceWith, params char[] chars)
          {
              if (null == chars)
                  return s;
      
              if (null == s)
                  return null;
      
              StringBuilder sb = null;
      
              for (int i = 0, count = s.Length; i < count; i++)
              {
                  var temp = s[i];
                  var replace = false;
      
                  for (int j = 0, cc = chars.Length; j < cc; j++)
                      if (temp == chars[j])
                      {
                          if (null == sb)
                          {
                              sb = GetReplaceSB(count);
                              if (i > 0)
                                  sb.Append(s, 0, i);
                          }
      
                          replace = true;
                          break;
                      }
      
                  if (replace)
                      sb.Append(replaceWith);
                  else
                      if (null != sb)
                          sb.Append(temp);
              }
      
              return null == sb ? s : sb.ToString();
          }
      }
      

      【讨论】:

        【解决方案6】:

        字符串只是不可变的字符数组

        你只需要让它可变:

        • 要么使用StringBuilder
        • 进入unsafe 世界并玩指针(虽然很危险)

        并尝试以最少的次数遍历字符数组。注意这里的HashSet,因为它避免了遍历循环内的字符序列。如果您需要更快的查找,可以将HashSet 替换为char 的优化查找(基于array[256])。

        StringBuilder 示例

        public static void MultiReplace(this StringBuilder builder, 
            char[] toReplace, 
            char replacement)
        {
            HashSet<char> set = new HashSet<char>(toReplace);
            for (int i = 0; i < builder.Length; ++i)
            {
                var currentCharacter = builder[i];
                if (set.Contains(currentCharacter))
                {
                    builder[i] = replacement;
                }
            }
        }
        

        编辑 - 优化版

        public static void MultiReplace(this StringBuilder builder, 
            char[] toReplace,
            char replacement)
        {
            var set = new bool[256];
            foreach (var charToReplace in toReplace)
            {
                set[charToReplace] = true;
            }
            for (int i = 0; i < builder.Length; ++i)
            {
                var currentCharacter = builder[i];
                if (set[currentCharacter])
                {
                    builder[i] = replacement;
                }
            }
        }
        

        然后你就这样使用它:

        var builder = new StringBuilder("my bad,url&slugs");
        builder.MultiReplace(new []{' ', '&', ','}, '-');
        var result = builder.ToString();
        

        【讨论】:

        • 请记住,.net 中的字符串是 wchar_t,您只替换了所有可能字符的一个子集(并且您需要 65536 个布尔值来优化它......)
        【解决方案7】:

        您也可以简单地编写这些string extension methods,并将它们放在您的解决方案中的某个位置:

        using System.Text;
        
        public static class StringExtensions
        {
            public static string ReplaceAll(this string original, string toBeReplaced, string newValue)
            {
                if (string.IsNullOrEmpty(original) || string.IsNullOrEmpty(toBeReplaced)) return original;
                if (newValue == null) newValue = string.Empty;
                StringBuilder sb = new StringBuilder();
                foreach (char ch in original)
                {
                    if (toBeReplaced.IndexOf(ch) < 0) sb.Append(ch);
                    else sb.Append(newValue);
                }
                return sb.ToString();
            }
        
            public static string ReplaceAll(this string original, string[] toBeReplaced, string newValue)
            {
                if (string.IsNullOrEmpty(original) || toBeReplaced == null || toBeReplaced.Length <= 0) return original;
                if (newValue == null) newValue = string.Empty;
                foreach (string str in toBeReplaced)
                    if (!string.IsNullOrEmpty(str))
                        original = original.Replace(str, newValue);
                return original;
            }
        }
        


        像这样称呼他们:

        "ABCDE".ReplaceAll("ACE", "xy");
        

        xyBxyDxy


        还有这个:

        "ABCDEF".ReplaceAll(new string[] { "AB", "DE", "EF" }, "xy");
        

        xyCxyF

        【讨论】:

          【解决方案8】:

          使用 RegEx.Replace,类似这样:

            string input = "This is   text with   far  too   much   " + 
                           "whitespace.";
            string pattern = "[;,]";
            string replacement = "\n";
            Regex rgx = new Regex(pattern);
            string result = rgx.Replace(input, replacement);
          

          这里有更多关于这个MSDN documentation for RegEx.Replace的信息

          【讨论】:

            【解决方案9】:

            就性能而言,这可能不是最好的解决方案,但它确实有效。

            var str = "filename:with&bad$separators.txt";
            char[] charArray = new char[] { '#', '%', '&', '{', '}', '\\', '<', '>', '*', '?', '/', ' ', '$', '!', '\'', '"', ':', '@' };
            foreach (var singleChar in charArray)
            {
               str = str.Replace(singleChar, '_');
            }
            

            【讨论】:

              【解决方案10】:
              string ToBeReplaceCharacters = @"~()@#$%&amp;+,'&quot;&lt;&gt;|;\/*?";
              string fileName = "filename;with<bad:separators?";
              
              foreach (var RepChar in ToBeReplaceCharacters)
              {
                  fileName = fileName.Replace(RepChar.ToString(), "");
              }
              

              【讨论】:

                【解决方案11】:

                一个 .NET Core 版本,用于将一组已定义的字符串字符替换为特定字符。它利用了最近引入的Span 类型和string.Create 方法。

                这个想法是准备一个替换数组,因此每个字符串 char 都不需要实际的比较操作。因此,替换过程提醒了状态机的工作方式。为了避免初始化替换数组的所有项,让我们在其中存储oldChar ^ newChar(异或)值,这样做有以下好处:

                • 如果 char 没有改变:ch ^ ch = 0 - 无需初始化不改变的项
                • 可以通过 XOR'ing 找到最终字符:ch ^ repl[ch]
                  • ch ^ 0 = ch - 字符大小写不变
                  • ch ^ (ch ^ newChar) = newChar - 替换字符

                所以唯一的要求是确保替换数组在初始化时为零。我们将使用ArrayPool&lt;char&gt; 来避免每次调用ReplaceAll 方法时进行分配。而且,为了确保数组归零而不会昂贵地调用Array.Clear 方法,我们将维护一个专用于ReplaceAll 方法的池。在将替换数组返回池之前,我们将清除替换数组(仅限精确项目)。

                public static class StringExtensions
                {
                    private static readonly ArrayPool<char> _replacementPool = ArrayPool<char>.Create();
                
                    public static string ReplaceAll(this string str, char newChar, params char[] oldChars)
                    {
                        // If nothing to do, return the original string.
                        if (string.IsNullOrEmpty(str) ||
                            oldChars is null ||
                            oldChars.Length == 0)
                        {
                            return str;
                        }
                
                        // If only one character needs to be replaced,
                        // use the more efficient `string.Replace`.
                        if (oldChars.Length == 1)
                        {
                            return str.Replace(oldChars[0], newChar);
                        }
                
                        // Get a replacement array from the pool.
                        var replacements = _replacementPool.Rent(char.MaxValue + 1);
                
                        try
                        {
                            // Intialize the replacement array in the way that
                            // all elements represent `oldChar ^ newChar`.
                            foreach (var oldCh in oldChars)
                            {
                                replacements[oldCh] = (char)(newChar ^ oldCh);
                            }
                
                            // Create a string with replaced characters.
                            return string.Create(str.Length, (str, replacements), (dst, args) =>
                            {
                                var repl = args.replacements;
                
                                foreach (var ch in args.str)
                                {
                                    dst[0] = (char)(repl[ch] ^ ch);
                                    dst = dst.Slice(1);
                                }
                            });
                        }
                        finally
                        {
                            // Clear the replacement array.
                            foreach (var oldCh in oldChars)
                            {
                                replacements[oldCh] = char.MinValue;
                            }
                
                            // Return the replacement array back to the pool.
                            _replacementPool.Return(replacements);
                        }
                    }
                }
                

                【讨论】:

                  【解决方案12】:

                  我知道这个问题太老了,但我想提供 2 个更有效的选项:

                  首先,Paul Walls 发布的扩展方法很好,但可以通过使用 StringBuilder 类提高效率,该类类似于字符串数据类型,但特别适用于您将多次更改字符串值的情况。这是我使用 StringBuilder 制作的扩展方法的一个版本:

                  public static string ReplaceChars(this string s, char[] separators, char newVal)
                  {
                      StringBuilder sb = new StringBuilder(s);
                      foreach (var c in separators) { sb.Replace(c, newVal); }
                      return sb.ToString();
                  }
                  

                  我运行此操作 100,000 次,使用 StringBuilder 需要 73 毫秒,而使用字符串则需要 81 毫秒。因此差异通常可以忽略不计,除非您正在运行许多操作或使用巨大的字符串。

                  其次,您可以使用 1 线循环:

                  foreach (char c in separators) { s = s.Replace(c, '\n'); }
                  

                  我个人认为这是最好的选择。它非常高效,不需要编写扩展方法。在我的测试中,这仅在 63 毫秒内运行了 100k 次迭代,使其成为最有效的。 这是上下文中的示例:

                  string s = "this;is,\ra\t\n\n\ntest";
                  char[] separators = new char[] { ' ', ';', ',', '\r', '\t', '\n' };
                  foreach (char c in separators) { s = s.Replace(c, '\n'); }
                  

                  此示例中的前 2 行归功于 Paul Walls。

                  【讨论】:

                    【解决方案13】:

                    我也摆弄过这个问题,发现这里的大多数解决方案都很慢。最快的实际上是 dodgy_coder 发布的 LINQ + Aggregate 方法。

                    但我想,根据有多少旧字符,内存分配可能也很繁重。所以我想出了这个:

                    这里的想法是为当前线程缓存旧字符的替换映射,以进行安全分配。除了仅使用输入的字符数组之外,稍后将再次作为字符串返回。而字符数组则尽量少修改。

                    [ThreadStatic]
                    private static bool[] replaceMap;
                    public static string Replace(this string input, char[] oldChars, char newChar)
                    {
                        if (input == null) throw new ArgumentNullException(nameof(input));
                        if (oldChars == null) throw new ArgumentNullException(nameof(oldChars));
                        if (oldChars.Length == 1) return input.Replace(oldChars[0], newChar);
                        if (oldChars.Length == 0) return input;
                    
                        replaceMap = replaceMap ?? new bool[char.MaxValue + 1];
                        foreach (var oldChar in oldChars)
                        {
                            replaceMap[oldChar] = true;
                        }
                    
                        try
                        {
                            var count = input.Length;
                            var output = input.ToCharArray();
                            for (var i = 0; i < count; i++)
                            {
                                if (replaceMap[input[i]])
                                {
                                    output[i] = newChar;
                                }
                            }
                    
                            return new string(output);
                        }
                        finally
                        {
                            foreach (var oldChar in oldChars)
                            {
                                replaceMap[oldChar] = false;
                            }
                        }
                    }
                    

                    对我来说,这最多是两个分配给实际输入字符串的工作。由于某些原因,StringBuilder 对我来说要慢得多。它比 LINQ 变体快 2 倍。

                    【讨论】:

                      【解决方案14】:

                      无“替换”(仅限 Linq):

                          string myString = ";,\r\t \n\n=1;;2,,3\r\r4\t\t5  6\n\n\n\n7=";
                          char NoRepeat = '\n';
                          string ByeBye = ";,\r\t ";
                          string myResult = myString.ToCharArray().Where(t => !"STOP-OUTSIDER".Contains(t))
                                       .Select(t => "" + ( ByeBye.Contains(t) ? '\n' : t))
                                        .Aggregate((all, next) => (
                                            next == "" + NoRepeat && all.Substring(all.Length - 1) == "" + NoRepeat
                                            ? all : all  + next ) );
                      

                      【讨论】:

                      • 我很惊讶许多已发布的解决方案返回的输入字符串结果不正确 ";,\r\t \n\n=1;;2,,3\r\ r4\t\t5 6\n\n\n\n7="。由于某种原因,\n 在结果中重复,和/或第一个字符不是 \n。 (来自作者 .Replace("\n\n", "\n")
                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2015-03-15
                      • 1970-01-01
                      • 1970-01-01
                      • 2016-09-10
                      相关资源
                      最近更新 更多