zoukankan      html  css  js  c++  java
  • 写一个高性能的敏感词检测组件

    最近写了一个高性能的敏感词检测组件【ToolGood.Words】。

    一、高性能,它的效率到底有多快?

    如果将正则表达式的算法效率设为1,高性能可达到正则表达式的1.5万倍。

    二、选一个巧妙的算法:

    AC自动机Aho-Corasick Automation)算法在1975年产生于贝尔实验室,是著名的多模式匹配算法之一;一个常见的例子就是给定N个单词,给定包含M个字符的文章,要求确定多少个给定的单词在文章中出现过;AC自动机在匹配文本时不需要回溯,处理时间复杂度与pattern无关,仅是target的长度O(N);构建AC自动机的时间复杂度。

    AC自动机的构建主要由三个步骤:

    2.1、针对所有模式串构建Trie树。

    2.2、针对所有Trie树上的接点构建Fail指针:Fail指针指向的是如果当前节点匹配失败,则从通过Fail指针指向的新的节点开始匹配,但新的节点必须满足所在在新节点模式串的前缀必须是转移前的节点所在模式串的子串,也就是已经匹配成功的部分。

    2.3、正式匹配过程:
    a)从Trie树root节点开始,每次根据读入的字符沿着自动机向下移动。
    b)当读入的字符,在分支中不存在时,递归走Fail指针路径。如果走Fail指针路径走到了root节点,则跳过该字符,处理下一个字符。
    c)因为AC自动机是沿着文本移动的,所以在读取完所有输入文本后,最后递归走失败路径,直到到达根节点,这样可以检测出所有的模式。

    三、优化AC自动机算法

    细看AC自动算法匹配过程,在(b)步骤我们会发现节点匹配失败后会顺Fail指针继续执行,这个步骤可以优化,优化Fail指针,将相同的节点合并成一个同一个节点。

    四、填平性能低洼地

    优化AC自动机算法后,测试代码后会发现代码性能没有太大提升。

    经测试发现,TrieNode获取下一节点的方法(TryGetValue)占用了90%的计算量。

    分析Trie树有两个特点:1root节点下有大量子节点,2、中间节点的子节点数量很少。

    优化TryGetValue方法:

    1、root的子节点转成数组类型

    2、修改TryGetValue方法内:

        public class TrieNode
        {
            public bool End { get; set; }
            public List<string> Results { get; set; }
            private Dictionary<char, TrieNode> m_values;
            private uint minflag = uint.MaxValue;
            private uint maxflag = uint.MinValue;
            //....
            public bool TryGetValue(char c, out TrieNode node)
            {
                if (minflag <= (uint)c && maxflag >= (uint)c) {
                    return m_values.TryGetValue(c, out node);
                }
                node = null;
                return false;
            }
        }
    

    五、优化后的代码段

    public class StringSearch
    {
            TrieNode _root = new TrieNode();
            TrieNode[] _first = new TrieNode[char.MaxValue + 1];
            //.....
            public bool ContainsAny(string text)
            {
                TrieNode ptr = null;
                for (int i = 0; i < text.Length; i++) {
                    TrieNode tn;
                    if (ptr == null) {
                        tn = _first[text[i]];
                    } else {
                        if (ptr.TryGetValue(text[i], out tn) == false) {
                            tn = _first[text[i]];
                        }
                    }
                    if (tn != null) {
                        if (tn.End) {
                            return true;
                        }
                    }
                    ptr = tn;
                }
                return false;
            }
    }
    

     六、性能对比图,10W次对比

    ToolGood.Words内的非法词(敏感词)检测类:StringSearchWordsSearchIllegalWordsSearchIllegalWordsQuickSearch;

    注:C#自带正则很慢,StringSearch.ContainsAnyRegex.IsMatch效率的1.5万倍。

    Regex.Matches的运行方式跟IQueryable的类似,只返回MatchCollection,还没有计算。

    TrieFilter,FastFilter来源: http://www.cnblogs.com/yeerh/archive/2011/10/20/2219035.html

    七、开源项目

    码云: https://git.oschina.net/toolgood/ToolGood.Words

    GitHub: https://github.com/toolgood/ToolGood.Words

  • 相关阅读:
    XSD文件生成C#VO实体类
    WPF根据Oracle数据库的表,生成CS文件小工具
    【求助】WPF 在XP下 有的Textbox光标会消失
    【转】oracle中in和exists的区别
    Spire.DOC生成表格测试
    【转】C#调用Windows图片和传真查看器打开图片
    WPF MVVM下做发送短信小按钮
    SignalR Progress
    C# readonly
    Settings.settings
  • 原文地址:https://www.cnblogs.com/toolgood/p/6284718.html
Copyright © 2011-2022 走看看