天天看点

安全开发第二讲-如何实现敏感词组的快速匹配

安全开发第二讲-如何实现敏感词组的快速匹配

敏感词词组的例子模式

今天&早上&吃饭      

词组模式可以将句子匹配词组化,从而减少纯句子匹配带来的漏报

设待检测文本的大小为分词之后存在500个词语

假如你手上有百万级的检测词组库,你能想到哪些方案快速检测(1秒内响应)?

1、set集合

首先我们来想一想集合是否可行,首先我们可以将词组通过&拆分,然后形成词语,每个词组是由多个词语构成的

AA&BB&CC => AA BB CC      

拆分之后的词语对象应该至少包含两个属性:词语名+词语所在的词组id列表,这样的对应关系需要额外的结构来存储。然后百万级的词库将产生百万级的对象(有多少个词就会产生多少个这样的对象),然后我们把这些词语全部存储到一个set集合中。我们知道,在Java8中,HashSet是基于HashMap实现的,而HashMap是基于链表+红黑树实现的,初始时,HashMap内部是链表的结构,当达到一定阈值的时候,就会转成红黑树来存储节点。同时,HashMap为了减少碰撞率,需要通过荷载因子进行扩容(即达到多少的荷载就进行一次扩容),当HashMap中存储的节点越多,扩容带来的影响就越大。在我们的场景中,如果让一个HashMap去存储百万级的对象,暂且不考虑是否会出现OOM或者栈溢出。光是每次扩容带来的影响就很头大了。

集合的平均查找时间复杂度是O(Logn),对于百万级,也就是说一个词语需要查询6-7次,然后再从词语名->词语所在的词组id列表的对应关系中取出(一般使用hashMap存储),也需要6-7次查询。也就是说,一个词语需要12-14次左右的查询。一篇文章大约一共需要12*500=6000次匹配,存储方面需要使用HashSet存储词,用HashMap存储词语和其所在id列表对应关系。

看起来这样的结果却是还是可以令人接受的,那么你还可以提出别的方法吗?

2、Trie树

和上面一样,词组需要进行拆分

AA&BB&CC&AAB => AA BB CC AAB      

trie树的每个节点都是一个字儿,对于 AA 和 AAB这两个词,最终的结构将是 root->A->A->B,其中在B和第二个A里面会标记为词语结束节点。每个处于结束位置的节点需要指定当前词组的id,从而形成所属的id列表。在百万级的词库下,我们需要拆分出每个词,也就是节点数大约是3*n(n为词库的大小),不需要额外的其他存储。从查询角度出发,对于一个词,只需要拆分为字序,然后按照顺序匹配即可,如果匹配不到,说明该词不在当前Trie树中,如果匹配结束,但是末尾节点在当前Trie树中不是结尾字符,那么也判定为结果不存在,时间复杂度为O(e),e为词语的长度。可见,Trie树的时间复杂度非常低,完全是依靠空间换时间的方法,在我们的开发应用环境中,内存等物理资源往往都是比较大的,节约时间,提高效率是每一个api提供者必要的修行。

以java为例,我们可以使用apache的包来实现这样的功能

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-collections4</artifactId>
    <version>4.4</version>
</dependency>      

继续阅读