我正在尝试模糊大量数据。我已经创建了一个我要替换的单词(标记)列表,我正在使用StringBuilder类逐个替换单词,如下所示:
var sb = new StringBuilder(one_MB_string);
foreach(var token in tokens)
{
sb.Replace(token, "new string");
}
这很慢!我能做些什么简单的事情来加速它吗?
令牌是一个大约一千个字符串的列表,每个字符串长度为5到15个字符。
答案 0 :(得分:13)
不是在一个巨大的字符串中进行替换(这意味着你要移动大量数据),而是一次完成字符串并替换一个令牌。
创建一个包含每个标记的下一个索引的列表,找到第一个标记,然后将文本复制到结果,然后替换标记。然后检查字符串中下一个令牌的出现位置,以使列表保持最新。重复,直到找不到更多的标记,然后将剩余的文本复制到结果中。
我做了一个简单的测试,这个方法在208毫秒内对一个1000000字符串进行了125000次替换。
Token和TokenList类:
public class Token {
public string Text { get; private set; }
public string Replacement { get; private set; }
public int Index { get; set; }
public Token(string text, string replacement) {
Text = text;
Replacement = replacement;
}
}
public class TokenList : List<Token>{
public void Add(string text, string replacement) {
Add(new Token(text, replacement));
}
private Token GetFirstToken() {
Token result = null;
int index = int.MaxValue;
foreach (Token token in this) {
if (token.Index != -1 && token.Index < index) {
index = token.Index;
result = token;
}
}
return result;
}
public string Replace(string text) {
StringBuilder result = new StringBuilder();
foreach (Token token in this) {
token.Index = text.IndexOf(token.Text);
}
int index = 0;
Token next;
while ((next = GetFirstToken()) != null) {
if (index < next.Index) {
result.Append(text, index, next.Index - index);
index = next.Index;
}
result.Append(next.Replacement);
index += next.Text.Length;
next.Index = text.IndexOf(next.Text, index);
}
if (index < text.Length) {
result.Append(text, index, text.Length - index);
}
return result.ToString();
}
}
使用示例:
string text =
"This is a text with some words that will be replaced by tokens.";
var tokens = new TokenList();
tokens.Add("text", "TXT");
tokens.Add("words", "WRD");
tokens.Add("replaced", "RPL");
string result = tokens.Replace(text);
Console.WriteLine(result);
输出:
This is a TXT with some WRD that will be RPL by tokens.
注意:此代码不处理重叠令牌。例如,如果您有令牌“菠萝”和“苹果”,则代码无法正常运行。
编辑:
要使代码与重叠标记一起使用,请替换以下行:
next.Index = text.IndexOf(next.Text, index);
使用此代码:
foreach (Token token in this) {
if (token.Index != -1 && token.Index < index) {
token.Index = text.IndexOf(token.Text, index);
}
}
答案 1 :(得分:5)
好的,你明白为什么要花很长时间,对吧?
您有1 MB的字符串,并且对于每个令牌,replace将迭代1 MB并创建一个新的1 MB副本。好吧,不是一个精确的副本,因为找到的任何令牌都被替换为新的令牌值。但对于每个令牌,您正在读取1 MB,新增1 MB存储空间,并写入1 MB。
现在,我们能想到更好的方法吗?如何而不是为每个令牌迭代1 MB字符串,我们改为走一次。
在走之前,我们将创建一个空输出字符串。
当我们遍历源字符串时,如果我们找到一个令牌,我们将向前跳转token.length()
个字符,并写出混淆的令牌。否则我们将继续下一个角色。
基本上,我们将过程内部转出,在长字符串上执行for循环,并在每个点寻找令牌。为了快速实现这一点,我们需要对令牌进行快速循环,因此我们将它们放入某种关联数组(一组)中。
我明白为什么长久没事, 但不确定修复。每1 MB 我正在表演的字符串 替换,我有1到2千 tokans我想替换。走路 字符寻找任何人 一千个令牌似乎没有 更快
一般来说,编程需要花费最长时间?新的记忆。
现在当我们创建一个StringBuffer时,可能发生的是分配了一些空间(比如64个字节,每当我们追加超过它的当前容量时,它可能会说它的空间加倍。然后复制旧的字符缓冲区到新的字符缓冲区。(我们可以将C的realloc重新分配,而不必复制。)
因此,如果我们从64字节开始,要达到1 MB,我们分配并复制: 然后是128,然后是128,然后是256,然后是512,然后是1024,然后是2048 ......我们这样做了20次,最多可以达到1 MB。到了这里,我们已经分配了1 MB只是为了扔掉它。
通过使用类似于C ++的reserve()
函数的预分配,至少可以让我们一次完成所有这些操作。但是每个令牌仍然是一次性的。您至少为每个令牌生成一个1 MB的临时字符串。如果你有2000个令牌,你将分配大约20亿字节的内存,所有内存最终都是1 MB。每个1 MB的一次性包含前一个结果字符串的转换,并应用当前令牌。
这就是为什么花了这么长时间。
现在是的,决定在每个角色上应用哪个令牌(如果有的话)也需要时间。你可能希望使用一个正则表达式,它在内部构建一个状态机来运行所有可能性,而不是像我最初建议的那样设置查找。但真正杀死你的是分配所有内存的时间,以及2000个1 MB字符串的副本。
丹·吉布森建议:对您的令牌进行排序,这样您就不必这样做了 每个寻找一千个令牌 字符。排序需要一些 时间,但它可能会结束 因为你没有必要更快 每个搜索数千个令牌 字符。
这是我将它们放入关联数组(例如Java HashSet)的原因。但另一个问题是匹配,例如,如果一个令牌是“a”而另一个是“an” - 如果有任何共同的前缀,也就是说,我们如何匹配?
这就是Keltex的答案派上用场的地方:他将匹配委托给Regex,这是一个好主意,正如Regex已定义(贪婪匹配)并实现如何做到这一点。匹配完成后,我们可以检查捕获的内容,然后使用Java Map(也是一个关联数组)来查找匹配的,未经过模糊处理的标记的混淆标记。
我想把我的答案集中在不仅仅是如何解决这个问题,而是为什么首先出现问题。
答案 2 :(得分:2)
如果您可以通过正则表达式找到您的令牌,您可以执行以下操作:
RegEx TokenFinder = new Regex("(tokencriteria)");
string newstring = myRegEx.Replace(one_MB_string, new MatchEvaluator(Replacer));
然后将Replacer定义为:
private string Replacer(Match match)
{
string token= match.Groups[1].Value;
return GetObfuscatedString(token);
}
答案 3 :(得分:1)
一次构建一个字符串是否更快,只有在需要时才更换?为此,GetObfuscatedString()
可以这样实现:
string GetObfuscatedString(string token)
{
if (TokenShouldBeReplaced(token))
return ReplacementForToken(token)
else
return token;
}
现在,您可以将每个令牌添加到构建器中,如下所示:
StringBuilder sb = new StringBuilder(one_MB_string.Length);
foreach (string token in tokens)
{
sb.Append(da.GetObfuscatedString(token));
}
你只需要对字符串进行一次传递,它可能会更快。