在内容审核场景中,实时替换文本中的敏感词是保障内容合规的基础需求。当敏感词数量达到上千甚至上万级别时,传统的循环遍历匹配方式时间复杂度极高,难以满足高并发场景的性能要求。结合Trie树的高效前缀匹配特性和正则的灵活边界处理能力,能实现高性能的敏感词实时替换功能。

Trie树的核心原理
Trie树也叫前缀树,是一种哈希树的变种,核心思想是用空间换时间,将敏感词按字符前缀组织成树形结构。每个节点存储一个字符,从根节点到某个节点的路径组成的字符串就是一个敏感词前缀。匹配时只需要遍历一次待检测文本,就能完成所有敏感词的匹配,时间复杂度为O(n),n为文本长度,不受敏感词数量影响。
敏感词Trie树的php实现
首先我们需要构建Trie树结构,支持敏感词的插入和匹配操作:
<?php
class TrieNode {
public $children = [];
public $isEnd = false;
}
class SensitiveWordTrie {
private $root;
public function __construct() {
$this->root = new TrieNode();
}
// 插入敏感词
public function insert($word) {
$node = $this->root;
$len = mb_strlen($word, 'UTF-8');
for ($i = 0; $i < $len; $i++) {
$char = mb_substr($word, $i, 1, 'UTF-8');
if (!isset($node->children[$char])) {
$node->children[$char] = new TrieNode();
}
$node = $node->children[$char];
}
$node->isEnd = true;
}
// 匹配文本中的敏感词,返回匹配的敏感词及起始位置
public function match($text) {
$result = [];
$len = mb_strlen($text, 'UTF-8');
for ($i = 0; $i < $len; $i++) {
$node = $this->root;
$j = $i;
while ($j < $len) {
$char = mb_substr($text, $j, 1, 'UTF-8');
if (!isset($node->children[$char])) {
break;
}
$node = $node->children[$char];
if ($node->isEnd) {
$result[] = [
'word' => mb_substr($text, $i, $j - $i + 1, 'UTF-8'),
'start' => $i,
'end' => $j
];
}
$j++;
}
}
return $result;
}
}
?>
结合正则过滤实现完整替换
Trie树匹配到敏感词的位置后,我们需要结合正则处理边界情况,比如避免替换掉部分正常词汇,同时支持自定义替换规则:
<?php
class SensitiveWordFilter {
private $trie;
private $replaceChar = '*'; // 默认替换字符
public function __construct($sensitiveWords = []) {
$this->trie = new SensitiveWordTrie();
foreach ($sensitiveWords as $word) {
$this->trie->insert($word);
}
}
// 设置替换字符
public function setReplaceChar($char) {
$this->replaceChar = $char;
}
// 正则过滤处理,避免替换边界错误的词汇
private function filterByRegex($text, $matchedWords) {
if (empty($matchedWords)) {
return $text;
}
// 按匹配位置降序排序,避免替换后位置偏移
usort($matchedWords, function($a, $b) {
return $b['start'] - $a['start'];
});
foreach ($matchedWords as $item) {
$wordLen = mb_strlen($item['word'], 'UTF-8');
$replaceStr = str_repeat($this->replaceChar, $wordLen);
// 使用正则确保替换的是完整匹配的词,避免部分替换
$pattern = '/' . preg_quote($item['word'], '/') . '/u';
$text = preg_replace($pattern, $replaceStr, $text);
}
return $text;
}
// 实时替换敏感词主方法
public function replaceSensitiveWords($text) {
$matched = $this->trie->match($text);
if (empty($matched)) {
return $text;
}
// 去重匹配结果,避免同一位置重复处理
$uniqueMatched = [];
$existPos = [];
foreach ($matched as $item) {
$posKey = $item['start'] . '_' . $item['end'];
if (!isset($existPos[$posKey])) {
$uniqueMatched[] = $item;
$existPos[$posKey] = true;
}
}
return $this->filterByRegex($text, $uniqueMatched);
}
}
?>
使用示例
以下是完整的调用示例,展示如何加载敏感词并实现实时替换:
<?php
// 敏感词列表,实际场景可从数据库或配置文件加载
$sensitiveWords = ['敏感词1', '敏感词2', '测试敏感词', '违规内容'];
$filter = new SensitiveWordFilter($sensitiveWords);
$filter->setReplaceChar('#'); // 设置替换字符为#
$testText = '这是一段包含测试敏感词和违规内容的文本,还有敏感词1需要替换';
$result = $filter->replaceSensitiveWords($testText);
echo "原始文本:" . $testText . "<br>";
echo "替换后文本:" . $result . "<br>";
// 输出结果:
// 原始文本:这是一段包含测试敏感词和违规内容的文本,还有敏感词1需要替换
// 替换后文本:这是一段包含#######和#######的文本,还有###需要替换
?>
注意事项
- 敏感词加载时可以提前做去重和空值过滤,避免无效数据影响Trie树结构
- 如果敏感词包含特殊正则字符,preg_quote方法会自动转义,无需额外处理
- 高并发场景下可以将Trie树实例做缓存,避免每次请求都重新构建树结构
- 替换字符可以根据业务需求自定义,比如替换为空字符串或者特定占位符