敏感词过滤(DFA算法)PHP实现

前言

最近项目需要用到敏感词过滤功能,最开始想的是使用正则匹配和mysql存储敏感词来对敏感词来进行过滤操作,但是这两种方法都感觉不好。大家都知道正则性能问题一直都是一个很大的问题,而使用mysql的话虽然可以实现,但是给数据库增加了额外的压力。后面经过Google了解到DFA(有穷自动机)算法可以解决我的问题。

 

什么是DFA算法?

 DFA全称为:Deterministic Finite Automaton,即确定有穷自动机。其特征为:有一个有限状态集合和一些从一个状态通向另一个状态的边,每条边上标记有一个符号,其中一个状态是初态,某些状态是终态。但不同于不确定的有限自动机,DFA中不会有从同一状态出发的两条边标志有相同的符号。简单点说就是,它是是通过event和当前的state得到下一个state,即event+state=nextstate。理解为系统中有多个节点,通过传递进入的event,来确定走哪个路由至另一个节点,而节点是有限的。

说了这么多是不是还是一脸懵逼?没关系,下面我们直接来撸代码,里面都有进行注释。如果还是不懂的话,可以去google搜索dfa算法的详细介绍。

 

算法DEMO

class Sensitive
{
    private static $instance = null;
    /**
     * 替换符号
     * @var string
     */
    private static $replaceSymbol = "*";
    /**
     * 敏感词树
     * @var array
     */
    private static $sensitiveWordTree = [];
    private function __construct(){}
    /**
     * 获取实例
     */
    public static function getInstance()
    {
        if (!(self::$instance instanceof Sensitive)) {
            return self::$instance = new self;
        }
        return self::$instance;
    }

    /**
     * 添加敏感词,组成树结构。
     * 例如敏感词为:傻子是傻帽,白痴,傻蛋 这组词组成如下结构。
     * [
     *     [傻] => [
     *           [子]=>[
     *               [是]=>[
     *                  [傻]=>[
     *                      [帽]=>[false]
     *                  ]
     *              ]
     *          ],
     *          [蛋]=>[false]
     *      ],
     *      [白]=>[
     *          [痴]=>[false]
     *      ]
     *  ]
     * @param $file_path 敏感词库文件路径
     */
    public static function addSensitiveWords(string $file_path) :void
    {
        foreach (self::readFile($file_path) as $words) {
            $len = mb_strlen($words);
            $treeArr = &self::$sensitiveWordTree;
            for ($i = 0; $i < $len; $i++) {
                $word = mb_substr($words, $i, 1);
                //敏感词树结尾记录状态为false;
                $treeArr = &$treeArr[$word] ?? $treeArr = false;
            }
        }
    }

    /**
     * 执行过滤
     * @param string $txt
     * @return string
     */
    public static function execFilter(string $txt) :string
    {
        $wordList = self::searchWords($txt);
        if(empty($wordList))
            return $txt;
        return strtr($txt,$wordList);
    }

    /**
     * 搜索敏感词
     * @param string $txt
     * @return array
     */
    private static function searchWords(string $txt) :array
    {
        $txtLength = mb_strlen($txt);
        $wordList = [];
        for($i = 0; $i < $txtLength; $i++){
            //检查字符是否存在敏感词树内,传入检查文本、搜索开始位置、文本长度
            $len = self::checkWordTree($txt,$i,$txtLength);
            //存在敏感词,进行字符替换。
            if($len > 0){
                //搜索出来的敏感词
                $word = mb_substr($txt,$i,$len);
                $wordList[$word] = str_repeat(self::$replaceSymbol,$len);
            }
        }
        return $wordList;
    }

    /**
     * 检查敏感词树是否合法
     * @param string $txt 检查文本
     * @param int $index 搜索文本位置索引
     * @param int $txtLength 文本长度
     * @return int 返回不合法字符个数
     */
    private static function checkWordTree(string $txt, int $index, int $txtLength) :int
    {
        $treeArr = &self::$sensitiveWordTree;
        $wordLength = 0;//敏感字符个数
        $flag = false;
        for($i = $index; $i < $txtLength; $i++){
            $txtWord = mb_substr($txt,$i,1); //截取需要检测的文本,和词库进行比对
            //如果搜索字不存在词库中直接停止循环。
            if(!isset($treeArr[$txtWord])) break;
            if($treeArr[$txtWord] !== false){//检测还未到底
                $treeArr = &$treeArr[$txtWord]; //继续搜索下一层tree
            }else{
                $flag = true;
            }
            $wordLength++;
        }
        //没有检测到敏感词,初始化字符长度
        $flag ?: $wordLength = 0;
        return $wordLength;
    }


    /**
     * 读取文件内容
     * @param string $file_path
     * @return Generator
     */
    private static function readFile(string $file_path) :Generator
    {
        $handle = fopen($file_path, 'r');
        while (!feof($handle)) {
            yield trim(fgets($handle));
        }
        fclose($handle);
    }

    private function __clone()
    {
        throw new \Exception("clone instance failed!");
    }

    private function __wakeup()
    {
        throw new \Exception("unserialize instance failed!");
    }

}

测试

require './Sensitive.php';
$instance = Sensitive::getInstance();
$instance->addSensitiveWords('./word.txt');//引入你的敏感词库文件
$txt = "我擦,——————————你麻痹的——————,尼玛的,你个傻逼";//需要过滤的文本
echo $instance->execFilter($txt);

测试结果

参考博客

https://blog.csdn.net/qq_36827957/article/details/74357283

 

 

  • 4
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值