[英]Optimize string.Replace method
我列出了网站上不允许使用的200多个单词。 下面的string.Replace
方法大约需要string.Replace
。 如果我将s < 1000
乘以10.00到s < 10,000
此延迟将达到〜834ms,即增加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]"); }
如果您希望大多数文本比首先扫描整个文本以查找匹配的单词相对好,那是更好的方法。 您还可以同时规范化单词文本,以捕获一些标准替代品。
也就是说,通过匹配单个单词(即正则表达式,如"\\w+"
)来扫描字符串,而不是替换要替换的单词字典中的每个检测到的单词查找(潜在的归一化值)。
您可以先进行扫描以获取“要替换的单词”列表,然后再进行替换,或者同时扫描并生成结果字符串(使用StringBuilder
或StreamWriter
,显然不是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));
使用StringBuilder.Replace
并尝试将其作为批处理操作。 也就是说,您应该只创建一次StringBuilder
,因为它有一些开销。 它不一定会快很多,但会提高内存效率。
您也应该只执行一次这种卫生处理,而不是每次请求数据时进行一次。 如果要从数据库中读取数据,则应考虑在将数据插入数据库中时对其进行一次清理,因此在将其读取并显示到页面上时需要做的工作较少。
也许有更好的方法,但这就是我要解决的方法。
您将需要创建一个树结构,其中包含要替换的单词词典。 该类可能类似于:
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;
}
您需要在其中的某个地方处理区分大小写的问题。 同样,您只需要构建一次树,之后就可以在任意数量的线程中使用它,而不必担心锁定,因为您只会从树中读取。 (基本上,我是说:将其存储在静态位置。)
现在,当您准备从字符串中删除单词时,您需要执行以下操作:
Char.IsWhitespace
开始定义单词分隔符。 StringBuilder
IsWord
字段。 如果为true
,则排除该单词,请勿将其添加到StringBuilder
。 如果IsWord
为false
,则不替换该单词,而是将其添加到StringBuilder
您还需要在StringBuilder
添加单词分隔符,希望在解析输入字符串时显而易见。 如果您仅在输入字符串中使用开始索引和停止索引,则应该能够解析整个字符串而无需创建任何垃圾字符串。
完成所有这些操作后,使用StringBuilder.ToString()
获得最终结果。
您可能还需要考虑Unicode代理码点,但你也许可以蒙混过关,而不必担心它。
当心,我直接在此处键入此代码,因此可能包括语法错误,错别字和其他意外的误导。
真正的正则表达式解决方案是:
var filteredWord = new Regex(@"\b(?:" + string.Join("|", FilteredWords.Select(Regex.Escape)) + @")\b", RegexOptions.Compiled);
text = filteredWord.Replace(text, "[REMOVED]");
我不知道这是否更快(但请注意,它也只能替换整个单词)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.