JavaScript 实现敏感词过滤:DFA算法原理与性能对比

作者:寒星时间:2026-10-22 16:10:28浏览:606

本文详细介绍在 JavaScript 中实现敏感词过滤的两种方案:传统全文匹配与 DFA 确定有限状态自动机。重点解析 DFA 算法通过构建词树结构实现高效查找的逻辑,并提供完整的代码实现。通过对比测试,DFA 算法在平均耗时和最大耗时上均显著优于原生 indexOf 方法,适合高并发场景。

敏感词过滤方案概述

近期开发了一个用户评论功能,允许用户提交评论并在文章下方查看。作为遵循社会主义核心价值观的内容平台,必须对评论内容进行敏感词过滤。经过调研,目前主要有两种常见的解决方案。

第一种方案是全文搜索并逐个匹配。这种方式虽然直观,但在数据量较大时存在明显的效率瓶颈,具体性能对比见文末。

第二种方案是采用 DFA 算法,即确定有限状态自动机(DFA)。关于该算法的详细定义可参考相关百科链接。

DFA 算法原理

DFA 算法简介

DFA 是一种计算模型,其数据源为有限集合。算法通过当前状态和输入事件来确定下一个状态,即遵循“状态 + 事件 = 下一状态”的规则。通过这一过程逐步构建出一个有向图,其中节点代表状态。由于 DFA 仅涉及查找和判断操作,无需复杂计算,因此能显著提升算法效率。

参考文章:Java 实现敏感词过滤

DFA 实现逻辑与代码

实现逻辑概述

数据结构构造

将敏感词列表转换为树状结构。例如,敏感词库包含 ['日本鬼子', '日本人', '日本男人'],其数据结构如下图所示(图片引用自参考文章)。

敏感词过滤方案概述 

每个汉字作为一个节点,连续节点组成一个完整词汇。以“日本人”为例,对应树中的特定路径。可以使用对象或 Map 来构建树结构,本例采用 Map 构建节点。每个节点包含一个状态标识,用于标记当前节点是否为词的结尾。每条路径必须有一个终点节点。以下是构建节点的流程图。

敏感词过滤方案概述 

判断逻辑说明

从文本的第一个字符开始检查。例如文本“你我是日本鬼子”,第一个字“你”在第一层节点中未找到,则继续检查第二个字。当遇到“日”时,在第一层找到对应节点,接着在下一层节点中查找“本”,同时判断该节点是否为结尾节点。如果是,则匹配成功;否则继续向后匹配。

代码实现部分

#### 构造数据结构


/**
* @description
* 构造敏感词map
* @private
* @returns
*/
private makeSensitiveMap(sensitiveWordList) {
 // 构造根节点
 const result = new Map();
 for (const word of sensitiveWordList) {
  let map = result;
  for (let i = 0; i < word.length; i++) {
   // 依次获取字
   const char = word.charAt(i);
   // 判断是否存在
   if (map.get(char)) {
    // 获取下一层节点
    map = map.get(char);
   } else {
    // 将当前节点设置为非结尾节点
    if (map.get('laster') === true) {
     map.set('laster', false);
    }
    const item = new Map();
    // 新增节点默认为结尾节点
    item.set('laster', true);
    map.set(char, item);
    map = map.get(char);
   }
  }

 }
 return result;
}

最终生成的 Map 结构如下所示。

敏感词过滤方案概述 

查找敏感词逻辑


/**
* @description
* 检查敏感词是否存在
* @private
* @param {any} txt
* @param {any} index
* @returns
*/
private checkSensitiveWord(sensitiveMap, txt, index) {
 let currentMap = sensitiveMap;
 let flag = false;
 let wordNum = 0;//记录过滤
 let sensitiveWord = ''; //记录过滤出来的敏感词
 for (let i = index; i < txt.length; i++) {
  const word = txt.charAt(i);
  currentMap = currentMap.get(word);
  if (currentMap) {
   wordNum++;
   sensitiveWord += word;
   if (currentMap.get('laster') === true) {
    // 表示已到词的结尾
    flag = true;
    break;
   }
  } else {
   break;
  }
 }
 // 两字成词
 if (wordNum < 2) {
  flag = false;
 }
 return { flag, sensitiveWord };
}
/**
* @description
* 判断文本中是否存在敏感词
* @param {any} txt
* @returns
*/
public filterSensitiveWord(txt, sensitiveMap) {
 let matchResult = { flag: false, sensitiveWord: '' };
 // 过滤掉除了中文、英文、数字之外的
 const txtTrim = txt.replace(/[^\u4e00-\u9fa5\u0030-\u0039\u0061-\u007a\u0041-\u005a]+/g, '');
 for (let i = 0; i < txtTrim.length; i++) {
  matchResult = checkSensitiveWord(sensitiveMap, txtTrim, i);
  if (matchResult.flag) {
   console.log(`sensitiveWord:${matchResult.sensitiveWord}`);
   break;
  }
 }
 return matchResult;
}

性能测试与对比

效率分析

为了验证 DFA 算法的效率,进行了简单测试。测试文本长度为 5095 个汉字,敏感词库包含 2000 个敏感词。对比算法包括 DFA 算法和 String 原生对象提供的 indexOf API。


// 简单的字符串匹配-indexOf
ensitiveWords.forEach((word) => {
 if (ss.indexOf(word) !== -1) {
  console.log(word)
 }
})

两种算法分别执行 100 次,结果如下。

敏感词过滤方案概述 

从结果可直观看出,DFA 的平均耗时约为 1ms,最大耗时为 5ms;而 indexOf 方式的平均耗时约为 9ms,最大耗时为 14ms。因此,DFA 在效率上具有明显优势。

总结

以上介绍了 JavaScript 实现敏感词过滤的算法及逻辑,希望对大家有所帮助。如有疑问请留言,小编会及时回复。感谢大家对本站的支持!

作者最新文章