树结构==>查询实现屏蔽字功能

这篇博客探讨了如何利用二叉树数据结构来实现查询屏蔽字的功能。作者特别提到,对于熟悉二叉树的读者来说,这是一个经典的应用场景。通常,二叉树的节点包含一个存储值的字段value,可以用来存放屏蔽字,从而高效地进行查找操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我最熟悉的是二叉树,最经典的是存的数字,一个节点Node,里面有个存储值的字段value

public class Node{
	private String value;
	private Node left;
	private Node right;
}

一个左节点Node,一个右节点Node.
1、在构建二叉树的时候,在添加一个节点的时候,先比较这个节点的value值和根节点value值的大小,小于,则放到左节点。
大于,则放到右节点--递归调用。
2、查询比较也是这种顺序,先和根节点判断,如果小于,就交给左节点(递归)


屏蔽词树结构:
他不是一个二叉树,他一个子节点下面,可能有很多个节点,二叉树只有两个节点。
比如要添加的屏蔽词为“操你妹”,构建的原理是:每个词 “操|你|妹” 都是一个ScreenNode。他会把字符串拆成一个一个的
字符,建立ScreenNode节点,当长度等于字符串的长度的时候,才会给这个子节点赋值oldWord,和newWord。


构建:
public class ScreenNode
{
	/** 子节点列表,Map的key是子节点对应的字符 */
	private Map<Character, ScreenNode> childs = new HashMap<Character, ScreenNode>();

	/** 要替换的旧字符串*/
	private String oldWord;
	
	/** 新字符串*/
	private String newWord;
}

构建方法:
/**
 * 构建树结构
 * @param oldWords 要屏蔽的词,比如“我爱你”
 * @param idx 首次添加为0,递归调用会+1,直到等于屏蔽词的长度
 * @param newWords 屏蔽词的显示词,操你妹对应***
 */
private void add(String oldWords, int idx, String newWords){
	if(oldWords.length() == idx){
		//达了树的叶子节点,此时对该节点设置字符串对
		this.newWord = newWords;
		this.oldWord = oldWords;
		return;
	}
	
	//把屏蔽词拆分,一个字一个字建立子节点
	char next_ch = oldWords.charAt(idx);
	ScreenNode ti = childs.get(next_ch);
	if(ti == null){
		ti = new ScreenNode();
		childs.put(next_ch, ti);
	}
	
	//读下一个字符,递归建立下一个字节点
	ti.add(oldWords, idx + 1, newWords); //调用递归取下一个字
}

整个类:
package com.clou.dooliu.global;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import com.clou.dooliu.log.LocalLogger;

/**
 * 违禁词过滤:用于替换多个字符串的类
 * 说明:只能替换完全匹配的违禁词(不是模糊匹配)
 * @author meteor
 *
 */
public class ScreenNode
{
	/** 子节点列表,Map的key是子节点对应的字符 */
	private Map<Character, ScreenNode> childs = new HashMap<Character, ScreenNode>();
	
	private Logger log = LocalLogger.getLogger("screening");

	/** 要替换的旧字符串*/
	private String oldWord;
	
	/** 新字符串*/
	private String newWord;
	
	
	private ScreenNode(){}
	
	/**
	 * 添加一对替换关键字
	 * @param oldWords	要替换的字符串
	 * @param newWords	新字符串
	 */
	private void add(String oldWords, String newWords){
		add(oldWords, 0, newWords);
	}

/**
 * 构建树结构
 * @param oldWords 要屏蔽的词,比如“我爱你”
 * @param idx 首次添加为0,递归调用会+1,直到等于屏蔽词的长度
 * @param newWords 屏蔽词的显示词,我爱你对应***
 */
private void add(String oldWords, int idx, String newWords){
	if(oldWords.length() == idx){
		//达了树的叶子节点,此时对该节点设置字符串对
		this.newWord = newWords;
		this.oldWord = oldWords;
		return;
	}
	
	//把屏蔽词拆分,一个字一个字建立子节点
	char next_ch = oldWords.charAt(idx);
	ScreenNode ti = childs.get(next_ch);
	if(ti == null){
		ti = new ScreenNode();
		childs.put(next_ch, ti);
	}
	
	//读下一个字符,递归建立下一个字节点
	ti.add(oldWords, idx + 1, newWords); //调用递归取下一个字
}
	
	/**
	 * 删除某个特定的key
	 * @param oldWords
	 */
	public void del(String oldWords){
		if(StringUtils.isBlank(oldWords))
			return;
		del(oldWords,0);
		
	}
	
	/**
	 * 删除
	 * @param oldWords
	 */
	private void del(String oldWords,int idx){
		if(oldWords.length() == idx){
			this.newWord = null;
			this.oldWord = null;
			return;
		}
		char next_ch = oldWords.charAt(idx);
		ScreenNode ti = childs.get(next_ch);
		if(ti == null)
			return;
		if(ti != null){
			ti.del(oldWords,idx+1);
		}
	}
	
	/**
	 * 添加单个关键字
	 * @param value
	 */
	public void add(String value){
		StringBuilder s = new StringBuilder();
		for(int i=0;i<value.length();i++)
			s.append("*");
		//根据违规字的长度,拼凑长度相同的*
		this.add(value, s.toString());
	}
	
	/**
	 * 批量添加
	 * @param values
	 */
	public void add(List<String> values){
		for(String oldWords : values){
			StringBuilder s = new StringBuilder();
			for(int i=0;i<oldWords.length();i++)
				s.append("*");
			this.add(oldWords, s.toString());
		}
	}
	
	
	/**
	 * 删除所有内存中的关键字Key
	 */
	public void delAllKey(){
		childs.clear();
	}
	
	/**
	 * 用于存放查找关键字时的位置信息
	 * @author meteor
	 *
	 */
	private static class FindResultPosInfo{
		String oldWord;		// 找到的旧关键字
		String newWord;		// 要替换成的新字符串
		int idx;			// 关键字出现的位置
		
		FindResultPosInfo(String oldWord, String newWord, int idx)
		{
			this.oldWord = oldWord;
			this.newWord = newWord;
			this.idx = idx;
		}
	}
	
	
	/**
	 * 从字符串指定位置开始比较当前位置是否有关键字匹配
	 */
		// 找到匹配节点,并且该节点没有子节点(满足此条件是为了最长匹配)时,直接返回当前位置
	private FindResultPosInfo compareWords(String s, int idx){
		if(this.oldWord != null && this.childs.size() == 0)
		{
			return new FindResultPosInfo(this.oldWord, this.newWord, idx - this.oldWord.length());
		}
		
		// 如果字符串结束,则返回null
		if(idx >= s.length())
			return null;

		// 查找匹配的字节点
		char c = s.charAt(idx);
		ScreenNode ti = childs.get(c);
		if(ti == null)
			return null;
		
		// 递归匹配其余的字节点
		FindResultPosInfo fi = ti.compareWords(s, idx + 1);
		
		// 如果其余的字节点不匹配,则检查当前节点是否需要替换的节点
		if(fi == null && ti.oldWord != null)
			return new FindResultPosInfo(ti.oldWord, ti.newWord, idx + 1 - ti.oldWord.length());

		return fi;
	}
	
	private FindResultPosInfo findWords(String s, int idx,Object userId,String source){
		// 逐个位置比较是否匹配替换关键字
		for(int i = idx; i < s.length(); ++i){
			FindResultPosInfo wi = compareWords(s, i);
			if(wi != null){
				if(log.isInfoEnabled())
					log.info(userId +"\t"+ source +"\t"+wi.oldWord +"\t"+s);
				return wi;
			}
		}
		return null;
	}
	
	/**
	 * 检查字符串中是否存在需要替换的关键字
	 * @param s		要检查的字符串
	 * @userId 用户id,主要为了打印日志记录
	 * @source 来源,可以不填,主要为了打印日志记录
	 * @return
	 */
	public boolean existWords(String s,Object userId,String source){
		return findWords(s, 0,userId,source) != null;
	}
	
	/**
	 * 替换字符串中的旧字符串
	 * @param s	要替换的字符串
	 * @userId 用户id,主要为了打印日志记录
	 * @source 来源,可以不填,主要为了打印日志记录
	 * @return 
	 */
	public String replace(String s,Object userId,String source){
		FindResultPosInfo wi = findWords(s, 0,userId,source);		// 查找第一个替换的位置
		if(wi == null)
			return s;
		StringBuilder sb = new StringBuilder(s.length());
		int writen = 0;
		for(; wi != null && wi.idx < s.length(); ){
			sb.append(s, writen, wi.idx);			// append 原字符串不需替换部分
			sb.append(wi.newWord);					// append 新字符串
			writen = wi.idx + wi.oldWord.length();	// 忽略原字符串需要替换部分
			wi = findWords(s, writen,userId,source);				// 查找下一个替换位置
		}
		sb.append(s, writen, s.length());			// 替换剩下的原字符串
		return sb.toString();
	}
	
	
	private static class SingletonContainer{     
        private static ScreenNode manager = new ScreenNode();  
    }
	
	public static ScreenNode getInstance(){
		return SingletonContainer.manager;
	}
	
	@Override
	public String toString() {
		return "ScreenUtil [childs=" + childs + ", oldWord=" + oldWord
				+ ", newWord=" + newWord + "]";
	}

	public static void main(String[] argv)
	{
		//这是用户输入的文字,中间可能有违禁词
		String s = "我是一个日本人,不是日耳曼名族,是一个大好人,hahahahh,i love you av";
		System.out.println("src: " + s);
		
		ScreenNode ti = ScreenNode.getInstance();
		//加入违禁词
		ti.add("日本人");
		ti.add("日耳曼人");
		ti.add("av");
		
		//删除违禁词
		ti.del("av",0);
//		ti.del(" av ", 0);
//		ti.delAllKey();
		
		String ss = ti.replace(s,null,null);
		
		Boolean flag = ti.existWords(s,null,null);
		System.out.println("是否含有违禁词:" + flag);
		System.out.println("result: " + ss);
	}
}

初始化从数据库加载sceening列表
/**
 * 缓存屏蔽字
 * 
 * @param screens
 */
public void cacheScreens(List<?> screens) {
	if(screens != null && !screens.isEmpty()){
		ScreenNode.getInstance().delAllKey();
		for (Object obj : screens) {
			Screening scr = (Screening) obj;
			ScreenNode.getInstance().add(scr.getValue()); //将数据加入到内存
		}
	}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值