【问题标题】:Clean the string? is there any better way of doing it?清理绳子?有没有更好的方法呢?
【发布时间】:2012-07-08 21:27:40
【问题描述】:

我正在使用这种方法来清理字符串

public static string CleanString(string dirtyString)
{
    string removeChars = " ?&^$#@!()+-,:;<>’\'-_*";
    string result = dirtyString;

    foreach (char c in removeChars)
    {
        result = result.Replace(c.ToString(), string.Empty);
    }

    return result;
}

此方法工作正常。但此方法存在性能故障。每次我传递字符串时,每个字符都会进入循环,如果我有一个大字符串,那么返回对象会花费太多时间。

还有其他更好的方法来做同样的事情吗?就像在 LINQ 或 JQUERY / Javascript 中一样

任何建议都将不胜感激。

【问题讨论】:

  • "cleaning"一个字符串的目的是什么?
  • 我基本上是在处理很多 Qurystring 值...
  • 将所有字符放入一个正则表达式的字符类中,然后一次性全部替换。
  • 定义“更好”。任何解决方案都会对字符进行循环。您的代码中的缺点是过度创建字符串对象,而不是每个字符的循环。
  • @patel.milanb 那么你要找的是HttpUtility.HtmlEncode 不是字符串清理

标签: c# asp.net string linq


【解决方案1】:

我在我当前的项目中使用它,它工作正常。它需要一个句子,它删除所有非字母数字字符,然后返回包含第一个字母大写的所有单词和小写其他所有单词的句子。也许我应该称它为 SentenceNormalizer。命名很难:)

    internal static string StringSanitizer(string whateverString)
{
    whateverString = whateverString.Trim().ToLower();
    Regex cleaner = new Regex("(?:[^a-zA-Z0-9 ])", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled);
    var listOfWords = (cleaner.Replace(whateverString, string.Empty).Split(' ', StringSplitOptions.RemoveEmptyEntries)).ToList();
    string cleanString = string.Empty;
    foreach (string word in listOfWords)
    {
        cleanString += $"{word.First().ToString().ToUpper() + word.Substring(1)} ";
    }
    return cleanString;
}

【讨论】:

    【解决方案2】:

    好的,考虑以下测试:

    public class CleanString
    {
        //by MSDN http://msdn.microsoft.com/en-us/library/844skk0h(v=vs.71).aspx
        public static string UseRegex(string strIn)
        {
            // Replace invalid characters with empty strings.
            return Regex.Replace(strIn, @"[^\w\.@-]", "");
        }
    
        // by Paolo Tedesco
        public static String UseStringBuilder(string strIn)
        {
            const string removeChars = " ?&^$#@!()+-,:;<>’\'-_*";
            // specify capacity of StringBuilder to avoid resizing
            StringBuilder sb = new StringBuilder(strIn.Length);
            foreach (char x in strIn.Where(c => !removeChars.Contains(c)))
            {
                sb.Append(x);
            }
            return sb.ToString();
        }
    
        // by Paolo Tedesco, but using a HashSet
        public static String UseStringBuilderWithHashSet(string strIn)
        {
            var hashSet = new HashSet<char>(" ?&^$#@!()+-,:;<>’\'-_*");
            // specify capacity of StringBuilder to avoid resizing
            StringBuilder sb = new StringBuilder(strIn.Length);
            foreach (char x in strIn.Where(c => !hashSet.Contains(c)))
            {
                sb.Append(x);
            }
            return sb.ToString();
        }
    
        // by SteveDog
        public static string UseStringBuilderWithHashSet2(string dirtyString)
        {
            HashSet<char> removeChars = new HashSet<char>(" ?&^$#@!()+-,:;<>’\'-_*");
            StringBuilder result = new StringBuilder(dirtyString.Length);
            foreach (char c in dirtyString)
                if (removeChars.Contains(c))
                    result.Append(c);
            return result.ToString();
        }
    
        // original by patel.milanb
        public static string UseReplace(string dirtyString)
        {
            string removeChars = " ?&^$#@!()+-,:;<>’\'-_*";
            string result = dirtyString;
    
            foreach (char c in removeChars)
            {
                result = result.Replace(c.ToString(), string.Empty);
            }
    
            return result;
        }
    
        // by L.B
        public static string UseWhere(string dirtyString)
        {
            return new String(dirtyString.Where(Char.IsLetterOrDigit).ToArray());
        }
    }
    
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            var dirtyString = "sdfdf.dsf8908()=(=(sadfJJLef@ssyd€sdöf////fj()=/§(§&/(\"&sdfdf.dsf8908()=(=(sadfJJLef@ssyd€sdöf////fj()=/§(§&/(\"&sdfdf.dsf8908()=(=(sadfJJLef@ssyd€sdöf";
            var sw = new Stopwatch();
    
            var iterations = 50000;
    
            sw.Start();
            for (var i = 0; i < iterations; i++)
                CleanString.<SomeMethod>(dirtyString);
            sw.Stop();
            Debug.WriteLine("CleanString.<SomeMethod>: " + sw.ElapsedMilliseconds.ToString());
            sw.Reset();
    
            ....
            <repeat>
            ....       
        }
    }
    

    输出

    CleanString.UseReplace: 791
    CleanString.UseStringBuilder: 2805
    CleanString.UseStringBuilderWithHashSet: 521
    CleanString.UseStringBuilderWithHashSet2: 331
    CleanString.UseRegex: 1700
    CleanString.UseWhere: 233
    

    结论

    你使用哪种方法可能并不重要。

    连续调用 50000(!) 次时,禁食 (UseWhere: 233ms) 和最慢 (UseStringBuilder: 2805ms) 方法之间的时间差为 2572ms。如果不经常运行该方法,您可能不需要关心它。

    但如果你这样做,请使用UseWhere 方法(由 L.B 编写);但也请注意,它略有不同。

    【讨论】:

    • 这会给你的机器上的return new String(dirtyString.Where(Char.IsLetterOrDigit).ToArray())带来什么?
    • 速度很快。 50000 次迭代:182ms(下一个是UseStringBuilderWithHashSet2 266ms)
    • 只是为了记录,对于 UseStringBuilderWithHashSet 和 UseStringBuilderWithHashSet2 测试将是if (!removeChars.Contains(c))
    • L.B 的 UseWhere 方法可以扩展以允许附加字符吗?像这样: public static string UseWhereExtended(string dirtyString) { IEnumerable stringQuery = from ch in dirtyString where char.IsLetterOrDigit(ch) || ch == '.' || ch == ',' || ch == '\'' || ch == '\"' || ch == '?' || ch == '!'选择通道;返回新字符串(stringQuery.ToArray()); }
    • 我认为UseStringBuilderWithHashSet2有错误,if(removeChars.Contains(c))不应该是if(!removeChars.Contains(c))吗?
    【解决方案3】:

    我无法花时间对此进行酸性测试,但这条线实际上并没有按照需要清理斜线。

    HashSet<char> removeChars = new HashSet<char>(" ?&^$#@!()+-,:;<>’\'-_*");
    

    我必须单独添加斜杠并转义反斜杠

    HashSet<char> removeChars = new HashSet<char>(" ?&^$#@!()+-,:;<>’'-_*");
    removeChars.Add('/');
    removeChars.Add('\\');
    

    【讨论】:

      【解决方案4】:

      这个更快!
      使用:

      string dirty=@"tfgtf$@$%gttg%$% 664%$";
      string clean = dirty.Clean();
      
      
          public static string Clean(this String name)
          {
              var namearray = new Char[name.Length];
      
              var newIndex = 0;
              for (var index = 0; index < namearray.Length; index++)
              {
                  var letter = (Int32)name[index];
      
                  if (!((letter > 96 && letter < 123) || (letter > 64 && letter < 91) || (letter > 47 && letter < 58)))
                      continue;
      
                  namearray[newIndex] = (Char)letter;
                  ++newIndex;
              }
      
              return new String(namearray).TrimEnd();
          }
      

      【讨论】:

        【解决方案5】:

        如果您追求的是纯粹的速度和效率,我建议您这样做:

        public static string CleanString(string dirtyString)
        {
            HashSet<char> removeChars = new HashSet<char>(" ?&^$#@!()+-,:;<>’\'-_*");
            StringBuilder result = new StringBuilder(dirtyString.Length);
            foreach (char c in dirtyString)
                if (!removeChars.Contains(c)) // prevent dirty chars
                    result.Append(c);
            return result.ToString();
        }
        

        RegEx 无疑是一个优雅的解决方案,但它增加了额外的开销。通过指定字符串生成器的起始长度,它只需要分配一次内存(最后为ToString 分配第二次)。这将减少内存使用并提高速度,尤其是在较长的字符串上。

        但是,作为 L.B.说,如果你使用它来正确编码绑定到 HTML 输出的文本,你应该使用 HttpUtility.HtmlEncode 而不是自己做。

        【讨论】:

        • removeChars.IndexOfO(n) 操作。 HashSet 会更好。
        • output 应该是 result。你也可以省略.ToCharArray(),因为字符串实现了IEnumerable&lt;char&gt;
        • Grrr.. 谢谢@BigYellowCactus。不知道我是怎么错过的。
        • 你也可以使用单行return new String(dirtyString.Where(c =&gt; !removeChars.Contains(c)).ToArray());
        • @Qweick 好吧,空格字符已经包含在内,但是如果您想包含任何其他空格字符,您可以将它们连接到字符串(例如“...” & vbTab)。
        【解决方案6】:

        也许先解释“为什么”然后解释“是什么”会有所帮助。性能下降的原因是因为 c# 会复制并替换每次替换的字符串。根据我在 .NET 中使用 Regex 的经验,它并不总是更好 - 尽管在大多数情况下(我认为包括这个)它可能会工作得很好。

        如果我真的需要性能,我通常不会靠运气,而只是告诉编译器我想要什么:即:创建一个具有上限字符数的字符串并复制其中的所有字符需要。也可以用开关/案例或数组替换散列集,在这种情况下,您最终可能会得到一个跳转表或数组查找——这甚至更快。

        “务实”的最佳但快速的解决方案是:

        char[] data = new char[dirtyString.Length];
        int ptr = 0;
        HashSet<char> hs = new HashSet<char>() { /* all your excluded chars go here */ };
        foreach (char c in dirtyString)
            if (!hs.Contains(c))
                data[ptr++] = c;
        return new string(data, 0, ptr);
        

        顺便说一句:当您想要处理高代理 Unicode 字符时,此解决方案不正确 - 但可以轻松调整以包含这些字符。

        -斯特凡。

        【讨论】:

          【解决方案7】:

          我不知道在性能方面,使用 Regex 或 LINQ 是否会有所改进。
          可能有用的方法是使用 StringBuilder 创建新字符串,而不是每次都使用 string.Replace

          using System.Linq;
          using System.Text;
          
          static class Program {
              static void Main(string[] args) {
                  const string removeChars = " ?&^$#@!()+-,:;<>’\'-_*";
                  string result = "x&y(z)";
                  // specify capacity of StringBuilder to avoid resizing
                  StringBuilder sb = new StringBuilder(result.Length);
                  foreach (char x in result.Where(c => !removeChars.Contains(c))) {
                      sb.Append(x);
                  }
                  result = sb.ToString();
              }
          }
          

          【讨论】:

          • 这当然有帮助。使用 StringBuilder 类为我开辟了一个新思路
          • removeChars.ContainsO(n)HashSet 会更好。
          【解决方案8】:
          【解决方案9】:

          使用正则表达式[?&amp;^$#@!()+-,:;&lt;&gt;’\'-_*] 替换为空字符串

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2014-06-04
            • 2011-07-13
            • 2016-07-21
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多