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

[java]  view plain copy print ?
  1. package com.jadyer.lucene;  
  2.   
  3. import java.io.IOException;  
  4. import java.io.StringReader;  
  5.   
  6. import org.apache.lucene.analysis.Analyzer;  
  7. import org.apache.lucene.analysis.TokenStream;  
  8. import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;  
  9. import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;  
  10. import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;  
  11. import org.apache.lucene.analysis.tokenattributes.TypeAttribute;  
  12.   
  13. /** 
  14.  * 【Lucene3.6.2入门系列】第05节_自定义分词器 
  15.  * @see ----------------------------------------------------------------------------------------------------------------------- 
  16.  * @see Lucene3.5推荐的四大分词器:SimpleAnalyzer,StopAnalyzer,WhitespaceAnalyzer,StandardAnalyzer 
  17.  * @see 这四大分词器有一个共同的抽象父类,此类有个方法public final TokenStream tokenStream(),即分词的一个流 
  18.  * @see 假设有这样的文本"how are you thank you",实际它是以一个java.io.Reader传进分词器中 
  19.  * @see Lucene分词器处理完毕后,会把整个分词转换为TokenStream,这个TokenStream中就保存所有的分词信息 
  20.  * @see TokenStream有两个实现类,分别为Tokenizer和TokenFilter 
  21.  * @see Tokenizer---->用于将一组数据划分为独立的语汇单元(即一个一个的单词) 
  22.  * @see TokenFilter-->过滤语汇单元 
  23.  * @see ----------------------------------------------------------------------------------------------------------------------- 
  24.  * @see 分词流程 
  25.  * @see 1)将一组数据流java.io.Reader交给Tokenizer,由其将数据转换为一个个的语汇单元 
  26.  * @see 2)通过大量的TokenFilter对已经分好词的数据进行过滤操作,最后产生TokenStream 
  27.  * @see 3)通过TokenStream完成索引的存储 
  28.  * @see ----------------------------------------------------------------------------------------------------------------------- 
  29.  * @see Tokenizer的一些子类 
  30.  * @see KeywordTokenizer-----不分词,传什么就索引什么 
  31.  * @see StandardTokenizer----标准分词,它有一些较智能的分词操作,诸如将'jadyer@yeah.net'中的'yeah.net'当作一个分词流 
  32.  * @see CharTokenizer--------针对字符进行控制的,它还有两个子类WhitespaceTokenizer和LetterTokenizer 
  33.  * @see WhitespaceTokenizer--使用空格进行分词,诸如将'Thank you,I am jadyer'会被分为4个词 
  34.  * @see LetterTokenizer------基于文本单词的分词,它会根据标点符号来分词,诸如将'Thank you,I am jadyer'会被分为5个词 
  35.  * @see LowerCaseTokenizer---它是LetterTokenizer的子类,它会将数据转为小写并分词 
  36.  * @see ----------------------------------------------------------------------------------------------------------------------- 
  37.  * @see TokenFilter的一些子类 
  38.  * @see StopFilter--------它会停用一些语汇单元 
  39.  * @see LowerCaseFilter---将数据转换为小写 
  40.  * @see StandardFilter----对标准输出流做一些控制 
  41.  * @see PorterStemFilter--还原一些数据,比如将coming还原为come,将countries还原为country 
  42.  * @see ----------------------------------------------------------------------------------------------------------------------- 
  43.  * @see eg:'how are you thank you'会被分词为'how','are','you','thank','you'合计5个语汇单元 
  44.  * @see 那么应该保存什么东西,才能使以后在需要还原数据时保证正确的还原呢???其实主要保存三个东西,如下所示 
  45.  * @see CharTermAttribute(Lucene3.5以前叫TermAttribute),OffsetAttribute,PositionIncrementAttribute 
  46.  * @see 1)CharTermAttribute-----------保存相应的词汇,这里保存的就是'how','are','you','thank','you' 
  47.  * @see 2)OffsetAttribute-------------保存各词汇之间的偏移量(大致理解为顺序),比如'how'的首尾字母偏移量为0和3,'are'为4和7,'thank'为12和17 
  48.  * @see 3)PositionIncrementAttribute--保存词与词之间的位置增量,比如'how'和'are'增量为1,'are'和'you'之间的也是1,'you'和'thank'的也是1 
  49.  * @see                               但假设'are'是停用词(StopFilter的效果),那么'how'和'you'之间的位置增量就变成了2 
  50.  * @see 当我们查找某一个元素时,Lucene会先通过位置增量来取这个元素,但如果两个词的位置增量相同,会发生什么情况呢 
  51.  * @see 假设还有一个单词'this',它的位置增量和'how'是相同的,那么当我们在界面中搜索'this'时 
  52.  * @see 也会搜到'how are you thank you',这样就可以有效的做同义词了,目前非常流行的一个叫做WordNet的东西,就可以做同义词的搜索 
  53.  * @see ----------------------------------------------------------------------------------------------------------------------- 
  54.  * @create Aug 4, 2013 5:48:25 PM 
  55.  * @author 玄玉<http://blog.csdn.net/jadyer> 
  56.  */  
  57. public class HelloCustomAnalyzer {  
  58.     /** 
  59.      * 查看分词信息 
  60.      * @see TokenStream还有两个属性,分别为FlagsAttribute和PayloadAttribute,都是开发时用的 
  61.      * @see FlagsAttribute----标注位属性 
  62.      * @see PayloadAttribute--做负载的属性,用来检测是否已超过负载,超过则可以决定是否停止搜索等等 
  63.      * @param txt        待分词的字符串 
  64.      * @param analyzer   所使用的分词器 
  65.      * @param displayAll 是否显示所有的分词信息 
  66.      */  
  67.     public static void displayTokenInfo(String txt, Analyzer analyzer, boolean displayAll){  
  68.         //第一个参数没有任何意义,可以随便传一个值,它只是为了显示分词  
  69.         //这里就是使用指定的分词器将'txt'分词,分词后会产生一个TokenStream(可将分词后的每个单词理解为一个Token)  
  70.         TokenStream stream = analyzer.tokenStream("此参数无意义"new StringReader(txt));  
  71.         //用于查看每一个语汇单元的信息,即分词的每一个元素  
  72.         //这里创建的属性会被添加到TokenStream流中,并随着TokenStream而增加(此属性就是用来装载每个Token的,即分词后的每个单词)  
  73.         //当调用TokenStream.incrementToken()时,就会指向到这个单词流中的第一个单词,即此属性代表的就是分词后的第一个单词  
  74.         //可以形象的理解成一只碗,用来盛放TokenStream中每个单词的碗,每调用一次incrementToken()后,这个碗就会盛放流中的下一个单词  
  75.         CharTermAttribute cta = stream.addAttribute(CharTermAttribute.class);  
  76.         //用于查看位置增量(指的是语汇单元之间的距离,可理解为元素与元素之间的空格,即间隔的单元数)  
  77.         PositionIncrementAttribute pia = stream.addAttribute(PositionIncrementAttribute.class);  
  78.         //用于查看每个语汇单元的偏移量  
  79.         OffsetAttribute oa = stream.addAttribute(OffsetAttribute.class);  
  80.         //用于查看使用的分词器的类型信息  
  81.         TypeAttribute ta = stream.addAttribute(TypeAttribute.class);  
  82.         try {  
  83.             if(displayAll){  
  84.                 //等价于while(stream.incrementToken())  
  85.                 for(; stream.incrementToken() ;){  
  86.                     System.out.println(ta.type() + " " + pia.getPositionIncrement() + " ["+oa.startOffset()+"-"+oa.endOffset()+"] ["+cta+"]");  
  87.                 }  
  88.             }else{  
  89.                 System.out.println();  
  90.                 while(stream.incrementToken()){  
  91.                     System.out.print("[" + cta + "]");  
  92.                 }  
  93.             }  
  94.         } catch (IOException e) {  
  95.             e.printStackTrace();  
  96.         }  
  97.     }  
  98. }  


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

[java]  view plain copy print ?
  1. package com.jadyer.analysis;  
  2.   
  3. import java.io.Reader;  
  4. import java.util.Set;  
  5.   
  6. import org.apache.lucene.analysis.Analyzer;  
  7. import org.apache.lucene.analysis.LetterTokenizer;  
  8. import org.apache.lucene.analysis.LowerCaseFilter;  
  9. import org.apache.lucene.analysis.StopAnalyzer;  
  10. import org.apache.lucene.analysis.StopFilter;  
  11. import org.apache.lucene.analysis.TokenStream;  
  12. import org.apache.lucene.util.Version;  
  13.   
  14. /** 
  15.  * 自定义的停用词分词器 
  16.  * @see 它主要用来过滤指定的字符串(忽略大小写) 
  17.  * @create Aug 5, 2013 1:55:15 PM 
  18.  * @author 玄玉<http://blog.csdn.net/jadyer> 
  19.  */  
  20. public class MyStopAnalyzer extends Analyzer {  
  21.     private Set<Object> stopWords; //存放停用的分词信息  
  22.       
  23.     /** 
  24.      * 自定义的用于过滤指定字符串的分词器 
  25.      * @param _stopWords 用于指定所要过滤的字符串(忽略大小写) 
  26.      */  
  27.     public MyStopAnalyzer(String[] _stopWords){  
  28.         //会自动将字符串数组转换为Set  
  29.         stopWords = StopFilter.makeStopSet(Version.LUCENE_36, _stopWords, true);  
  30.         //将原有的停用词加入到现在的停用词中  
  31.         stopWords.addAll(StopAnalyzer.ENGLISH_STOP_WORDS_SET);  
  32.     }  
  33.       
  34.     @Override  
  35.     public TokenStream tokenStream(String fieldName, Reader reader) {  
  36.         //为这个分词器设定过滤器链和Tokenizer  
  37.         return new StopFilter(Version.LUCENE_36,  
  38.                         //这里就可以存放很多的TokenFilter  
  39.                         new LowerCaseFilter(Version.LUCENE_36, new LetterTokenizer(Version.LUCENE_36, reader)),  
  40.                         stopWords);  
  41.     }  
  42. }  

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

[java]  view plain copy print ?
  1. package com.jadyer.analysis;  
  2.   
  3. import java.io.IOException;  
  4. import java.io.Reader;  
  5. import java.util.HashMap;  
  6. import java.util.Map;  
  7. import java.util.Stack;  
  8.   
  9. import org.apache.lucene.analysis.Analyzer;  
  10. import org.apache.lucene.analysis.TokenFilter;  
  11. import org.apache.lucene.analysis.TokenStream;  
  12. import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;  
  13. import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;  
  14. import org.apache.lucene.util.AttributeSource;  
  15.   
  16. import com.chenlb.mmseg4j.ComplexSeg;  
  17. import com.chenlb.mmseg4j.Dictionary;  
  18. import com.chenlb.mmseg4j.analysis.MMSegTokenizer;  
  19.   
  20. /** 
  21.  * 自定义的同义词分词器 
  22.  * @create Aug 5, 2013 5:11:46 PM 
  23.  * @author 玄玉<http://blog.csdn.net/jadyer> 
  24.  */  
  25. public class MySynonymAnalyzer extends Analyzer {  
  26.     @Override  
  27.     public TokenStream tokenStream(String fieldName, Reader reader) {  
  28.         //借助MMSeg4j实现自定义分词器,写法参考MMSegAnalyzer类的tokenStream()方法  
  29.         //但为了过滤并处理分词后的各个语汇单元,以达到同义词分词器的功能,故自定义一个TokenFilter  
  30.         //实际执行流程就是字符串的Reader首先进入MMSegTokenizer,由其进行分词,分词完毕后进入自定义的MySynonymTokenFilter  
  31.         //然后在MySynonymTokenFilter中添加同义词  
  32.         return new MySynonymTokenFilter(new MMSegTokenizer(new ComplexSeg(Dictionary.getInstance()), reader));  
  33.     }  
  34. }  
  35.   
  36.   
  37. /** 
  38.  * 自定义的TokenFilter 
  39.  * @create Aug 5, 2013 5:11:58 PM 
  40.  * @author 玄玉<http://blog.csdn.net/jadyer> 
  41.  */  
  42. class MySynonymTokenFilter extends TokenFilter {  
  43.     private CharTermAttribute cta;              //用于获取TokenStream中的语汇单元  
  44.     private PositionIncrementAttribute pia;     //用于获取TokenStream中的位置增量  
  45.     private AttributeSource.State tokenState;   //用于保存语汇单元的状态  
  46.     private Stack<String> synonymStack;         //用于保存同义词  
  47.       
  48.     protected MySynonymTokenFilter(TokenStream input) {  
  49.         super(input);  
  50.         this.cta = this.addAttribute(CharTermAttribute.class);  
  51.         this.pia = this.addAttribute(PositionIncrementAttribute.class);  
  52.         this.synonymStack = new Stack<String>();  
  53.     }  
  54.       
  55.     /** 
  56.      * 判断是否存在同义词 
  57.      */  
  58.     private boolean isHaveSynonym(String name){  
  59.         //先定义同义词的词典  
  60.         Map<String, String[]> synonymMap = new HashMap<String, String[]>();  
  61.         synonymMap.put("我"new String[]{"咱""俺"});  
  62.         synonymMap.put("中国"new String[]{"兲朝""大陆"});  
  63.         if(synonymMap.containsKey(name)){  
  64.             for(String str : synonymMap.get(name)){  
  65.                 this.synonymStack.push(str);  
  66.             }  
  67.             return true;  
  68.         }  
  69.         return false;  
  70.     }  
  71.   
  72.     @Override  
  73.     public boolean incrementToken() throws IOException {  
  74.         while(this.synonymStack.size() > 0){  
  75.             restoreState(this.tokenState); //将状态还原为上一个元素的状态  
  76.             cta.setEmpty();  
  77.             cta.append(this.synonymStack.pop()); //获取并追加同义词  
  78.             pia.setPositionIncrement(0);         //设置位置增量为0  
  79.             return true;  
  80.         }  
  81.         if(input.incrementToken()){  
  82.             //注意:当发现当前元素存在同义词之后,不能立即追加同义词,即不能在目标元素上直接处理  
  83.             if(this.isHaveSynonym(cta.toString())){  
  84.                 this.tokenState = captureState(); //存在同义词时,则捕获并保存当前状态  
  85.             }  
  86.             return true;  
  87.         }else {  
  88.             return false//只要TokenStream中没有元素,就返回false  
  89.         }  
  90.     }  
  91. }  

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

[java]  view plain copy print ?
  1. package com.jadyer.test;  
  2.   
  3. import org.apache.lucene.analysis.StopAnalyzer;  
  4. import org.apache.lucene.analysis.standard.StandardAnalyzer;  
  5. import org.apache.lucene.document.Document;  
  6. import org.apache.lucene.document.Field;  
  7. import org.apache.lucene.index.IndexReader;  
  8. import org.apache.lucene.index.IndexWriter;  
  9. import org.apache.lucene.index.IndexWriterConfig;  
  10. import org.apache.lucene.index.Term;  
  11. import org.apache.lucene.search.IndexSearcher;  
  12. import org.apache.lucene.search.ScoreDoc;  
  13. import org.apache.lucene.search.TermQuery;  
  14. import org.apache.lucene.search.TopDocs;  
  15. import org.apache.lucene.store.Directory;  
  16. import org.apache.lucene.store.RAMDirectory;  
  17. import org.apache.lucene.util.Version;  
  18. import org.junit.Test;  
  19.   
  20. import com.jadyer.analysis.MyStopAnalyzer;  
  21. import com.jadyer.analysis.MySynonymAnalyzer;  
  22. import com.jadyer.lucene.HelloCustomAnalyzer;  
  23.   
  24. public class HelloCustomAnalyzerTest {  
  25.     /** 
  26.      * 测试自定义的用于过滤指定字符串(忽略大小写)的停用词分词器 
  27.      */  
  28.     @Test  
  29.     public void stopAnalyzer(){  
  30.         String txt = "This is my house, I`m come from Haerbin,My email is jadyer@yeah.net, My QQ is 517751422";  
  31.         HelloCustomAnalyzer.displayTokenInfo(txt, new StandardAnalyzer(Version.LUCENE_36), false);  
  32.         HelloCustomAnalyzer.displayTokenInfo(txt, new StopAnalyzer(Version.LUCENE_36), false);  
  33.         HelloCustomAnalyzer.displayTokenInfo(txt, new MyStopAnalyzer(new String[]{"I""EMAIL""you"}), false);  
  34.     }  
  35.       
  36.   
  37.     /** 
  38.      * 测试自定义的同义词分词器 
  39.      */  
  40.     @Test  
  41.     public void synonymAnalyzer(){  
  42.         String txt = "我来自中国黑龙江省哈尔滨市巴彦县兴隆镇";  
  43.         IndexWriter writer = null;  
  44.         IndexSearcher searcher = null;  
  45.         Directory directory = new RAMDirectory();  
  46.         try {  
  47.             writer = new IndexWriter(directory, new IndexWriterConfig(Version.LUCENE_36, new MySynonymAnalyzer()));  
  48.             Document doc = new Document();  
  49.             doc.add(new Field("content", txt, Field.Store.YES, Field.Index.ANALYZED));  
  50.             writer.addDocument(doc);  
  51.             writer.close(); //搜索前要确保IndexWriter已关闭,否则会报告异常org.apache.lucene.index.IndexNotFoundException: no segments* file found  
  52.             searcher = new IndexSearcher(IndexReader.open(directory));  
  53.             TopDocs tds = searcher.search(new TermQuery(new Term("content""咱")), 10);  
  54.             for(ScoreDoc sd : tds.scoreDocs){  
  55.                 System.out.println(searcher.doc(sd.doc).get("content"));  
  56.             }  
  57.             searcher.close();  
  58.         } catch (Exception e) {  
  59.             e.printStackTrace();  
  60.         }  
  61.         HelloCustomAnalyzer.displayTokenInfo(txt, new MySynonymAnalyzer(), true);  
  62.     }  
  63. }  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值