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.packagecom.piaoxuexianjing.cn;
2.
3.importjava.io.IOException;
4.importjava.io.Reader;
5.
6.importorg.apache.lucene.analysis.Tokenizer;
7.importorg.apache.lucene.analysis.tokenattributes.CharTermAttribute;
8.importorg.apache.lucene.analysis.tokenattributes.OffsetAttribute;
9.importorg.apache.lucene.util.AttributeSource.AttributeFactory;
10.
11.publicclassChinaextendsTokenizer{
12.
13.publicChina(Readerin){
14.super(in);
15.}
16.
17.publicChina(AttributeFactoryfactory,Readerin){
18.super(factory,in);
19.}
20.
21.privateintoffset=0,bufferIndex=0,dataLen=0;
22.privatefinalstaticintMAX_WORD_LEN=255;
23.privatefinalstaticintIO_BUFFER_SIZE=1024;
24.privatefinalchar[]buffer=newchar[MAX_WORD_LEN];
25.privatefinalchar[]ioBuffer=newchar[IO_BUFFER_SIZE];
26.
27.
28.privateintlength;
29.privateintstart;
30.
31.privatefinalCharTermAttributetermAtt=addAttribute(CharTermAttribute.class);
32.privatefinalOffsetAttributeoffsetAtt=addAttribute(OffsetAttribute.class);
33.
34.privatefinalvoidpush(charc){
35.
36.if(length==0)start=offset-1;//startoftoken
37.buffer[length++]=Character.toLowerCase(c);//bufferit
38.
39.}
40.
41.privatefinalbooleanflush(){
42.
43.if(length>0){
44.//System.out.println(newString(buffer,0,
45.//length));
46.termAtt.copyBuffer(buffer,0,length);
47.offsetAtt.setOffset(correctOffset(start),correctOffset(start+length));
48.returntrue;
49.}
50.else
51.returnfalse;
52.}
53.
54.@Override
55.publicbooleanincrementToken()throwsIOException{
56.clearAttributes();
57.
58.length=0;
59.start=offset;
60.
61.
62.while(true){
63.
64.finalcharc;
65.offset++;
66.
67.if(bufferIndex>=dataLen){
68.dataLen=input.read(ioBuffer);
69.bufferIndex=0;
70.}
71.
72.if(dataLen==-1){
73.offset--;
74.returnflush();
75.}else
76.c=ioBuffer[bufferIndex++];
77.
78.
79.switch(Character.getType(c)){
80.
81.caseCharacter.DECIMAL_DIGIT_NUMBER://注意此部分不过滤一些熟悉或者字母
82.caseCharacter.LOWERCASE_LETTER://注意此部分
83.caseCharacter.UPPERCASE_LETTER://注意此部分
84.//push(c);
85.//if(length==MAX_WORD_LEN)returnflush();
86.//break;
87.
88.caseCharacter.OTHER_LETTER:
89.if(length>0){
90.bufferIndex--;
91.offset--;
92.returnflush();
93.}
94.push(c);
95.returnflush();
96.
97.default:
98.if(length>0)returnflush();
99.
100.break;
101.
102.}
103.}
104.}
105.
106.@Override
107.publicfinalvoidend(){
108.//setfinaloffset
109.finalintfinalOffset=correctOffset(offset);
110.this.offsetAtt.setOffset(finalOffset,finalOffset);
111.}
112.
113.@Override
114.publicvoidreset()throwsIOException{
115.super.reset();
116.offset=bufferIndex=dataLen=0;
117.}
118.
119.}
然后定义个自己的分词器
Java代码复制代码收藏代码
1.packagecom.piaoxuexianjing.cn;
2.
3.importjava.io.Reader;
4.
5.importorg.apache.lucene.analysis.Analyzer;
6.importorg.apache.lucene.analysis.Tokenizer;
7.
8./**
9.*@author三劫散仙
10.*单字切分
11.*
12.***/
13.publicclassMyChineseAnalyzerextendsAnalyzer{
14.
15.@Override
16.protectedTokenStreamComponentscreateComponents(Stringarg0,Readerarg1){
17.
18.Tokenizertoken=newChina(arg1);
19.
20.returnnewTokenStreamComponents(token);
21.}
22.
23.
24.
25.
26.
27.}
下面我们来看单字切词效果,对于字符串
Stringtext="天气不错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.packagecom.splitanalyzer;
2.
3.importjava.io.Reader;
4.
5.importorg.apache.lucene.analysis.util.CharTokenizer;
6.importorg.apache.lucene.util.Version;
7.
8./***
9.*
10.*@author三劫散仙
11.*拆分charTokenizer
12.*
13.**/
14.publicclassSpiltTokenizerextendsCharTokenizer{
15.
16.charc;
17.publicSpiltTokenizer(VersionmatchVersion,Readerinput,charc){
18.super(matchVersion,input);
19.//TODOAuto-generatedconstructorstub
20.this.c=c;
21.}
22.
23.@Override
24.protectedbooleanisTokenChar(intarg0){
25.returnarg0==c?false:true;
26.}
27.
28.
29.
30.
31.}
然后在定义自己的分词器
Java代码复制代码收藏代码
1.packagecom.splitanalyzer;
2.
3.importjava.io.Reader;
4.
5.importorg.apache.lucene.analysis.Analyzer;
6.importorg.apache.lucene.util.Version;
7.
8./**
9.*@author三劫散仙
10.*自定义单个char字符分词器
11.***/
12.publicclassSplitAnalyzerextendsAnalyzer{
13.charc;//按特定符号进行拆分
14.
15.publicSplitAnalyzer(charc){
16.this.c=c;
17.}
18.
19.@Override
20.protectedTokenStreamComponentscreateComponents(Stringarg0,Readerarg1){
21.//TODOAuto-generatedmethodstub
22.returnnewTokenStreamComponents(newSpiltTokenizer(Version.LUCENE_43,arg1,c));
23.}
24.
25.
26.}
下面看一些测试效果
Java代码复制代码收藏代码
1.packagecom.splitanalyzer;
2.
3.importjava.io.StringReader;
4.
5.importorg.apache.lucene.analysis.TokenStream;
6.importorg.apache.lucene.analysis.tokenattributes.CharTermAttribute;
7.
8./**
9.*测试的demo
10.*
11.***/
12.publicclassTest{
13.
14.publicstaticvoidmain(String[]args)throwsException{
15.SplitAnalyzeranalyzer=newSplitAnalyzer('#');
16.//SplitAnalyzeranalyzer=newSplitAnalyzer('+');
17.//PatternAnalyzeranalyzer=newPatternAnalyzer("abc");
18.TokenStreamts=analyzer.tokenStream("field",newStringReader("我#你#他"));
19.//TokenStreamts=analyzer.tokenStream("field",newStringReader("我+你+他"));
20.CharTermAttributeterm=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.packagecom.splitanalyzer;
2.
3.importjava.io.Reader;
4.importjava.util.regex.Pattern;
5.
6.importorg.apache.lucene.analysis.Analyzer;
7.importorg.apache.lucene.analysis.pattern.PatternTokenizer;
8.
9./**
10.*@author三劫散仙
11.*自定义分词器
12.*针对单字切
13.*单个符号切分
14.*多个符号组合切分
15.*
16.***/
17.publicclassPatternAnalyzerextendsAnalyzer{
18.
19.Stringregex;//使用的正则拆分式
20.publicPatternAnalyzer(Stringregex){
21.this.regex=regex;
22.}
23.
24.@Override
25.protectedTokenStreamComponentscreateComponents(Stringarg0,Readerarg1){
26.returnnewTokenStreamComponents(newPatternTokenizer(arg1,Pattern.compile(regex),-1));
27.}
28.
29.
30.
31.}
我们来看下运行效果:
Java代码复制代码收藏代码
1.packagecom.splitanalyzer;
2.
3.importjava.io.StringReader;
4.
5.importorg.apache.lucene.analysis.TokenStream;
6.importorg.apache.lucene.analysis.tokenattributes.CharTermAttribute;
7.
8./**
9.*测试的demo
10.*
11.***/
12.publicclassTest{
13.
14.publicstaticvoidmain(String[]args)throwsException{
15.//SplitAnalyzeranalyzer=newSplitAnalyzer('#');
16.PatternAnalyzeranalyzer=newPatternAnalyzer("");
17.//空字符串代表单字切分
18.TokenStreamts=analyzer.tokenStream("field",newStringReader("我#你#他"));
19.CharTermAttributeterm=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.PatternAnalyzeranalyzer=newPatternAnalyzer("#");
输出效果:
Java代码复制代码收藏代码
1.我
2.你
3.他
传入任意长度的字符串参数
Java代码复制代码收藏代码
1.PatternAnalyzeranalyzer=newPatternAnalyzer("分割");
2.okenStreamts=analyzer.tokenStream("field",newStringReader("我分割你分割他分割"));
输出效果:
Java代码复制代码收藏代码
1.我
2.你
3.他