最近花钱买了一个牛客网上的项目,学习一下业务上的逻辑。这个项目是用SpringBoot开发的,省去了一系列的配置,十分方便。现在这个项目已经做到了站内信的地方,之后把整个项目搬到博客上,当做纪念。
现在把其中的一部分先搬上来,这部分内容是敏感词过滤。这部分算法涉及到字典树和字符匹配的方法,值的研究一下。整体步骤如下所示:
首先创建字典树
其次读取敏感词的属性文件
接着将敏感词组成字典树
最后把字符串与字典树进行匹配,即匹配算法
代码如下所示,具体解析已经全部放在代码里了。package com.xuan.xupro.Service; import com.xuan.xupro.Controller.IndexController; import org.apache.commons.lang3.CharUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.util.HashMap; import java.util.Map; /** *敏感词过滤服务 * Created by Administrator on 2018/2/21. */ public class SensitiveService implements InitializingBean { //添加日志文件 private static final Logger logger = LoggerFactory.getLogger(SensitiveService.class); //敏感词替换为“**” private String DEFAULT_WORD = "**"; //字典树,所有敏感词均在字典树上 class TireNode{ //每个树结点对应的是一个map,以字符映射树结点 private Map<Character, TireNode> subNode = new HashMap<Character, TireNode>(); //敏感词的颜色判断,若为结尾,则该标识为true private boolean endcolor; //得到该树节点的下一个树结点 TireNode getSubNode(Character c) { return subNode.get(c); } //为该树节点增加下一个树节点 void addSubNode(Character c, TireNode tireNode) { subNode.put(c,tireNode); } //判断当前树节点是否为根节点 boolean isEndcolor() { return endcolor; } //给根节点设置标识 void setEndcolor(boolean endcolor) { this.endcolor = endcolor; } } private TireNode rootNode = new TireNode(); //判断该树节点的字符是否为标点符号 private boolean isSympol(Character c){ //char转化为int,其中c拆箱,从Character自动转化为char int inc = (int)c; //若为标点符号,则返回ture return !CharUtils.isAsciiAlphanumeric(c) && (inc < 0x2E80 || inc > 0x9FFF); } //该方法为向字典树里添加敏感词 private void addWord(String string){ //将rootNode的引用赋予当前树节点tempNode,此后便是对tempNode结点的操作 TireNode tempNode = rootNode; for(int i = 0; i < string.length(); i++){ //将该索引下的字符转化为char,再自动装箱为Character Character c = string.charAt(i); //如果该索引下的字符为标点符号,则跳过此次循环,执行下一个循环 if(!isSympol(c)){ continue; } //判断tempNode下,是否有字符c,之后及相应的子节点node TireNode node = tempNode.getSubNode(c); //如果node不存在,即该字典树没有字符c,tempNode结点添加字符c if(node == null){ node = new TireNode(); tempNode.addSubNode(c, node); } //如果有字符c,则把node的引用赋予tempNode tempNode = node; //如果该循环的索引为String的最后一位,则将树节点tempNode标志为true if(i == string.length() - 1){ tempNode.setEndcolor(true); } } } private String sensitiveWordMatch(String str){ //先判断字符串是否为空 if(StringUtils.isBlank(str)){ return ""; } /*该sb为替换掉敏感词的String,在一个方法里使用sb,必定为 线程安全,因此使用StringBuilder */ StringBuilder sb = new StringBuilder(); /*接下来开始匹配敏感词 pos为一个匹配过程中匹配的位置 cur为当前开始匹配过程时的匹配位置 tempNode为字典树的根节点 */ int pos = 0; int cur = 0; TireNode tempNode = rootNode; //判断cur是否走到str尽头 while(cur < str.length()) { //判断一个匹配过程中是否存在敏感词 while (pos < str.length()) { //当前匹配位置的字符 Character c = str.charAt(pos); //判断该字符是否为标点符号 if(isSympol(c)){ sb.append(c); //如果该字符为匹配的第一个字符,则cur++ if(tempNode == rootNode){ cur++; } //如果不是第一个,则把pos++即可 pos++; continue; } //根据字符c得到当前节点 tempNode = tempNode.getSubNode(c); //如果tempNode不为空 if (tempNode != null) { /*再次判断,该结点是否为最后一位,若是,则将匹配到的 的词替换为***,同时,令pos为pos+1,cur=pos,返回根 结点 */ if (tempNode.isEndcolor()) { sb.append(DEFAULT_WORD); pos = pos + 1; cur = pos; tempNode = rootNode; //否则只将pos++ } else { pos++; } //若tempNode为空,说明cur位置处匹配不到敏感词。 } else { sb.append(c); cur = cur + 1; pos = cur; } } /*若在一次匹配过程中pos越界了,则说明cur位置处匹配不到 敏感词,但不代表cur位置以后的地方匹配不到敏感词。因此, 需要判断cur是否越界,如果不越界,则需再次进行敏感词匹配。 */ //判断cur是否越界,若不越界,则把当前cur索引下的字符加入。 if(cur < str.length() - 1){ sb.append(str.indexOf(cur)); cur++; pos = cur; tempNode = rootNode; } } return sb.toString(); } //该方法为重写InitializingBean的方法,在bean初始化时,执行该方法 @Override public void afterPropertiesSet() throws Exception{ //首先从文件中读取敏感词集合 try { InputStream is = new FileInputStream("SensitiveWord"); InputStreamReader reader = new InputStreamReader(is); BufferedReader br = new BufferedReader(reader); String str; //每读一行便将其加入到字典树中,直到将敏感词加入到字典树中完毕 while (br.readLine() != null) { str = br.readLine().trim(); addWord(str); } //关闭资源,否则会一直阻塞住。 reader.close(); }catch (Exception e){ //log文件读取错误信息 logger.error("读取敏感词失败" + e.getMessage()); } } }