高效的关键字过滤及查找算法(Trie KO Hash)

   在实际当中,聊天过滤工作 最终可能会消耗掉可观的(有时是惊人的)资源总量--无论是从开发方面还是从原始计算能力方面来说。有些知名SNS类游戏仅在聊天过滤上就耗费了超过50%的计算资源。因此对于这部分的优化工作就显得特别重要。

最近做游戏服务器,需要用到聊天过滤,首先想到的是使用HashSet<string> 的方式。

基本的思路是把所有需要过滤的关键字保存在HashSet<string>中。

对于用户输入的消息进行暴力分割。

如:今天你好吗

单个字符分割为: 今/天/你/好/吗

2个字符分割为: 今天/天你/你好/好吗

3个字符分割为: 今天你/天你好/你好吗

..........以此类推

对于大的字符串。我们对分割把分割的长度限制为需过滤关键字的长度。

然后对分割的了字符串进行匹配。

 

View Code
    public class HashFilter
    {
        
int m_maxLen; //关键字最大长度
        HashSet<string> m_keys = new HashSet<string>();
        
        
/// <summary>
        
/// 插入新的Key.
        
/// </summary>
        
/// <param name="name"></param>
        public void AddKey(string key)
        {
            
if ((!string.IsNullOrEmpty(key)) && m_keys.Add(key) && key.Length > m_maxLen)
            {
                m_maxLen 
= key.Length;
            }
        }
        
/// <summary>
        
/// 检查是否包含非法字符
        
/// </summary>
        
/// <param name="text">输入文本</param>
        
/// <returns>找到的第1个非法字符.没有则返回string.Empty</returns>
        public string FindOne(string text)
        {
            
for (int len = 1; len <= text.Length; len++)
            {
                
int maxIndex = text.Length - len;
                
for (int index = 0; index <= maxIndex; index++)
                {
                    
string key = text.Substring(index, len);
                    
if (m_keys.Contains(key))
                    {
                        
return key;
                    }
                }
            }
            
return string.Empty;
        }
    }

这个方式有个缺点.就是使用 String.Substring会创建大量临时对象.

即便是使用了最大长度进行分割字符串的控件.在需要过滤的字符串较长时,还是不见得高效.

 

Trie,又称单词查找树键树,是一种形结构,是一种哈希树的变种。它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。

我们先来看一个Trie结构的例子

在这个Trie结构中,保存了A、to、tea、ted、ten、i、in、inn这8个字符串

首先看我们如何实现这个结构:

 public class TrieFilter
    {
        
private Char m_key;
        
private Dictionary<Char, TrieFilter> m_values;
        //根节点,不包含字符,即m_key='\0';
        
public TrieFilter()
        {
            m_values 
= new Dictionary<Char, TrieFilter>();
        }
        //用于创建子节点
        TrieFilter(Char key)
        {
            m_key 
= key;
            m_values 
= new Dictionary<Char, TrieFilter>();
        }
        
/// <summary>
        
/// 添加关键字
        
/// </summary>
        
/// <param name="key"></param>
        public void AddKey(string key)
        {
            
if (string.IsNullOrEmpty(key))
            {
                
return;
            }
            TrieFilter node 
= this;
            
foreach (var c in key)
            {
                TrieFilter subnode;
                
if (!node.m_values.TryGetValue(c, out subnode))
                {
                    subnode 
= new TrieFilter(c);
                    node.m_values.Add(c, subnode);
                }
                node 
= subnode;
            }
            //最后的结点,表示单词结束,用‘\0’表示,指向一个空对象
            node.m_values[
'\0'= null;
        }
    }
}

 这样。一个C#的Trie结构就表示完成。。

下面我们来看看如何实现关键字查找

 

 /// <summary>
        
/// 检查是否包含非法字符
        
/// </summary>
        
/// <param name="text">输入文本</param>
        
/// <returns>找到的第1个非法字符.没有则返回string.Empty</returns>
        public string FindOne(string text)
        {
            
for (int i = 0; i < text.Length; i++)
            {
                TrieFilter node;
                
if (m_values.TryGetValue(text[i], out node))
                {
                    
for (int j = i + 1; j < text.Length; j++)
                    {
                        
if (node.m_values.TryGetValue(text[j], out node))
                        {
                            
if (node.m_values.ContainsKey('\0'))
                            {
                                
return text.Substring(i, (j + 1 - i));
                            }
                        }
                        
else
                        {
                            
break;
                        }
                    }
                }
            }
            
return string.Empty;
        }

是不是很简单呢?

把由蛮力匹配转变成基于Trie的匹配方案会极大的节省执行时间,这个比较对较长字符串进行匹配时更加明显。

对于长度在20左右的字符串。Tire的匹配速度比Hash的方式快了近10倍。

下面附上完整的代码下载

里面的BadWord.txt包括了7000多个敏感关键字。 

完整的代码及性能对比测试下载:http://files.cnblogs.com/yeerh/FilterTest.rar

 

希望本文能对你有所帮助。欢迎拍砖。

 

 

posted @ 2011-08-24 22:27 边城浪 阅读(...) 评论(...) 编辑 收藏