千家信息网

php如何使用前缀树实现关键词查找

发表于:2025-01-24 作者:千家信息网编辑
千家信息网最后更新 2025年01月24日,这篇文章主要讲解了"php如何使用前缀树实现关键词查找 ",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"php如何使用前缀树实现关键词查找 "吧!之前旧
千家信息网最后更新 2025年01月24日php如何使用前缀树实现关键词查找

这篇文章主要讲解了"php如何使用前缀树实现关键词查找 ",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"php如何使用前缀树实现关键词查找 "吧!

之前旧的php系统有一个关键词查找和敏感词过滤的功能,直接使用了如下代码实现,

foreach ($words as $word){    if(strrpos($content, $word) !== false){        $tags[] = $word;    }}

随着关键词增多,性能上有点拖后腿,一直想优化一下性能。
刚好从网上看到一个比较简单的java版本"利用利用字典树(前缀树)过滤敏感词"(https://blog.csdn.net/qq_37050329/article/details/84276344)的算法文章,感觉按这算法实现可以提升性能。
php第一个版本前缀树过滤迅速实现:

subNodes[$key] = $node;    }    /**             * 获取下个节点     */    public function getSubNode($key) {        return isset($this->subNodes[$key]) == true ? $this->subNodes[$key] : null;    }        function isKeywordEnd() {        return $this->end;    }        function setKeywordEnd($end) {        $this->end = $end;    }}class FilterWords{    //根节点    private $rootNode;        function __construct() {        $this->rootNode = new Words();    }        public function isSymbol($c){        $symbol = array('\t', '\r\n',            '\r', '\n', 'amp;', '>','。', '?','!',',','、',';',':','丨','|',':','《','》','"','"',            '.',',',';',':','?','!',' ',' ','(',')'        );        if(in_array($c, $symbol)){            return true;        }        else{            return false;        }    }        /**             * 过滤敏感词     */    function filter($text) {        $mblen = mb_strlen($text);        if ($mblen == 0) {            return null;        }        $tempNode = $this->rootNode;        $begin = 0; // 回滚数        $position = 0; // 当前比较的位置                $tagBuffer = array();        $tempBuffer = "";        while ($position < $mblen) {            $c = mb_substr($text, $position, 1);            //特殊符号直接跳过            if ($this->isSymbol($c)) {                if ($tempNode == $this->rootNode) {                    ++$begin;                }                ++$position;                continue;            }            $tempNode = $tempNode->getSubNode($c);            // 当前位置的匹配结束            if ($tempNode == null) {                // 跳到下一个字符开始测试                $position = $begin + 1;                $begin = $position;                // 回到树初始节点                $tempNode = $this->rootNode;                $tempBuffer = '';            } else if ($tempNode->isKeywordEnd()) {                $tempBuffer .= $c;                $tagBuffer[] = $tempBuffer;                $position = $position + 1;                $begin = $position;                $tempNode = $this->rootNode;            } else {                $tempBuffer .= $c;                ++$position;            }        }        return $tagBuffer;    }        /**             * 构造字典树     * @param lineTxt     */    public function addWord($lineTxt) {        $tempNode = $this->rootNode;        $mblen = mb_strlen($lineTxt);        // 循环每个字节        for ($i = 0; $i < $mblen; ++$i) {            $c = mb_substr($lineTxt, $i, 1);            // 过滤空格            if ($this->isSymbol($c)) {                continue;            }            $node = $tempNode->getSubNode($c);                        if ($node == null) { // 没初始化                $node = new Words();                $tempNode->addSubNode($c, $node);            }                        $tempNode = $node;                        if ($i == mb_strlen($lineTxt) - 1) {                $tempNode->setKeywordEnd(true);            }        }    }}

开发完,测试了下,

$filterWords = new FilterWords();$filterWords->addWord("☺");$filterWords->addWord("沪伦通");$filterWords->addWord("中欧");$tags = $filterWords->filter("????☺????沪伦通首单即将开启 中欧资本融合驶入快车道");var_dump($tags);输出:array(3) {  [0]=>  string(3) "☺"  [1]=>  string(9) "沪伦通"  [2]=>  string(6) "中欧"}

性能测试,关键过滤词14600个。
旧处理方式性能

array(            'method'=>"GET",            'header'=>"Accept-Language: zh-CN,zh;q=0.9\r\n" .            "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36\r\n"        )    );    $context = stream_context_create($opts);    $file = file_get_contents($url, false, $context);    return $file;}$content = getHtml('http://baijiahao.baidu.com/s?id=1637904839202713990');$content = strip_tags($content);$words = readFileByLine("banned.txt");$start = microtime_float();$tags = array();foreach ($words as $word){    if(strrpos($content, $word) !== false){        $tags[] = $word;    }}var_dump($tags);$end = microtime_float();echo "耗时:$end - $start = ".($end - $start)."\n";//耗时:1562250442.266 - 1562250441.2643 = 1.0016851425171

第一个版本前缀树性能

addWord($word);}$start = microtime_float();$tags = $filterWords->filter($content);var_dump($tags);$end = microtime_float();echo "耗时:$end - $start = ".($end - $start)."\n";//耗时:1562250499.7439 - 1562250484.4361 = 15.307857036591

使用前缀树的性能比原来strpos慢了10多倍。。。。。。
检查了一翻,怀疑可能是使用mb_substr来把文章分割成字符数组损耗性能利害,在Java中使用统一的Unicode,而在PHP中使用的UTF-8字符集,用1-4个字节不同的长度来表示一个字符,$text[$position]不一定能表示一个完整字符。
增加一个getChars测试方法,先把文本转成字符数组,再把字符数组传到$filterWords->filter($chars)方法,经测试,性能明显比旧的方式好。

public function getChars($lineTxt){        $mblen = mb_strlen($lineTxt);        $chars = array();        for($i = 0; $i < $mblen; $i++){            $c = mb_substr($lineTxt, $i, 1, 'UTF-8');            $chars[] = $c;        }        return $chars;    }

这样可以确定前缀树查找算法性能没问题,性能问题是在mb_substr方法上,因些需要改进获取字符的方法。可以通过判断当前字符是多少字节,然后再通过$text[$position]来获取字节拼接成完整字符。
第二个版本优化调整后的前缀树过滤实现:

class FilterWords{    public $rootNode;    function __construct() {        $this->rootNode = array('_end_'=>false);    }       public function getChars($lineTxt){        $mblen = mb_strlen($lineTxt);        $chars = array();        for($i = 0; $i < $mblen; $i++){            $c = mb_substr($lineTxt, $i, 1, 'UTF-8');            $chars[] = $c;        }        return $chars;    }      /**             * 构造字典树     * @param $word     */    public function addWord($word) {        $tempNode = &$this->rootNode;        $mblen = mb_strlen($word);        // 循环每个字节        for ($i = 0; $i < $mblen; ++$i) {            $c = mb_substr($word, $i, 1);                        if(empty($tempNode[$c]) == true){                $tempNode[$c] = array('_end_'=>false);            }            $tempNode = &$tempNode[$c];                        if ($i == $mblen - 1) {                $tempNode['_end_'] = true;            }        }    }        function filter($text) {        $len = strlen($text);        if ($len == 0) {            return null;        }        $tempNode = $this->rootNode;        $position = 0;        $tags = array();        $temp = "";        while ($position < $len) {            $c = $text[$position];            $n = ord($c);            if(($n >> 7) == 0){       //1字节            }            else if(($n >> 4) == 15 ){     //4字节                if($position < $len - 3){                    $c = $c.$text[$position + 1].$text[$position + 2].$text[$position + 3];                    $position += 3;                }            }            else if(($n >> 5) == 7){  //3字节                if($position < $len - 2){                    $c = $c.$text[$position + 1].$text[$position + 2];                    $position += 2;                }            }            else if(($n >> 6) == 3){  //2字节                if($position < $len - 1){                    $c = $c.$text[$position + 1];                    $position++;                }            }            $tempNode = isset($tempNode[$c]) == true ? $tempNode[$c] : null;            // 当前位置的匹配结束            if ($tempNode == null) {                $position = $position + 1;                // 回到树初始节点                $tempNode = $this->rootNode;                $temp = '';            } else if ($tempNode['_end_'] == true) {                $temp .= $c;                $tags[] = $temp;                $temp = '';                $position = $position + 1;                $tempNode = $this->rootNode;            } else {                $temp .= $c;                ++$position;            }        }        return $tags;    }}

第二个版本前缀树性能:

$content = getHtml('http://baijiahao.baidu.com/s?id=1637904839202713990');$content = strip_tags($content);var_dump($content);$words = readFileByLine("banned.txt");$filterWords = new FilterWords();foreach($words as $word){    $filterWords->addWord($word);}$start = microtime_float();$tags = $filterWords->filter($content);var_dump($tags);$end = microtime_float();echo "耗时:$end - $start = ".($end - $start)."\n";耗时:1562250534.7054 - 1562250534.6888 = 0.016629934310913

耗时0.016629934310913,比旧方式的耗时1.0016851425171性能提升了一大截。
后期继续调整代码优化。

感谢各位的阅读,以上就是"php如何使用前缀树实现关键词查找 "的内容了,经过本文的学习后,相信大家对php如何使用前缀树实现关键词查找 这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是,小编将为大家推送更多相关知识点的文章,欢迎关注!

性能 前缀 字符 字节 关键 关键词 版本 测试 方法 数组 文章 方式 算法 节点 问题 中欧 学习 代码 位置 内容 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 网络安全法与网络安全 摩沙串口服务器厂家 如何参与国家网络安全周 网络安全主题水粉画一等奖 纳克萨玛斯服务器报价 数据库用于删除视图的语句是 何为关系数据库 然后清除数据库sa口令 网络安全分裂的节点是什么 博爱县众博网络技术工作室 晋城软件开发哪家好 军营网络安全宣传周活动启动仪式 网络安全工程师地域分析 石家庄数据库营销 学校网络安全隐患排查台账 数据库创建表 外键约束 服务器旁边的是什么灯 数据库期末考试试题及答案 如何将数据库实例备份和还原 如何撤回数据库论文 人员外包软件开发方案 怎么样删除windows服务器 思科网络技术学院教程第三版 小学网络安全宣传活动的总结 .《网络安全法》内容 无线网覆盖服务器维修 拓文网络技术有限公司6 三菱服务器和电机 php打开网页数据库发生错误 服务器怎么判断用户登录
0