闲碎记事本 闲碎记事本
首页
  • JAVA
  • Cloudflare
  • 学完再改一遍UI
友链
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

YAN

我要偷偷记录...
首页
  • JAVA
  • Cloudflare
  • 学完再改一遍UI
友链
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • java

    • SpringBoot

    • SpringSecurity

    • MybatisPlus

    • Netty

    • sip

    • 其他

      • MDC 使用
      • 位运算
      • RedisMQ实现
      • 自定义枚举序列化
      • Mybatis使用自定义枚举
      • Jackson反序列化泛型注意点
      • 敏感词过滤算法
        • 基类
        • 核心类
        • 测试
          • 控制台输出
      • 线程
      • 并发学习
      • jni使用
      • 关于注释
      • 为什么一个Byte用两个16进制表示
      • JAVA获取系统信息
      • 对extends和super的理解
      • JAVA系统API
      • java探针初探
      • JAVA获取USB信息
      • HashMap初探
      • JAVA远程调试
      • 初探webflux
      • SSE示例
  • linux

  • docker

  • redis

  • nginx

  • mysql

  • 其他

  • 环境搭建

  • 知识库
  • java
  • 其他
Yan
2023-04-10
目录

敏感词过滤算法

敏感词过滤算法,详情见注释

# 基类

public class TrieNode {
    /**
     * true 关键词的终结 ; false 继续
     */
    private boolean end = false;

    /**
     * 根节点默认大小
     */
    private static final int ROOT_NODE_SIZE  = 64;
    
    /**
     * 子节点默认大小
     */
    private static final int SUB_NODE_SIZE  = 16;


    public static  TrieNode buildRootNode() {
        return new TrieNode(ROOT_NODE_SIZE);
    }

    public static  TrieNode buildSubNode() {
        return new TrieNode(SUB_NODE_SIZE);
    }
    
    /**
     * key下一个字符,value是对应的节点
     */
    private final Map<Character, TrieNode> subNodes;

    public TrieNode(int size) {
        subNodes = new HashMap<>(size);
    }

    /**
     * 向指定位置添加节点树
     */
    void addSubNode(Character key, TrieNode node) {
        subNodes.put(key, node);
    }

    /**
     * 获取下个节点
     */
    TrieNode getSubNode(Character key) {
        return subNodes.get(key);
    }

    boolean isKeywordEnd() {
        return end;
    }

    void setKeywordEnd() {
        this.end = true;
    }
}


# 核心类


public class KeyWord {

    /**
     * 默认敏感词替换符
     */
    private static final String DEFAULT_REPLACEMENT = "**";
    /**
     * 根节点
     */
    private final TrieNode ROOT_NODE = TrieNode.buildRootNode();

    /**
     * 得到根节点
     * @return 根节点
     */
    private TrieNode getRootNode() {
        return ROOT_NODE;
    }


    /**
     * 判断是否是一个符号
     *
     * @param c char
     * @return true是字符,false不是字符
     */
    private boolean isSymbol(char c) {
        // 0x2E80-0x9FFF 东亚文字范围
        return !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) && ((int) c < 0x2E80 || (int) c > 0x9FFF);
    }



    /**
     * 过滤敏感词
     */
    public String filter(String text) {
        if (text.trim().length() == 0) {
            return text;
        }
        StringBuilder result = new StringBuilder();
        //
        TrieNode tempNode = getRootNode();

        int begin = 0;
        int position = 0;

        while (position < text.length()) {
            char c = text.charAt(position);
            // 空格直接跳过
            if (isSymbol(c)) {
                //如果是根节点证明是正常字符
                //如果不是根节点证明是混淆字符,例如: S*B
                if (tempNode == getRootNode()) {
                    //记录当前字符移动指针
                    result.append(c);
                    //归位
                    ++begin;
                }
                //移动指针
                ++position;
                continue;
            }

            //尝试从敏感字典里获取
            tempNode = tempNode.getSubNode(c);

            if (tempNode == null) {
                result.append(text.charAt(begin));

                /*
                没有获取到敏感词,有两种情况:
                以敏感词:'傻逼'举例

                情况1:从头到尾都没有敏感词:
                例如:你是个小可爱
                匹配结束,begin=6,position = 6

                情况2:敏感词被截断
                例如:你是个傻可爱。
                匹配到疑似敏感词'傻': begin=3,position = 4
                匹配到非敏感词'可',于上不足以构成完全的敏感词, 需要重新记录'傻'字  => text.charAt(3) == '傻'
                由于此时 position 可能进行了多次'疑似敏感词'的位移,
                而 begin 进行了一次值捕获,需要移动到下一位
                所以需要重置position位置为移动后的begin位置
                 */
                position = ++begin;
                // 因为此时没有匹配到敏感字,回到树初始节点
                tempNode = getRootNode();
            }  else if (tempNode.isKeywordEnd()) {
                //匹配到一个完整敏感词,用  DEFAULT_REPLACEMENT 替代
                result.append(DEFAULT_REPLACEMENT);
                //此时 begin下标还是为在: 当前敏感词首位的下标-1
                //所以需要重置下标为 position ,也就是当前敏感词结束下标
                begin = ++position;
                //匹配敏感字完成,回到树初始节点
                tempNode = getRootNode();
            } else {
                //疑似敏感词位移
                ++position;
            }
        }

        //此处截取是为了防止末尾敏感词不全,导致的信息不全问题
        // 字符:你是傻
        //截取到最后,position==3,begin==2,result=='你是' ,text.substring(begin) == '傻'
        //由于不够组成敏感词,需要追加
        result.append(text.substring(begin));
        return result.toString();
    }


    /**
     * 构造字典树
     *
     * @param lineTxt 字符
     */
    private void addWord(String lineTxt) {
        TrieNode currNode = getRootNode();

        // 循环每个字节
        for (int i = 0; i < lineTxt.length(); ++i) {
            Character c = lineTxt.charAt(i);
            // 过滤空格
            if (isSymbol(c)) {
                continue;
            }

            //尝试获取一个敏感字符
            TrieNode node = currNode.getSubNode(c);
            if (node == null) {
            //没拿到,初始化
                node = TrieNode.buildSubNode();
                currNode.addSubNode(c, node);
            }

            currNode = node;
            if (i == (lineTxt.length() - 1) ) {
                // 关键词结束, 设置结束标志
                currNode.setKeywordEnd();
            }

        }
    } 

}


# 测试

public static void main(String[] argv) {
    KeyWord s = new KeyWord();
    s.addWord("SB");
    s.addWord("TM");
    System.out.println(s.filter("你是TM真是个SB"));
}   

# 控制台输出

你是**真是个**
上次更新: 2025/05/14, 01:34:05
Jackson反序列化泛型注意点
线程

← Jackson反序列化泛型注意点 线程→

最近更新
01
Caddy操作指南
04-25
02
Swap空间
04-22
03
Alist使用
04-21
更多文章>
Theme by Vdoing | Copyright © 2022-2025 YAN | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式