【Lucene3.6.2入门系列】第05节_自定义停用词分词器和同义词分词器

首先是用于显示分词信息的HelloCustomAnalyzer.java

 

package com.jadyer.lucene;

import java.io.IOException;
import java.io.StringReader;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;
import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
import org.apache.lucene.analysis.tokenattributes.TypeAttribute;

/**
 * 【Lucene3.6.2入门系列】第05节_自定义分词器
 * @see -----------------------------------------------------------------------------------------------------------------------
 * @see Lucene3.5推荐的四大分词器:SimpleAnalyzer,StopAnalyzer,WhitespaceAnalyzer,StandardAnalyzer
 * @see 这四大分词器有一个共同的抽象父类,此类有个方法public final TokenStream tokenStream(),即分词的一个流
 * @see 假设有这样的文本"how are you thank you",实际它是以一个java.io.Reader传进分词器中
 * @see Lucene分词器处理完毕后,会把整个分词转换为TokenStream,这个TokenStream中就保存所有的分词信息
 * @see TokenStream有两个实现类,分别为Tokenizer和TokenFilter
 * @see Tokenizer---->用于将一组数据划分为独立的语汇单元(即一个一个的单词)
 * @see TokenFilter-->过滤语汇单元
 * @see -----------------------------------------------------------------------------------------------------------------------
 * @see 分词流程
 * @see 1)将一组数据流java.io.Reader交给Tokenizer,由其将数据转换为一个个的语汇单元
 * @see 2)通过大量的TokenFilter对已经分好词的数据进行过滤操作,最后产生TokenStream
 * @see 3)通过TokenStream完成索引的存储
 * @see -----------------------------------------------------------------------------------------------------------------------
 * @see Tokenizer的一些子类
 * @see KeywordTokenizer-----不分词,传什么就索引什么
 * @see StandardTokenizer----标准分词,它有一些较智能的分词操作,诸如将'jadyer@yeah.net'中的'yeah.net'当作一个分词流
 * @see CharTokenizer--------针对字符进行控制的,它还有两个子类WhitespaceTokenizer和LetterTokenizer
 * @see WhitespaceTokenizer--使用空格进行分词,诸如将'Thank you,I am jadyer'会被分为4个词
 * @see LetterTokenizer------基于文本单词的分词,它会根据标点符号来分词,诸如将'Thank you,I am jadyer'会被分为5个词
 * @see LowerCaseTokenizer---它是LetterTokenizer的子类,它会将数据转为小写并分词
 * @see -----------------------------------------------------------------------------------------------------------------------
 * @see TokenFilter的一些子类
 * @see StopFilter--------它会停用一些语汇单元
 * @see LowerCaseFilter---将数据转换为小写
 * @see StandardFilter----对标准输出流做一些控制
 * @see PorterStemFilter--还原一些数据,比如将coming还原为come,将countries还原为country
 * @see -----------------------------------------------------------------------------------------------------------------------
 * @see eg:'how are you thank you'会被分词为'how','are','you','thank','you'合计5个语汇单元
 * @see 那么应该保存什么东西,才能使以后在需要还原数据时保证正确的还原呢???其实主要保存三个东西,如下所示
 * @see CharTermAttribute(Lucene3.5以前叫TermAttribute),OffsetAttribute,PositionIncrementAttribute
 * @see 1)CharTermAttribute-----------保存相应的词汇,这里保存的就是'how','are','you','thank','you'
 * @see 2)OffsetAttribute-------------保存各词汇之间的偏移量(大致理解为顺序),比如'how'的首尾字母偏移量为0和3,'are'为4和7,'thank'为12和17
 * @see 3)PositionIncrementAttribute--保存词与词之间的位置增量,比如'how'和'are'增量为1,'are'和'you'之间的也是1,'you'和'thank'的也是1
 * @see                               但假设'are'是停用词(StopFilter的效果),那么'how'和'you'之间的位置增量就变成了2
 * @see 当我们查找某一个元素时,Lucene会先通过位置增量来取这个元素,但如果两个词的位置增量相同,会发生什么情况呢
 * @see 假设还有一个单词'this',它的位置增量和'how'是相同的,那么当我们在界面中搜索'this'时
 * @see 也会搜到'how are you thank you',这样就可以有效的做同义词了,目前非常流行的一个叫做WordNet的东西,就可以做同义词的搜索
 * @see -----------------------------------------------------------------------------------------------------------------------
 * @create Aug 4, 2013 5:48:25 PM
 * @author 玄玉<http://blog.csdn.net/jadyer>
 */
public class HelloCustomAnalyzer {
	/**
	 * 查看分词信息
	 * @see TokenStream还有两个属性,分别为FlagsAttribute和PayloadAttribute,都是开发时用的
	 * @see FlagsAttribute----标注位属性
	 * @see PayloadAttribute--做负载的属性,用来检测是否已超过负载,超过则可以决定是否停止搜索等等
	 * @param txt        待分词的字符串
	 * @param analyzer   所使用的分词器
	 * @param displayAll 是否显示所有的分词信息
	 */
	public static void displayTokenInfo(String txt, Analyzer analyzer, boolean displayAll){
		//第一个参数没有任何意义,可以随便传一个值,它只是为了显示分词
		//这里就是使用指定的分词器将'txt'分词,分词后会产生一个TokenStream(可将分词后的每个单词理解为一个Token)
		TokenStream stream = analyzer.tokenStream("此参数无意义", new StringReader(txt));
		//用于查看每一个语汇单元的信息,即分词的每一个元素
		//这里创建的属性会被添加到TokenStream流中,并随着TokenStream而增加(此属性就是用来装载每个Token的,即分词后的每个单词)
		//当调用TokenStream.incrementToken()时,就会指向到这个单词流中的第一个单词,即此属性代表的就是分词后的第一个单词
		//可以形象的理解成一只碗,用来盛放TokenStream中每个单词的碗,每调用一次incrementToken()后,这个碗就会盛放流中的下一个单词
		CharTermAttribute cta = stream.addAttribute(CharTermAttribute.class);
		//用于查看位置增量(指的是语汇单元之间的距离,可理解为元素与元素之间的空格,即间隔的单元数)
		PositionIncrementAttribute pia = stream.addAttribute(PositionIncrementAttribute.class);
		//用于查看每个语汇单元的偏移量
		OffsetAttribute oa = stream.addAttribute(OffsetAttribute.class);
		//用于查看使用的分词器的类型信息
		TypeAttribute ta = stream.addAttribute(TypeAttribute.class);
		try {
			if(displayAll){
				//等价于while(stream.incrementToken())
				for(; stream.incrementToken() ;){
					System.out.println(ta.type() + " " + pia.getPositionIncrement() + " ["+oa.startOffset()+"-"+oa.endOffset()+"] ["+cta+"]");
				}
			}else{
				System.out.println();
				while(stream.incrementToken()){
					System.out.print("[" + cta + "]");
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}


下面是自定义的停用词分词器MyStopAnalyzer.java

 

package com.jadyer.analysis;

import java.io.Reader;
import java.util.Set;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.LetterTokenizer;
import org.apache.lucene.analysis.LowerCaseFilter;
import org.apache.lucene.analysis.StopAnalyzer;
import org.apache.lucene.analysis.StopFilter;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.util.Version;

/**
 * 自定义的停用词分词器
 * @see 它主要用来过滤指定的字符串(忽略大小写)
 * @create Aug 5, 2013 1:55:15 PM
 * @author 玄玉<http://blog.csdn.net/jadyer>
 */
public class MyStopAnalyzer extends Analyzer {
	private Set<Object> stopWords; //存放停用的分词信息
	
	/**
	 * 自定义的用于过滤指定字符串的分词器
	 * @param _stopWords 用于指定所要过滤的字符串(忽略大小写)
	 */
	public MyStopAnalyzer(String[] _stopWords){
		//会自动将字符串数组转换为Set
		stopWords = StopFilter.makeStopSet(Version.LUCENE_36, _stopWords, true);
		//将原有的停用词加入到现在的停用词中
		stopWords.addAll(StopAnalyzer.ENGLISH_STOP_WORDS_SET);
	}
	
	@Override
	public TokenStream tokenStream(String fieldName, Reader reader) {
		//为这个分词器设定过滤器链和Tokenizer
		return new StopFilter(Version.LUCENE_36,
						//这里就可以存放很多的TokenFilter
						new LowerCaseFilter(Version.LUCENE_36, new LetterTokenizer(Version.LUCENE_36, reader)),
						stopWords);
	}
}


下面是自定义的同义词分词器MySynonymAnalyzer.java

 

 

package com.jadyer.analysis;

import java.io.IOException;
import java.io.Reader;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenFilter;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
import org.apache.lucene.util.AttributeSource;

import com.chenlb.mmseg4j.ComplexSeg;
import com.chenlb.mmseg4j.Dictionary;
import com.chenlb.mmseg4j.analysis.MMSegTokenizer;

/**
 * 自定义的同义词分词器
 * @create Aug 5, 2013 5:11:46 PM
 * @author 玄玉<http://blog.csdn.net/jadyer>
 */
public class MySynonymAnalyzer extends Analyzer {
	@Override
	public TokenStream tokenStream(String fieldName, Reader reader) {
		//借助MMSeg4j实现自定义分词器,写法参考MMSegAnalyzer类的tokenStream()方法
		//但为了过滤并处理分词后的各个语汇单元,以达到同义词分词器的功能,故自定义一个TokenFilter
		//实际执行流程就是字符串的Reader首先进入MMSegTokenizer,由其进行分词,分词完毕后进入自定义的MySynonymTokenFilter
		//然后在MySynonymTokenFilter中添加同义词
		return new MySynonymTokenFilter(new MMSegTokenizer(new ComplexSeg(Dictionary.getInstance()), reader));
	}
}


/**
 * 自定义的TokenFilter
 * @create Aug 5, 2013 5:11:58 PM
 * @author 玄玉<http://blog.csdn.net/jadyer>
 */
class MySynonymTokenFilter extends TokenFilter {
	private CharTermAttribute cta;              //用于获取TokenStream中的语汇单元
	private PositionIncrementAttribute pia;     //用于获取TokenStream中的位置增量
	private AttributeSource.State tokenState;   //用于保存语汇单元的状态
	private Stack<String> synonymStack;         //用于保存同义词
	
	protected MySynonymTokenFilter(TokenStream input) {
		super(input);
		this.cta = this.addAttribute(CharTermAttribute.class);
		this.pia = this.addAttribute(PositionIncrementAttribute.class);
		this.synonymStack = new Stack<String>();
	}
	
	/**
	 * 判断是否存在同义词
	 */
	private boolean isHaveSynonym(String name){
		//先定义同义词的词典
		Map<String, String[]> synonymMap = new HashMap<String, String[]>();
		synonymMap.put("我", new String[]{"咱", "俺"});
		synonymMap.put("中国", new String[]{"兲朝", "大陆"});
		if(synonymMap.containsKey(name)){
			for(String str : synonymMap.get(name)){
				this.synonymStack.push(str);
			}
			return true;
		}
		return false;
	}

	@Override
	public boolean incrementToken() throws IOException {
		while(this.synonymStack.size() > 0){
			restoreState(this.tokenState); //将状态还原为上一个元素的状态
			cta.setEmpty();
			cta.append(this.synonymStack.pop()); //获取并追加同义词
			pia.setPositionIncrement(0);         //设置位置增量为0
			return true;
		}
		if(input.incrementToken()){
			//注意:当发现当前元素存在同义词之后,不能立即追加同义词,即不能在目标元素上直接处理
			if(this.isHaveSynonym(cta.toString())){
				this.tokenState = captureState(); //存在同义词时,则捕获并保存当前状态
			}
			return true;
		}else {
			return false; //只要TokenStream中没有元素,就返回false
		}
	}
}


最后是JUnit4.x编写的小测试

 

 

package com.jadyer.test;

import org.apache.lucene.analysis.StopAnalyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.RAMDirectory;
import org.apache.lucene.util.Version;
import org.junit.Test;

import com.jadyer.analysis.MyStopAnalyzer;
import com.jadyer.analysis.MySynonymAnalyzer;
import com.jadyer.lucene.HelloCustomAnalyzer;

public class HelloCustomAnalyzerTest {
	/**
	 * 测试自定义的用于过滤指定字符串(忽略大小写)的停用词分词器
	 */
	@Test
	public void stopAnalyzer(){
		String txt = "This is my house, I`m come from Haerbin,My email is jadyer@yeah.net, My QQ is 517751422";
		HelloCustomAnalyzer.displayTokenInfo(txt, new StandardAnalyzer(Version.LUCENE_36), false);
		HelloCustomAnalyzer.displayTokenInfo(txt, new StopAnalyzer(Version.LUCENE_36), false);
		HelloCustomAnalyzer.displayTokenInfo(txt, new MyStopAnalyzer(new String[]{"I", "EMAIL", "you"}), false);
	}
	

	/**
	 * 测试自定义的同义词分词器
	 */
	@Test
	public void synonymAnalyzer(){
		String txt = "我来自中国黑龙江省哈尔滨市巴彦县兴隆镇";
		IndexWriter writer = null;
		IndexSearcher searcher = null;
		Directory directory = new RAMDirectory();
		try {
			writer = new IndexWriter(directory, new IndexWriterConfig(Version.LUCENE_36, new MySynonymAnalyzer()));
			Document doc = new Document();
			doc.add(new Field("content", txt, Field.Store.YES, Field.Index.ANALYZED));
			writer.addDocument(doc);
			writer.close(); //搜索前要确保IndexWriter已关闭,否则会报告异常org.apache.lucene.index.IndexNotFoundException: no segments* file found
			searcher = new IndexSearcher(IndexReader.open(directory));
			TopDocs tds = searcher.search(new TermQuery(new Term("content", "咱")), 10);
			for(ScoreDoc sd : tds.scoreDocs){
				System.out.println(searcher.doc(sd.doc).get("content"));
			}
			searcher.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		HelloCustomAnalyzer.displayTokenInfo(txt, new MySynonymAnalyzer(), true);
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
目录 序言: 1 第一章 LUCENE基础 2 1.1 索引部分的核心类 2 1.2 分词部分的核心类 2 1.3 搜索部分的核心类 2 第二章 索引建立 3 2.1 创建Directory 3 2.2 创建Writer 3 2.3 创建文档并且添加索引 4 2.4 查询索引的基本信息 5 2.5 删除和更新索引 5 (1) 使用writer删除 5 (2) 使用reader删除 5 (3) 恢复删除 5 (4) 彻底删除 6 (5) 更新索引 6 (6) 手动优化 6 2.6 索引文件作用 7 第三章 搜索功能 8 3.1 简单搜索 8 (1) 创建IndexReader 8 (2) 创建IndexSearcher 8 (3) 创建Term和TermQuery 9 (5) 根据TopDocs获取ScoreDoc 9 (6) 根据ScoreDoc获取相应文档 9 3.2 其他搜索 9 (1) 范围查询(TermRangeQuery) 10 (2) 数字查询(NumericRangeQuery) 11 (3) 前缀查询(PrefixQuery) 11 (4) 通配符查询(WildcardQuery) 11 (5) 多条件查询(BooleanQuery) 12 (6) 短语查询(PhraseQuery) 12 (7) 模糊查询(FuzzyQuery) 12 3.3 QueryParser 13 (1) 创建QueryParser 13 (2) 各种匹配方式 13 3.4 分页搜索 14 (1) 普通分页 14 (2) searchAfter分页 15 第四章 分词基础 17 4.1 分词效果 17 (1) 准备分词输出类 17 (2) 创建分词器 18 (3) 英文分词效果 18 (4) 中文分词效果 19 4.2 分词原理 21 (1) TokenStream 21 (2) Tokenizer 22 (3) TokenFilter 23 4.3 分词属性 23 (1) 分词属性查看 24 (2) 分词属性对比 25 4.4 自定义分词器 26 (1) 自定义Stop分词器 26 (2) 实现简单同义词索引 27 第五章 高级搜索 32 5.1 搜索排序 34 (1) 建立搜索类 34 (2) 默认排序 35 (3) 根据评分排序 35 (4) 根据索引号排序 36 (5) 根据文件大小排序 36 (6) 根据日期排序 37 (7) 根据文件名排序(倒序) 37 (8) 多条件排序 38 5.2 搜索过滤 39 (1) 建立搜索类 39 (2) 文本域范围过滤(TermRangeFilter) 40 (3) 数字域范围过滤(NumericRangeFilter) 40 (4) 查询结果过滤(QueryWrapperFilter) 40 5.3 自定义评分 41 (1) 创建一个类继承CustomScoreQuery 41 (2) 创建一个类继承CustomScoreProvider 42 (3) 自定义评分效果测试 43 5.4 自定义QueryParser 44 (1) 限制低性能的QueryParser 44 (2) 扩展基于数字和日期的查询 45 (3) 自定义QueryParser效果测试 46 5.5 自定义过滤器 49 (1) 分析需求,创建接口 49 (2) 创建过滤器,继承Filter 50 (3) 实现接口,效果演示 52 第六章 LUCENE扩展 54 6.1 Luke 54 (1) 启动Luke 54 (2) 索引概述页面 55 (3) 查看索引信息 56 (4) 查询索引信息 57 6.2 Tika 58 (1) Tika的使用 58 (2) Tika的原理 59 6.3 高亮显示 61 (1) 自定义高亮标签 61 (2) 多个域高亮显示 62 6.4 近实时搜索 65 (1) 近实时搜索的创建 66 (2) 近实时搜索的使用 67

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值