Lucene的内置的分词器

本来的Lucene的内置的分词器,差不多可以完成我们的大部分分词工作了,如果是英文文章那么可以使用StandardAnalyzer标准分词器,WhitespaceAnalyzer空格分词器,对于中文我们则可以选择IK分词器,Messeg4j,庖丁等分词器。

我们先来看看下面的几个需求


编号 需求分析
1 按单个字符进行分词无论是数字,字母还是特殊符号
2 按特定的字符进行分词,类似String中spilt()方法
3 按照某个字符或字符串进行分词

仔细分析下上面的需求,会觉得上面的需求很没意思,但是在特定的场合下确实是存在这样的需求的,看起来上面的需求很简单,但是lucene里面内置的分析器却没有一个支持这种变态的"无聊的"分词需求,如果想要满足上面的需求,可能就需要我们自己定制自己的分词器了。


先来看第一个需求,单个字符切分,这就要不管你是the一个单词还是一个电话号码还是一段话还是其他各种特殊符号都要保留下来,进行单字切分,这种特细粒度的分词,有两种需求情况,可能适应这两种场景
(-)100%的实现数据库模糊匹配
(=)对于某个电商网站笔记本的型号Y490,要求用户无论输入Y还是4,9,0都可以找到这款笔记本


这种单字切分确实可以实现数据库的百分百模糊检索,但是同时也带来了一些问题,如果这个域中是存电话号码,或者身份证之类的与数字的相关的信息,那么这种分词法,会造成这个域的倒排链表非常之长,反映到搜索上,就会出现中文检索很快,而数字的检索确实非常之慢的问题。原因是因为数字只有0-9个字符,而汉字则远远比这个数量要大的多,所以在选用这种分词时,还是要慎重的考虑下自己的业务场景到底适不适合这种分词,否则就会可能出一些问题。

再来分析下2和3的需求,这种需求可能存在这么一种情况,就是某个字段里存的内容是按照逗号或者空格,#号,或者是自己定义的一个字符串进行分割存储的,而这种时候我们可能就会想到一些非常简单的做法,直接调用String类的spilt方法进行打散,确实,这种方式是可行的,但是lucene里面的结构某些情况下,就可能不适合用字符串拆分的方法,而是要求我们必须定义一个自己的分词器来完成这种功能,因为涉及到一些参数需要传一个分词器或者索引和检索时都要使用分词器来构造解析,所以有时候就必须得自己定义个专门处理这种情况的分词器了。

好了,散仙不在唠叨了,下面开始给出代码,首先针对第一个需求,单字切分,其实这个需求没什么难的,只要熟悉lucene的Tokenizer就可以轻松解决,我们改写ChineseTokenizer来满足我们的需求.



Java代码 复制代码 收藏代码
1.package com.piaoxuexianjing.cn; 
2. 
3.import java.io.IOException; 
4.import java.io.Reader; 
5. 
6.import org.apache.lucene.analysis.Tokenizer; 
7.import org.apache.lucene.analysis.tokenattributes.CharTermAttribute; 
8.import org.apache.lucene.analysis.tokenattributes.OffsetAttribute; 
9.import org.apache.lucene.util.AttributeSource.AttributeFactory; 
10. 
11.public class China extends Tokenizer { 
12.     
13.     public China(Reader in) { 
14.          super(in); 
15.        } 
16. 
17.        public China(AttributeFactory factory, Reader in) { 
18.          super(factory, in); 
19.        } 
20.            
21.        private int offset = 0, bufferIndex=0, dataLen=0; 
22.        private final static int MAX_WORD_LEN = 255; 
23.        private final static int IO_BUFFER_SIZE = 1024; 
24.        private final char[] buffer = new char[MAX_WORD_LEN]; 
25.        private final char[] ioBuffer = new char[IO_BUFFER_SIZE]; 
26. 
27. 
28.        private int length; 
29.        private int start; 
30. 
31.        private final CharTermAttribute termAtt = addAttribute(CharTermAttribute.class); 
32.        private final OffsetAttribute offsetAtt = addAttribute(OffsetAttribute.class); 
33.         
34.        private final void push(char c) { 
35. 
36.            if (length == 0) start = offset-1;            // start of token 
37.            buffer[length++] = Character.toLowerCase(c);  // buffer it 
38. 
39.        } 
40. 
41.        private final boolean flush() { 
42. 
43.            if (length>0) { 
44.                //System.out.println(new String(buffer, 0, 
45.                //length)); 
46.              termAtt.copyBuffer(buffer, 0, length); 
47.              offsetAtt.setOffset(correctOffset(start), correctOffset(start+length)); 
48.              return true; 
49.            } 
50.            else 
51.                return false; 
52.        } 
53. 
54.        @Override 
55.        public boolean incrementToken() throws IOException { 
56.            clearAttributes(); 
57. 
58.            length = 0; 
59.            start = offset; 
60. 
61. 
62.            while (true) { 
63. 
64.                final char c; 
65.                offset++; 
66. 
67.                if (bufferIndex >= dataLen) { 
68.                    dataLen = input.read(ioBuffer); 
69.                    bufferIndex = 0; 
70.                } 
71. 
72.                if (dataLen == -1) { 
73.                  offset--; 
74.                  return flush(); 
75.                } else 
76.                    c = ioBuffer[bufferIndex++]; 
77. 
78. 
79.                switch(Character.getType(c)) { 
80. 
81.                case Character.DECIMAL_DIGIT_NUMBER://注意此部分不过滤一些熟悉或者字母 
82.                case Character.LOWERCASE_LETTER://注意此部分 
83.                case Character.UPPERCASE_LETTER://注意此部分 
84.//                  push(c); 
85.//                  if (length == MAX_WORD_LEN) return flush(); 
86.//                  break; 
87.              
88.                case Character.OTHER_LETTER: 
89.                    if (length>0) { 
90.                        bufferIndex--; 
91.                        offset--; 
92.                        return flush(); 
93.                    } 
94.                    push(c); 
95.                    return flush(); 
96. 
97.                default: 
98.                    if (length>0) return flush(); 
99.                      
100.                        break; 
101.                     
102.                } 
103.            } 
104.        } 
105.         
106.        @Override 
107.        public final void end() { 
108.          // set final offset 
109.          final int finalOffset = correctOffset(offset); 
110.          this.offsetAtt.setOffset(finalOffset, finalOffset); 
111.        } 
112. 
113.        @Override 
114.        public void reset() throws IOException { 
115.          super.reset(); 
116.          offset = bufferIndex = dataLen = 0; 
117.        } 
118. 
119.} 



然后定义个自己的分词器



Java代码 复制代码 收藏代码
1.package com.piaoxuexianjing.cn; 
2. 
3.import java.io.Reader; 
4. 
5.import org.apache.lucene.analysis.Analyzer; 
6.import org.apache.lucene.analysis.Tokenizer; 
7. 
8./**
9. * @author 三劫散仙
10. * 单字切分
11. * 
12. * **/ 
13.public class MyChineseAnalyzer extends Analyzer { 
14. 
15.    @Override 
16.    protected TokenStreamComponents createComponents(String arg0, Reader arg1) { 
17.        
18.        Tokenizer token=new China(arg1); 
19.         
20.        return new TokenStreamComponents(token); 
21.    } 
22.     
23.     
24.     
25.     
26. 
27.} 


下面我们来看单字切词效果,对于字符串
String text="天气不错132abc@#$+-)(*&^.,/";



Java代码 复制代码 收藏代码
1.天 
2.气 
3.不 
4.错 
5.1 
6.3 
7.2 
8.a 
9.b 
10.c 
11.@ 
12.# 
13.$ 
14.+ 
15.- 
16.) 
17.( 
18.* 
19.& 
20.^ 
21.. 
22., 
23./ 



对于第二种需求我们要模仿空格分词器的的原理,代码如下



Java代码 复制代码 收藏代码
1.package com.splitanalyzer; 
2. 
3.import java.io.Reader; 
4. 
5.import org.apache.lucene.analysis.util.CharTokenizer; 
6.import org.apache.lucene.util.Version; 
7. 
8./***
9. *
10. *@author 三劫散仙
11. *拆分char Tokenizer
12. * 
13. * */ 
14.public class SpiltTokenizer extends CharTokenizer { 
15.  
16.       char c; 
17.    public SpiltTokenizer(Version matchVersion, Reader input,char c) { 
18.        super(matchVersion, input); 
19.        // TODO Auto-generated constructor stub 
20.        this.c=c; 
21.    } 
22. 
23.    @Override 
24.    protected boolean isTokenChar(int arg0) { 
25.        return arg0==c?false:true ; 
26.    } 
27.     
28.     
29.     
30. 
31.} 


然后在定义自己的分词器



Java代码 复制代码 收藏代码
1.package com.splitanalyzer; 
2. 
3.import java.io.Reader; 
4. 
5.import org.apache.lucene.analysis.Analyzer; 
6.import org.apache.lucene.util.Version; 
7. 
8./**
9. * @author 三劫散仙
10. * 自定义单个char字符分词器
11. * **/ 
12.public class SplitAnalyzer extends Analyzer{ 
13.    char c;//按特定符号进行拆分 
14.     
15.    public SplitAnalyzer(char c) { 
16.        this.c=c; 
17.    } 
18. 
19.    @Override 
20.    protected TokenStreamComponents createComponents(String arg0, Reader arg1) { 
21.        // TODO Auto-generated method stub 
22.        return  new TokenStreamComponents(new SpiltTokenizer(Version.LUCENE_43, arg1,c)); 
23.    } 
24.     
25. 
26.} 


下面看一些测试效果



Java代码 复制代码 收藏代码
1.package com.splitanalyzer; 
2. 
3.import java.io.StringReader; 
4. 
5.import org.apache.lucene.analysis.TokenStream; 
6.import org.apache.lucene.analysis.tokenattributes.CharTermAttribute; 
7. 
8./**
9. * 测试的demo
10. * 
11. * **/ 
12.public class Test { 
13.     
14.    public static void main(String[] args)throws Exception { 
15.         SplitAnalyzer analyzer=new SplitAnalyzer('#'); 
16.             //SplitAnalyzer analyzer=new SplitAnalyzer('+'); 
17.        //PatternAnalyzer analyzer=new PatternAnalyzer("abc"); 
18.        TokenStream ts= analyzer.tokenStream("field", new StringReader("我#你#他")); 
19.       // TokenStream ts=   analyzer.tokenStream("field", new StringReader("我+你+他")); 
20.        CharTermAttribute term=ts.addAttribute(CharTermAttribute.class); 
21.        ts.reset(); 
22.        while(ts.incrementToken()){ 
23.            System.out.println(term.toString()); 
24.        } 
25.        ts.end(); 
26.        ts.close(); 
27.          
28.    } 
29. 
30.} 
31.我 
32.你 
33.他 



到这里,可能一些朋友已经看不下去了,代码太多太臃肿了,有没有一种通用的办法,解决此类问题,散仙的回答是肯定的,如果某些朋友,连看到这部分的耐心都没有的话,那么,不好意思,你只能看到比较低级的解决办法了,当然能看到这部分的道友们,散仙带着大家来看一下比较通用解决办法,这个原理其实是基于正则表达式的,所以由此看来,正则表达式在处理文本字符串上面有其独特的优势。下面我们要做的就是改写自己的正则解析器,代码非常精简,功能却是很强大的,上面的3个需求都可以解决,只需要传入不用的参数即可。





Java代码 复制代码 收藏代码
1.package com.splitanalyzer; 
2. 
3.import java.io.Reader; 
4.import java.util.regex.Pattern; 
5. 
6.import org.apache.lucene.analysis.Analyzer; 
7.import org.apache.lucene.analysis.pattern.PatternTokenizer; 
8. 
9./**
10. * @author 三劫散仙
11. * 自定义分词器
12. * 针对单字切
13. * 单个符号切分
14. * 多个符号组合切分
15. * 
16. * **/ 
17.public class PatternAnalyzer  extends Analyzer { 
18.     
19.    String regex;//使用的正则拆分式 
20.    public PatternAnalyzer(String regex) { 
21.         this.regex=regex; 
22.    } 
23. 
24.    @Override 
25.    protected TokenStreamComponents createComponents(String arg0, Reader arg1) { 
26.        return new TokenStreamComponents(new PatternTokenizer(arg1, Pattern.compile(regex),-1)); 
27.    } 
28.     
29.     
30.     
31.} 



我们来看下运行效果:



Java代码 复制代码 收藏代码
1.package com.splitanalyzer; 
2. 
3.import java.io.StringReader; 
4. 
5.import org.apache.lucene.analysis.TokenStream; 
6.import org.apache.lucene.analysis.tokenattributes.CharTermAttribute; 
7. 
8./**
9. * 测试的demo
10. * 
11. * **/ 
12.public class Test { 
13.     
14.    public static void main(String[] args)throws Exception { 
15.       //  SplitAnalyzer analyzer=new SplitAnalyzer('#'); 
16.         PatternAnalyzer analyzer=new PatternAnalyzer(""); 
17.         //空字符串代表单字切分   
18.        TokenStream ts= analyzer.tokenStream("field", new StringReader("我#你#他")); 
19.        CharTermAttribute term=ts.addAttribute(CharTermAttribute.class); 
20.        ts.reset(); 
21.        while(ts.incrementToken()){ 
22.            System.out.println(term.toString()); 
23.        } 
24.        ts.end(); 
25.        ts.close(); 
26.          
27.    } 
28. 
29.} 


输出效果:



Java代码 复制代码 收藏代码
1.我 
2.# 
3.你 
4.# 
5.他 


传入#号参数



Java代码 复制代码 收藏代码
1.PatternAnalyzer analyzer=new PatternAnalyzer("#"); 


输出效果:



Java代码 复制代码 收藏代码
1.我 
2.你 
3.他 


传入任意长度的字符串参数



Java代码 复制代码 收藏代码
1.PatternAnalyzer analyzer=new PatternAnalyzer("分割"); 
2.okenStream ts=  analyzer.tokenStream("field", new StringReader("我分割你分割他分割")); 


输出效果:



Java代码 复制代码 收藏代码
1.我 
2.你 
3.他 

猜你喜欢

转载自weitao1026.iteye.com/blog/2265611
今日推荐