首先是用于显示分词信息的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. DTcms 导航选中样式以及简化方法

    (建议使用方法2,执行效率略高) 一般用于导航不能循环输出的情况. 可以循环输出导航的情况直接用if判断即可. 首页模版中顶部,自定义c#代码. <%set string channel=&qu ...

  2. ASP.NET文件上传的三种基本方法

    ASP.NET依托.net framework类库,封装了大量的功能,使得上传文件非常简单,主要有以下三种基本方法. 方法一:用Web控件FileUpload,上传到网站根目录. <form i ...

  3. gridview 字段没有绑定由于column visible= false

    由于gridview column visible=false, 后面执行gridview databound()操作 不会将数据绑定到相关的单元格,其实这个时候我们希望绑定数据只是不显示而已. 可以 ...

  4. Linux恢复删除文件

    一.介绍extundelete 1.extundelete的文件恢复工具,该工具最给力的一点就是支持ext3/ext4双格式分区恢复. 2. 在实际线上恢复过程中,切勿将extundelete安装到你 ...

  5. The content of element type "sqlMapConfig" is incomplete,

    The content of element type "sqlMapConfig" is incomplete, it must match "(properties? ...

  6. iOS中为网站添加图标到主屏幕以及增加启动画面

    虽然没有能力开发Native App,但还是可以利用iOS中Safari浏览器的特性小小的折腾一下,做一个伪Web App满足下小小的虚荣心的. 既然是在iOS中的Safari折腾的,那么代码中利用到 ...

  7. shell调用sqlplus批量执行sql文件

    在最近的工作中,经常需要批量执行一些DML, DDL, PL/SQL语句或导入一些Function, Procedure.因为support的国家比较多,常常需要一个登陆到一个国家的数据库上执行完成后 ...

  8. 【MyBatis】 通过包含的jdbcType类型

    BIT         FLOAT      CHAR           TIMESTAMP       OTHER       UNDEFINED TINYINT     REAL       V ...

  9. sql shard/partition

    sql http://www.infoq.com/news/2011/02/SQL-Sharding/ http://channel9.msdn.com/Shows/Data-Exposed/SqlD ...

  10. How to open MS word document from the SharePoint 2010 using Microsoft.Office.Interop.dll

    or this you must change the identity of word component inC:\windows\System32\comexp.mscto be interac ...