敏感字过滤

一、前言

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

具体实现:

 1、匹配大小写过滤

 2、匹配全角半角过滤

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

 4、敏感词重复词过滤。


例如:

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

fuck 全小写

FuCk 大小写

fuck全角半角

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

fffuuuucccckkk 重复词

二、代码实现

    其目录结构如下:



   其中resources资源目录中:

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

wd.txt:敏感词库。


   1、WordFilter敏感词过滤类


    

  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>(10241); // 存储节点  
  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敏感词节点

   

  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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值