首先是用于显示分词信息的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);
}
}

【Lucene3.6.2入门系列】第05节_自定义停用词分词器和同义词分词器的更多相关文章

  1. 【Lucene3.6.2入门系列】第04节_中文分词器

    package com.jadyer.lucene; import java.io.IOException; import java.io.StringReader; import org.apach ...

  2. 【Lucene3.6.2入门系列】第03节_简述Lucene中常见的搜索功能

    package com.jadyer.lucene; import java.io.File; import java.io.IOException; import java.text.SimpleD ...

  3. 【Lucene3.6.2入门系列】第14节_SolrJ操作索引和搜索文档以及整合中文分词

    package com.jadyer.solrj; import java.util.ArrayList; import java.util.List; import org.apache.solr. ...

  4. 【Lucene3.6.2入门系列】第15节_SolrJ高亮

    package com.jadyer.solrj; import java.util.ArrayList; import java.util.List; import java.util.Map; i ...

  5. 【Lucene3.6.2入门系列】第10节_Tika

    首先贴出来的是演示了借助Tika创建索引的HelloTikaIndex.java PS:关于Tika的介绍及用法,详见下方的HelloTika.java package com.jadyer.luce ...

  6. ASP.NET AJAX入门系列(8):自定义异常处理

    在UpdatePanel控件异步更新时,如果有错误发生,默认情况下会弹出一个Alert对话框显示出错误信息,这对用户来说是不友好的,本文看一下如何在服务端和客户端脚本中自定义异常处理,翻译自官方文档. ...

  7. STM32入门系列-STM32时钟系统,自定义系统时钟

    在时钟树的讲解中我们知道,通过修改PLLMUL中的倍系数值(2-16)可以改变系统的时钟频率.在库函数中也有对时钟倍频因子配置的函数,如下: void RCC_PLLConfig(uint32_t R ...

  8. 【168】ENVI入门系列

    参考:ENVI-IDL中国的博客 [ENVI入门系列]01.ENVI产品简介与入门 [ENVI入门系列]02.自定义坐标系(北京54.西安80.2000坐标系) [ENVI入门系列]03.基于自带定位 ...

  9. ASP.NET AJAX入门系列

    ASP.NET AJAX入门系列将会写关于ASP.NET AJAX一些控件的使用方法以及基础知识,其中部分文章为原创,也有一些文章是直接翻译自官方文档,本部分内容会不断更新. 目录 ASP.NET A ...

随机推荐

  1. 模板:多Case输入处理

    利用cin实现 while(cin >> value) { } 调试时使用Ctrl + Z 输入文件结束符

  2. Dribbble客户端应用源码

    简约大气的Dribbble客户端,帮你时刻紧跟潮流,版本描述,添加对Likes & Following的支持设计简约的Dribbble客户端,提供了全面的浏览功能,让你时刻紧跟潮流! A BE ...

  3. PHP 魔术方法 __isset __unset (三)

    慢慢长寻夜,明月高空挂 __isset()  - 在对类中属性或者非类中属性使用isset()方法的时候如果没有或者非公有属性,则自动执行__isset()的方法 __unset() - 在对类中属性 ...

  4. 一本JavaEE的案例书

    案例很好.1.网上书店 2.AJAX网页聊天

  5. html5实现渐变效果

    <canvas id='test01'></canvas> <script> function draw25(id) { var canvas = document ...

  6. CentOS平台下为Python添加MongoDB支持PyMongo

    下载PyMongo [root@leezhen ~]# wget https://pypi.python.org/packages/source/p/pymongo/pymongo-2.6.3.tar ...

  7. js中的计时器

    在JS中做二级菜单时,被一个鼠标移出时隐藏的小问题困扰了很久. <script> function Menu(id){ var _this=this; this.obj=document. ...

  8. RAC环境下SCAN IP可以PING通,1521端口也可以TELNET,但是无法建立数据库连接

    昨天用户请求帮助处理一个问题:有个厂家需要连某个业务系统的数据库,网络上已经开通了权限,SCAN IP可以PING通,测试TELNET 1521端口也是正常.但是想通过SQLPLUS连接,总是会提示连 ...

  9. Windows.Andy.Code4App.dll Win8.1/WP8.1通用类库@ver1.0.0

    直接入题! Win8.1和WP8.1眼下已经渐渐融为一体,WP8.1不断向Win8.1靠拢,虽然一些方法上WP8.1和Win8.1不同(ps:WP8.1和Win8.1的不同之处),但大部分还是相同的. ...

  10. 微软职位内部推荐-Senior Data Scientist

    微软近期Open的职位: Extracting accurate, insightful and actionable information from data is part art and pa ...