敏感字过滤

一、前言

开发中经常要处理用户一些文字的提交,所以涉及到了敏感词过滤的功能,参考资料中DFA有穷状态机算法的实现,创建有向图。完成了对敏感词、广告词的过滤,而且效率较好,所以分享一下。

具体实现:

1、匹配大小写过滤

2、匹配全角半角过滤

3、匹配过滤停顿词过滤。

4、敏感词重复词过滤。

 

例如:

支持如下类型类型过滤检测:

fuck 全小写

FuCk 大小写

fuck全角半角

f!!!u&c ###k 停顿词

fffuuuucccckkk 重复词

二、代码实现

其目录结构如下:


 

其中resources资源目录中:

stopwd.txt :停顿词,匹配时间直接过滤。

wd.txt:敏感词库。

 

1、WordFilter敏感词过滤类

 

 

[java] view plain copyprint?

  1. package org.andy.sensitivewdfilter;
  2.  
  3. import java.io.BufferedReader;
  4. import java.io.IOException;
  5. import java.io.InputStreamReader;
  6. import java.util.ArrayList;
  7. import java.util.HashMap;
  8. import java.util.HashSet;
  9. import java.util.List;
  10. import java.util.Map;
  11. import java.util.Set;
  12.  
  13. import org.andy.sensitivewdfilter.util.BCConvert;
  14.  
  15. /**
  16. * 创建时间:2016年8月30日 下午3:01:12
  17. *
  18. * 思路: 创建一个FilterSet,枚举了0~65535的所有char是否是某个敏感词开头的状态
  19. *
  20. * 判断是否是 敏感词开头 | | 是 不是 获取头节点 OK--下一个字 然后逐级遍历,DFA算法
  21. *
  22. * @author andy
  23. * @version 2.2
  24. */
  25. public class WordFilter {
  26.  
  27. private static final FilterSet set = new FilterSet(); // 存储首字
  28. private static final Map<Integer, WordNode> nodes = new HashMap<Integer, WordNode>(1024, 1); // 存储节点
  29. private static final Set<Integer> stopwdSet = new HashSet<>(); // 停顿词
  30. private static final char SIGN = '*'; // 敏感词过滤替换
  31.  
  32. static {
  33. try {
  34. long a = System.nanoTime();
  35. init();
  36. a = System.nanoTime() - a;
  37. System.out.println("加载时间 : " + a + "ns");
  38. System.out.println("加载时间 : " + a / 1000000 + "ms");
  39. } catch (Exception e) {
  40. throw new RuntimeException("初始化过滤器失败");
  41. }
  42. }
  43.  
  44. private static void init() {
  45. // 获取敏感词
  46. addSensitiveWord(readWordFromFile("wd.txt"));
  47. addStopWord(readWordFromFile("stopwd.txt"));
  48. }
  49.  
  50. /**
  51. * 增加敏感词
  52. * @param path
  53. * @return
  54. */
  55. private static List<String> readWordFromFile(String path) {
  56. List<String> words;
  57. BufferedReader br = null;
  58. try {
  59. br = new BufferedReader(new InputStreamReader(WordFilter.class.getClassLoader().getResourceAsStream(path)));
  60. words = new ArrayList<String>(1200);
  61. for (String buf = ""; (buf = br.readLine()) != null;) {
  62. if (buf == null || buf.trim().equals(""))
  63. continue;
  64. words.add(buf);
  65. }
  66. } catch (Exception e) {
  67. throw new RuntimeException(e);
  68. } finally {
  69. try {
  70. if (br != null)
  71. br.close();
  72. } catch (IOException e) {
  73. }
  74. }
  75. return words;
  76. }
  77.  
  78. /**
  79. * 增加停顿词
  80. *
  81. * @param words
  82. */
  83. private static void addStopWord(final List<String> words) {
  84. if (words != null && words.size() > 0) {
  85. char[] chs;
  86. for (String curr : words) {
  87. chs = curr.toCharArray();
  88. for (char c : chs) {
  89. stopwdSet.add(charConvert(c));
  90. }
  91. }
  92. }
  93. }
  94.  
  95. /**
  96. * 添加DFA节点
  97. * @param words
  98. */
  99. private static void addSensitiveWord(final List<String> words) {
  100. if (words != null && words.size() > 0) {
  101. char[] chs;
  102. int fchar;
  103. int lastIndex;
  104. WordNode fnode; // 首字母节点
  105. for (String curr : words) {
  106. chs = curr.toCharArray();
  107. fchar = charConvert(chs[0]);
  108. if (!set.contains(fchar)) {// 没有首字定义
  109. set.add(fchar);// 首字标志位 可重复add,反正判断了,不重复了
  110. fnode = new WordNode(fchar, chs.length == 1);
  111. nodes.put(fchar, fnode);
  112. } else {
  113. fnode = nodes.get(fchar);
  114. if (!fnode.isLast() && chs.length == 1)
  115. fnode.setLast(true);
  116. }
  117. lastIndex = chs.length - 1;
  118. for (int i = 1; i < chs.length; i++) {
  119. fnode = fnode.addIfNoExist(charConvert(chs[i]), i == lastIndex);
  120. }
  121. }
  122. }
  123. }
  124.  
  125. /**
  126. * 过滤判断 将敏感词转化为成屏蔽词
  127. * @param src
  128. * @return
  129. */
  130. public static final String doFilter(final String src) {
  131. char[] chs = src.toCharArray();
  132. int length = chs.length;
  133. int currc;
  134. int k;
  135. WordNode node;
  136. for (int i = 0; i < length; i++) {
  137. currc = charConvert(chs[i]);
  138. if (!set.contains(currc)) {
  139. continue;
  140. }
  141. node = nodes.get(currc);// 日 2
  142. if (node == null)// 其实不会发生,习惯性写上了
  143. continue;
  144. boolean couldMark = false;
  145. int markNum = -1;
  146. if (node.isLast()) {// 单字匹配(日)
  147. couldMark = true;
  148. markNum = 0;
  149. }
  150. // 继续匹配(日你/日你妹),以长的优先
  151. // 你-3 妹-4 夫-5
  152. k = i;
  153. for (; ++k < length;) {
  154. int temp = charConvert(chs[k]);
  155. if (stopwdSet.contains(temp))
  156. continue;
  157. node = node.querySub(temp);
  158. if (node == null)// 没有了
  159. break;
  160. if (node.isLast()) {
  161. couldMark = true;
  162. markNum = k - i;// 3-2
  163. }
  164. }
  165. if (couldMark) {
  166. for (k = 0; k <= markNum; k++) {
  167. chs[k + i] = SIGN;
  168. }
  169. i = i + markNum;
  170. }
  171. }
  172.  
  173. return new String(chs);
  174. }
  175.  
  176. /**
  177. * 是否包含敏感词
  178. * @param src
  179. * @return
  180. */
  181. public static final boolean isContains(final String src) {
  182. char[] chs = src.toCharArray();
  183. int length = chs.length;
  184. int currc;
  185. int k;
  186. WordNode node;
  187. for (int i = 0; i < length; i++) {
  188. currc = charConvert(chs[i]);
  189. if (!set.contains(currc)) {
  190. continue;
  191. }
  192. node = nodes.get(currc);// 日 2
  193. if (node == null)// 其实不会发生,习惯性写上了
  194. continue;
  195. boolean couldMark = false;
  196. if (node.isLast()) {// 单字匹配(日)
  197. couldMark = true;
  198. }
  199. // 继续匹配(日你/日你妹),以长的优先
  200. // 你-3 妹-4 夫-5
  201. k = i;
  202. for (; ++k < length;) {
  203. int temp = charConvert(chs[k]);
  204. if (stopwdSet.contains(temp))
  205. continue;
  206. node = node.querySub(temp);
  207. if (node == null)// 没有了
  208. break;
  209. if (node.isLast()) {
  210. couldMark = true;
  211. }
  212. }
  213. if (couldMark) {
  214. return true;
  215. }
  216. }
  217.  
  218. return false;
  219. }
  220.  
  221. /**
  222. * 大写转化为小写 全角转化为半角
  223. *
  224. * @param src
  225. * @return
  226. */
  227. private static int charConvert(char src) {
  228. int r = BCConvert.qj2bj(src);
  229. return (r >= 'A' && r <= 'Z') ? r + 32 : r;
  230. }
  231.  
  232. }
package org.andy.sensitivewdfilter;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.andy.sensitivewdfilter.util.BCConvert;

/**
 * 创建时间:2016年8月30日 下午3:01:12
 * 
 * 思路: 创建一个FilterSet,枚举了0~65535的所有char是否是某个敏感词开头的状态
 * 
 * 判断是否是 敏感词开头 | | 是 不是 获取头节点 OK--下一个字 然后逐级遍历,DFA算法
 * 
 * @author andy
 * @version 2.2
 */
public class WordFilter {

	private static final FilterSet set = new FilterSet(); // 存储首字
	private static final Map<Integer, WordNode> nodes = new HashMap<Integer, WordNode>(1024, 1); // 存储节点
	private static final Set<Integer> stopwdSet = new HashSet<>(); // 停顿词
	private static final char SIGN = '*'; // 敏感词过滤替换

	static {
		try {
			long a = System.nanoTime();
			init();
			a = System.nanoTime() - a;
			System.out.println("加载时间 : " + a + "ns");
			System.out.println("加载时间 : " + a / 1000000 + "ms");
		} catch (Exception e) {
			throw new RuntimeException("初始化过滤器失败");
		}
	}

	private static void init() {
		// 获取敏感词
		addSensitiveWord(readWordFromFile("wd.txt"));
		addStopWord(readWordFromFile("stopwd.txt"));
	}

	/**
	 * 增加敏感词
	 * @param path
	 * @return
	 */
	private static List<String> readWordFromFile(String path) {
		List<String> words;
		BufferedReader br = null;
		try {
			br = new BufferedReader(new InputStreamReader(WordFilter.class.getClassLoader().getResourceAsStream(path)));
			words = new ArrayList<String>(1200);
			for (String buf = ""; (buf = br.readLine()) != null;) {
				if (buf == null || buf.trim().equals(""))
					continue;
				words.add(buf);
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		} finally {
			try {
				if (br != null)
					br.close();
			} catch (IOException e) {
			}
		}
		return words;
	}

	/**
	 * 增加停顿词
	 * 
	 * @param words
	 */
	private static void addStopWord(final List<String> words) {
		if (words != null && words.size() > 0) {
			char[] chs;
			for (String curr : words) {
				chs = curr.toCharArray();
				for (char c : chs) {
					stopwdSet.add(charConvert(c));
				}
			}
		}
	}

	/**
	 * 添加DFA节点
	 * @param words
	 */
	private static void addSensitiveWord(final List<String> words) {
		if (words != null && words.size() > 0) {
			char[] chs;
			int fchar;
			int lastIndex;
			WordNode fnode; // 首字母节点
			for (String curr : words) {
				chs = curr.toCharArray();
				fchar = charConvert(chs[0]);
				if (!set.contains(fchar)) {// 没有首字定义
					set.add(fchar);// 首字标志位 可重复add,反正判断了,不重复了
					fnode = new WordNode(fchar, chs.length == 1);
					nodes.put(fchar, fnode);
				} else {
					fnode = nodes.get(fchar);
					if (!fnode.isLast() && chs.length == 1)
						fnode.setLast(true);
				}
				lastIndex = chs.length - 1;
				for (int i = 1; i < chs.length; i++) {
					fnode = fnode.addIfNoExist(charConvert(chs[i]), i == lastIndex);
				}
			}
		}
	}

	/**
	 * 过滤判断 将敏感词转化为成屏蔽词
	 * @param src
	 * @return
	 */
	public static final String doFilter(final String src) {
		char[] chs = src.toCharArray();
		int length = chs.length;
		int currc;
		int k;
		WordNode node;
		for (int i = 0; i < length; i++) {
			currc = charConvert(chs[i]);
			if (!set.contains(currc)) {
				continue;
			}
			node = nodes.get(currc);// 日 2
			if (node == null)// 其实不会发生,习惯性写上了
				continue;
			boolean couldMark = false;
			int markNum = -1;
			if (node.isLast()) {// 单字匹配(日)
				couldMark = true;
				markNum = 0;
			}
			// 继续匹配(日你/日你妹),以长的优先
			// 你-3 妹-4 夫-5
			k = i;
			for (; ++k < length;) {
				int temp = charConvert(chs[k]);
				if (stopwdSet.contains(temp))
					continue;
				node = node.querySub(temp);
				if (node == null)// 没有了
					break;
				if (node.isLast()) {
					couldMark = true;
					markNum = k - i;// 3-2
				}
			}
			if (couldMark) {
				for (k = 0; k <= markNum; k++) {
					chs[k + i] = SIGN;
				}
				i = i + markNum;
			}
		}

		return new String(chs);
	}
	
	/**
	 * 是否包含敏感词
	 * @param src
	 * @return
	 */
	public static final boolean isContains(final String src) {
		char[] chs = src.toCharArray();
		int length = chs.length;
		int currc;
		int k;
		WordNode node;
		for (int i = 0; i < length; i++) {
			currc = charConvert(chs[i]);
			if (!set.contains(currc)) {
				continue;
			}
			node = nodes.get(currc);// 日 2
			if (node == null)// 其实不会发生,习惯性写上了
				continue;
			boolean couldMark = false;
			if (node.isLast()) {// 单字匹配(日)
				couldMark = true;
			}
			// 继续匹配(日你/日你妹),以长的优先
			// 你-3 妹-4 夫-5
			k = i;
			for (; ++k < length;) {
				int temp = charConvert(chs[k]);
				if (stopwdSet.contains(temp))
					continue;
				node = node.querySub(temp);
				if (node == null)// 没有了
					break;
				if (node.isLast()) {
					couldMark = true;
				}
			}
			if (couldMark) {
				return true;
			}
		}

		return false;
	}

	/**
	 * 大写转化为小写 全角转化为半角
	 * 
	 * @param src
	 * @return
	 */
	private static int charConvert(char src) {
		int r = BCConvert.qj2bj(src);
		return (r >= 'A' && r <= 'Z') ? r + 32 : r;
	}

}

 

 

其中:

isContains :是否包含敏感词

doFilter:过滤敏感词

 

2、WordNode敏感词节点

 

[java] view plain copyprint?

  1. package org.andy.sensitivewdfilter;
  2.  
  3. import java.util.LinkedList;
  4. import java.util.List;
  5.  
  6. /**
  7. * 创建时间:2016年8月30日 下午3:07:45
  8. *
  9. * @author andy
  10. * @version 2.2
  11. */
  12. public class WordNode {
  13.  
  14. private int value; // 节点名称
  15.  
  16. private List<WordNode> subNodes; // 子节点
  17.  
  18. private boolean isLast;// 默认false
  19.  
  20. public WordNode(int value) {
  21. this.value = value;
  22. }
  23.  
  24. public WordNode(int value, boolean isLast) {
  25. this.value = value;
  26. this.isLast = isLast;
  27. }
  28.  
  29. /**
  30. *
  31. * @param subNode
  32. * @return 就是传入的subNode
  33. */
  34. private WordNode addSubNode(final WordNode subNode) {
  35. if (subNodes == null)
  36. subNodes = new LinkedList<WordNode>();
  37. subNodes.add(subNode);
  38. return subNode;
  39. }
  40.  
  41. /**
  42. * 有就直接返回该子节点, 没有就创建添加并返回该子节点
  43. *
  44. * @param value
  45. * @return
  46. */
  47. public WordNode addIfNoExist(final int value, final boolean isLast) {
  48. if (subNodes == null) {
  49. return addSubNode(new WordNode(value, isLast));
  50. }
  51. for (WordNode subNode : subNodes) {
  52. if (subNode.value == value) {
  53. if (!subNode.isLast && isLast)
  54. subNode.isLast = true;
  55. return subNode;
  56. }
  57. }
  58. return addSubNode(new WordNode(value, isLast));
  59. }
  60.  
  61. public WordNode querySub(final int value) {
  62. if (subNodes == null) {
  63. return null;
  64. }
  65. for (WordNode subNode : subNodes) {
  66. if (subNode.value == value)
  67. return subNode;
  68. }
  69. return null;
  70. }
  71.  
  72. public boolean isLast() {
  73. return isLast;
  74. }
  75.  
  76. public void setLast(boolean isLast) {
  77. this.isLast = isLast;
  78. }
  79.  
  80. @Override
  81. public int hashCode() {
  82. return value;
  83. }
  84.  
  85. }
package org.andy.sensitivewdfilter;

import java.util.LinkedList;
import java.util.List;

/**
 * 创建时间:2016年8月30日 下午3:07:45
 * 
 * @author andy
 * @version 2.2
 */
public class WordNode {

	private int value; // 节点名称

	private List<WordNode> subNodes; // 子节点

	private boolean isLast;// 默认false

	public WordNode(int value) {
		this.value = value;
	}

	public WordNode(int value, boolean isLast) {
		this.value = value;
		this.isLast = isLast;
	}

	/**
	 * 
	 * @param subNode
	 * @return 就是传入的subNode
	 */
	private WordNode addSubNode(final WordNode subNode) {
		if (subNodes == null)
			subNodes = new LinkedList<WordNode>();
		subNodes.add(subNode);
		return subNode;
	}

	/**
	 * 有就直接返回该子节点, 没有就创建添加并返回该子节点
	 * 
	 * @param value
	 * @return
	 */
	public WordNode addIfNoExist(final int value, final boolean isLast) {
		if (subNodes == null) {
			return addSubNode(new WordNode(value, isLast));
		}
		for (WordNode subNode : subNodes) {
			if (subNode.value == value) {
				if (!subNode.isLast && isLast)
					subNode.isLast = true;
				return subNode;
			}
		}
		return addSubNode(new WordNode(value, isLast));
	}

	public WordNode querySub(final int value) {
		if (subNodes == null) {
			return null;
		}
		for (WordNode subNode : subNodes) {
			if (subNode.value == value)
				return subNode;
		}
		return null;
	}

	public boolean isLast() {
		return isLast;
	}

	public void setLast(boolean isLast) {
		this.isLast = isLast;
	}

	@Override
	public int hashCode() {
		return value;
	}

}

 

 

 

 

三、测试结果

 

项目包含敏感词库,源码,停顿词库等,只需运行maven打jar包直接可运行。

 

 

 

博客来源http://blog.csdn.net/fengshizty?viewmode=list

项目源码http://download.csdn.net/detail/fengshizty/9617695

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值