优化string.Replace方法

时间:2013-10-19 05:51:36

标签: c# string

我有一个200多个单词的列表,这些单词在网站上是不允许的。下面的string.Replace方法需要大约80毫秒。如果我将s < 1000增加10.00到s < 10,000,则此延迟会增加到约834毫秒,增加10.43。我担心这个函数的可伸缩性,特别是如果列表的大小增加。我被告知字符串是不可变的,text.Replace()在内存中创建200个新字符串。是否有类似于Stringbuilder的内容?

List<string> FilteredWords = new List<string>();
FilteredWords.Add("RED");
FilteredWords.Add("GREEN");
FilteredWords.Add("BLACK");
for (int i = 1; i < 200; i++)
{ FilteredWords.Add("STRING " + i.ToString()); }

string text = "";

//simulate a large dynamically generated html page
for (int s = 1; s < 1000; s++)
{ text += @"Lorem ipsum dolor sit amet, minim BLACK cetero cu nam.
            No vix platonem sententiae, pro wisi congue graecis id, GREEN assum interesset in vix.
            Eum tamquam RED pertinacia ex."; }

// This is the function I seek to optimize
foreach (string s in FilteredWords)
{ text = text.Replace(s, "[REMOVED]"); }

4 个答案:

答案 0 :(得分:2)

如果您希望大多数文本比扫描整个文本相对更好,首先匹配单词可能是更好的方法。您还可以同时对单词文本进行标准化,以捕获一些标准替换。

即。扫描字符串通过匹配单个单词(即正则表达式,如"\w+"),而不是每个检测到的单词查找(可能标准化的值)在要替换的单词的字典中。

您可以先扫描一下以获取“要替换的单词”列表,然后再单独替换单个单词,或者同时扫描并构建生成的字符串(使用StringBuilderStreamWriter,显然不是String.Concat / +)。

注意:Unicode提供了大量优秀的字符,因此不要指望您的努力非常成功。即试着在下面的文字中找到“酷”:“你是сооl”。

示例代码(依赖Regex.Replace进行标记化并构建字符串,并HashSet进行匹配)。

var toFind = FilteredWords.Aggregate(
      new HashSet<string>(), (c, i) => { c.Add(i); return c;});

text = new Regex(@"\w+")
   .Replace(text, m => toFind.Contains(m.Value) ? "[REMOVED]" : m.Value));

答案 1 :(得分:2)

使用StringBuilder.Replace并尝试将其作为批处理操作。也就是说,您应该尝试仅创建一次StringBuilder,因为它有一些开销。它不一定快得多,但它的内存效率会更高。

您也应该只进行一次卫生,而不是每次请求数据。如果您正在从数据库中读取数据,那么在将数据插入数据库时​​应该考虑将其清理一次,因此在阅读并将其显示到页面时,可以做的工作较少。

答案 2 :(得分:1)

可能有更好的方法,但这就是解决问题的方法。

您需要创建一个包含要替换的单词词典的树结构。这个班可能是这样的:

public class Node 
{
    public Dictionary<char, Node> Children;
    public bool IsWord;
}

为儿童使用字典可能不是最佳选择,但它提供了最简单的示例。此外,您还需要一个构造函数来初始化Children字段。 IsWord字段用于处理编辑的“单词”可能是另一个编辑的“单词”的前缀的可能性。例如,如果要删除“红色”和“补救”。

您将从每个替换单词中的每个字符构建树。例如:

public void AddWord ( string word ) 
{
    // NOTE: this assumes word is non-null and contains at least one character...

    Node currentNode = Root;

    for (int iIndex = 0; iIndex < word.Length; iIndex++)
    {
        if (currentNode.Children.ContainsKey(word[iIndex])))
        {
            currentNode = currentNode.Children[word[iIndex];
            continue;
        }

        Node newNode = new Node();
        currentNode.Children.Add(word[iIndex], newNode);
        currentNode = newNode;
    }

    // finished, mark the last node as being a complete word..
    currentNode.IsWord = true;
}

你需要在那里的某处处理区分大小写。此外,您只需要构建一次树,之后您可以从任意数量的线程中使用它而不必担心锁定,因为您只会从中读取它。 (基本上,我说的是:将它存放在静止的地方。)

现在,当您准备从字符串中删除单词时,您需要执行以下操作:

  • 创建一个StringBuilder实例来存储结果
  • 解析源字符串,查找“单词”的开始和结束。你如何定义“单词”将很重要。为简单起见,我建议从Char.IsWhitespace开始定义单词分隔符。
  • 一旦确定一系列字符是“单词”,从树的根开始,找到与“单词”中第一个字符关联的子节点。
  • 如果找不到子节点,则整个单词将添加到StringBuilder
  • 如果找到子节点,则继续使用与当前节点的子节点匹配的下一个字符,直到您用完字符或节点外。
  • 如果到达“单词”的末尾,请检查最后一个节点的IsWord字段。如果true该词被排除在外,请不要将其添加到StringBuilder。如果IsWordfalse,则不会替换该字词,并将其添加到StringBuilder
  • 重复直到用尽输入字符串。

您还需要在StringBuilder中添加单词分隔符,希望在解析输入字符串时这一点很明显。如果你小心只在输入字符串中使用start和stop索引,你应该能够解析整个字符串而不创建任何垃圾字符串。

完成所有这些操作后,请使用StringBuilder.ToString()获取最终结果。

您可能还需要考虑Unicode代理代码点,但您可以可能离开而不必担心它。

请注意,我直接在此输入此代码,因此可能包含语法错误,拼写错误和其他意外误导。

答案 3 :(得分:0)

真正的正则表达式解决方案是:

var filteredWord = new Regex(@"\b(?:" + string.Join("|", FilteredWords.Select(Regex.Escape)) + @")\b", RegexOptions.Compiled);
text = filteredWord.Replace(text, "[REMOVED]");

我不知道这是否更快(但请注意,它也只替换整个单词)。