Solr分词器

首先咱们来看看我当初使用Lucene5是如何实现的,
http://dl2.iteye.com/upload/attachment/0109/7925/0051fb7f-0b63-3371-9967-7e0e5169e102.png
 
Solr5中,咱们只须要为IKTokenizer扩展一个IKTokenizerFactory,PinyinTokenFilter扩展一个PinyinTokenFilterFactory,PinyinNGramTokenFilter扩展一个PinyinNGramTokenFilterFactory,其中IKTokenizerFactory我已经扩展过了,剩下须要作的就是自定义PinyinTokenFilterFactoryPinyinNGramTokenFilterFactory了。若是你不知道如何扩展,请参看SolrStopFilterFactory类源码,照葫芦画瓢。OK,我来全程截图示范,我是如何扩展的?
java

     既然是要扩展PinyinTokenFilterFactory,从类名就知道它是PinyinTokenFilter的工厂类,因此咱们首先须要把我以前写的PinyinTokenFilterPinyinNGramTokenFiltercopy到一个新的项目中来,如图:
http://dl2.iteye.com/upload/attachment/0109/7931/abec5110-887e-3e05-bc48-ab875e839144.png
 
我新建一个solr-analyzer-extra Java Project,把我以前写的几个类copy到如图红色框住的package中,那几个类你在我Lucene5系列博客中均可以找到源码,或者你到个人GitHub上也能够获得相关源码。个人GitHub地址待会儿我会在博客的结尾处贴出来,敬请关注哦!图片中显示还有ikansj两个package,这就是我为了前面几篇博客扩展的TokenizerFactory,你懂的!而后咱们须要添加依赖的Jar包,如图:
http://dl2.iteye.com/upload/attachment/0109/7933/f4a74d1b-2718-3e6d-9c56-13f6dfd229ac.png
 
之因此分Lucene5Solr5两个包,就是为了方便打包Jar包,这样我就能够把lucene5包下的类单独打包成一个jarsolr5下打包成一个jar,当你仅仅只是在Lucene5下须要使用拼音分词,那solr5包下的类是用不到的,打包成两个jar是为了按需加载类,你懂的!特此说明。
node

     OK,开始在Solr5包下扩展PinyinTokenFilterFactory,我扩展的源码如图:web

http://dl2.iteye.com/upload/attachment/0109/7960/c41df5ae-10b9-35bc-8ed1-2d6f83521423.png
 扩展的PinyinNGramTokenFilterFactory源码如图:
http://dl2.iteye.com/upload/attachment/0109/7950/4f64a1b8-42be-3212-bd4e-7fe803882cd8.png
 
对应的PinyinNGramTokenFilter类我稍做了修改,主要是添加了nGramNumber参数,用于控制是否对纯数字进行nGram处理,有时候可能并不但愿对相似 2011 这样的数字进行nGram,固然若是你须要对纯数字字符串进行nGram处理,请把nGramNumber参数设置为true便可,默认该值为falsePinyinNGramTokenFilter类我修改的地方以下:
http://dl2.iteye.com/upload/attachment/0109/7952/89946743-0b45-3515-bbba-6dca06ebf378.png
 
http://dl2.iteye.com/upload/attachment/0109/7954/c5285d29-c634-31f4-bd6a-209b9fb4d678.png
 
http://dl2.iteye.com/upload/attachment/0109/7956/665940ad-a8de-3681-98b9-7002571ad668.png
 
http://dl2.iteye.com/upload/attachment/0109/7958/9583c8fc-03ae-3ffb-abdd-b78a831f2daa.png
apache

其中定义了一个常量类Constant,就是不想把默认值常量写死在各个类里,因此统一放到了一个常量类里,如图:
http://dl2.iteye.com/upload/attachment/0109/7962/60aa70f5-5817-360a-b45c-60cc24cad364.png
tomcat

上面涉及到的全部源码我待会儿都会在底下附件里上传分享给大家。OK,到此该扩展的类都编写完毕了,咱们须要将他们打包成jar,如图:
http://dl2.iteye.com/upload/attachment/0109/7964/e2b81999-581e-36a3-a752-2626dc010df3.png
 
http://dl2.iteye.com/upload/attachment/0109/7966/6fcdf04e-1aa7-3480-b7f0-7dbea1e075b8.png
 
http://dl2.iteye.com/upload/attachment/0109/7968/313dc1f3-38e1-35f0-af17-b8b270fd0a7d.png

http://dl2.iteye.com/upload/attachment/0109/7970/af065013-e50e-3960-8cb2-9aac56a7e5e3.png
 
http://dl2.iteye.com/upload/attachment/0109/7972/3fa5ad4a-785f-3202-87e4-b0c2df53d1fc.png
 
http://dl2.iteye.com/upload/attachment/0109/7974/17c276bc-8bf9-31ec-8710-44dcb99ac8bb.png
 
http://dl2.iteye.com/upload/attachment/0109/7976/f8a4bf42-0bb6-35fe-b595-b93697f56c49.png
 
http://dl2.iteye.com/upload/attachment/0109/7978/f1fa026d-8da4-3b11-9fed-15a19793042a.png
 
而后你就会在你的桌面上看到这个jar包,
http://dl2.iteye.com/upload/attachment/0109/7980/08938f80-cecf-37b6-8de5-e940d2e61178.png
 OK,
同理,对solr5包下的类进行打包,提供给使用Solr5的用户使用,如图:
http://dl2.iteye.com/upload/attachment/0109/7982/6aff06d7-d4de-3108-aa37-effac44afb00.png
 
http://dl2.iteye.com/upload/attachment/0109/7984/9f4bb301-9734-3496-80e4-1547f1f43296.png
 
http://dl2.iteye.com/upload/attachment/0109/7986/1a5cef32-c435-3b65-a2d5-ba6b80226681.png
 
http://dl2.iteye.com/upload/attachment/0109/7988/289edc1a-235f-34c5-a23d-bad55e2273cc.png
 
而后两个jar包就都打好了,如图:
http://dl2.iteye.com/upload/attachment/0109/7990/4262bb3b-a12f-34f1-9da4-a626f8c54f2a.png
 
接下来,咱们就须要把咱们打好的jar包导入到咱们的corelib目录下,如图:
http://dl2.iteye.com/upload/attachment/0109/7992/bb946fa9-f785-3c55-94f0-aa3610392389.png
 
因为咱们的汉字转拼音使用到了pinyin4j类库,因此咱们还须要把pinyin4jjar包也复制到当前corelib目录下,如图:
http://dl2.iteye.com/upload/attachment/0109/7994/44e65e48-85e5-34e8-b5fc-679464eacd74.png
 
因为咱们是先进行中文分词,而后再对分出来的中文词语进行拼音转换,而这里我以IK分词器为例,因此咱们还须要把IKjar包也copy进去,如图:
http://dl2.iteye.com/upload/attachment/0109/7998/afd24bdf-940c-3eb9-9eb2-42a6f2536c53.png
OK,jar
包导入完毕后,咱们须要在咱们的schema.xml中定义域类型,配置示例如图:
http://dl2.iteye.com/upload/attachment/0109/8000/829db201-8772-3c92-b111-c5ddb80fd93d.png
 
http://dl2.iteye.com/upload/attachment/0109/8002/9e669c22-5ff7-3485-9d25-856d452791fb.png
 
这是默认最基本的配置,固然PinyinTokenFilterFactoryPinyinNGramTokenFilterFactory这两个工厂类是有可选的配置参数能够设置的,请看图:
http://dl2.iteye.com/upload/attachment/0109/8004/78bcd96f-0773-3d8e-8f4d-1229e6c21373.png
 
http://dl2.iteye.com/upload/attachment/0109/8006/90e6dc94-90e4-3526-97eb-4b0dfae9b370.png
 
所以,你也能够这样配置:
http://dl2.iteye.com/upload/attachment/0109/8008/b9fb0caf-6333-3d4e-a9eb-ca16af93d842.png
 
域类型定义好后,你须要在你的某个域上应用这个新定义的text_pinyin域类型,如图:
http://dl2.iteye.com/upload/attachment/0109/8010/bf7005a0-5e7c-333a-8d92-ddb60a96ae1e.png
 OK,
启动你的tomcat,开始进行拼音分词测试,如图:
http://dl2.iteye.com/upload/attachment/0109/8012/e5170602-e888-3ed5-8565-626f6e108d7e.png
 
http://dl2.iteye.com/upload/attachment/0109/8014/14526a25-f058-38ae-9651-0cfe0bc4e343.png
 OK
,到此关于Solr5中关于拼音分词以及拼音搜索就讲解到这儿了
网络

要想在Sor中使用MMSeg4J分词器,首先你须要自定义一个TokenizerFactory实现类,虽然直接配置Analyzer类也能够,但那样没法配置Analyzer构造函数的参数,不够灵活,存在弊端,因此我一直都是以扩展TokenizerFactory的方式来说解相似MMSeg4J这样的中文分词器在Solr中的使用。app

      MMSegTokenizerFactory类我花了3个多小时修改了源码并通过N多测试,表示已经可使用,我主要的是针对Lucene5 APIMMSegTokenizer类作了升级更新并添加了自定义停用词功能,默认MMSeg4J没有实现自定义停用词功能。相关jar包请到底下的附件里去下载。下面介绍MMSeg4Jsolr5中的使用步骤:eclipse

     1. copy依赖的jar包到当前core\lib目录下,如图:webapp

http://dl2.iteye.com/upload/attachment/0109/6634/2d0c99fb-0757-3102-ada4-a997ec4db5e6.png
     2.在你的schema.xml中配置fieldType应用上我扩展的MMSegTokenizerFactory类,具体配置看图:
http://dl2.iteye.com/upload/attachment/0109/6636/86973c2b-40b8-3d65-b6dc-b4012fe9e3a3.png
 
ide

Xml代码  

  1. <fieldType name="text_mm" class="solr.TextField">  
  2.         <analyzer type="index">  
  3.             <tokenizer class="com.chenlb.mmseg4j.solr.MMSegTokenizerFactory" mode="simple"   
  4.                 stopwordsPath="mmseg-stopwords/stopwords.dic"/>  
  5.         </analyzer>  
  6.         <analyzer type="query">  
  7.             <tokenizer class="com.chenlb.mmseg4j.solr.MMSegTokenizerFactory" mode="complex"/>  
  8.         </analyzer>  
  9. </fieldType>  

    其中mode参数表示MMSeg4J的分词模式,自带有3种可选值:simple,complex,maxword, mode参数不配置默认为maxword模式stopwordsPath是用来配置自定义停用词加载路径的,默认是相对于classPath的,自定义停用词字典文件放置路径请看图:
http://dl2.iteye.com/upload/attachment/0109/6638/946fdefe-e2e2-3ccc-ad86-ccf3bbb738d5.png
 
自定义停用词词典文件加载路径配置参数是可选的,不过因为MMSeg4J没有内置停用词功能,因此像空格字符,标点符号等等都会被分出来,因此通常建议添加停用词词典文件。不过要注意的是,自定义的停用词词典文件的编码必须是UTF-8BOM格式,并且在你使用文本编辑软件打开进行编辑的时候,请务必将你的编辑软件的编码设置为UTF-8,不然可能会出现原本是UTF-8BOM编码,你打开编辑保存后编码就改变了。当你发现明明停用词在词典文件里,却很奇怪不起做用时,那十有八九是由于词典文件编码已经被破坏,建议词典文件不要本身新建,能够保留一个dic模版文件,每次直接copy过来修改文件名而后再打开编辑。

 

     3.而后你须要在你的某个field域上应用刚才定义的FieldType(域类型),如图:
http://dl2.iteye.com/upload/attachment/0109/6640/35bc9c40-4dff-32c7-b024-e4709656ea39.png
        OK
,如今你能够启动你的Tomcat进行分词测试了,如图:
http://dl2.iteye.com/upload/attachment/0109/6644/49626cf8-7c06-35e0-9597-81a8ad1f0abc.png
 mmseg-stopwrods
目录下的stopwords.dic停用词词典文件我添加了以下停用词:
http://dl2.iteye.com/upload/attachment/0109/6646/bc270f40-81c9-33f5-8e85-8ccf11dea1af.png
 
3个是一个空格字符,第4个是中文状态下的逗号字符,第5个是中文状态下的句号字符。你想要剔除哪些字符,具体留给大家本身去完善。

     若是我想配置自定义新词呢,好比么么哒,萌萌哒之类的,默认确定是分不出来的,该如何配置呢?MMSeg4J默认是内置了自定义词典扩展功能的,且默认加载思路以下:

       从默认目录加载词库文件, 查找默认目录顺序:

       1.首先从系统属性mmseg.dic.path指定的目录中加载

       2.若从系统属性mmseg.dic.path指定的目录中加载不到,再从classpath/data目录加载

       3.若从classpath/data目录加载不到,再从user.dir/data目录加载

 

须要注意的是,MMSeg4J对于字典dic文件的命名有要求,只有以words开头 .dic结尾的文件才会被加载

知道上述加载原理,那咱们只须要把自定义扩展词典文件如图放置便可:
http://dl2.iteye.com/upload/attachment/0109/6648/afe37842-1f11-3b22-a18b-2785a88f7a21.png
 
http://dl2.iteye.com/upload/attachment/0109/6650/c2793f60-9eff-3834-a63e-9ac37db6d226.png
 
http://dl2.iteye.com/upload/attachment/0109/6652/70d730d6-ddf9-3ea8-be69-3f4973cf47a0.png
       
到此,MMSeg4J分词器在Solr5中的使用就讲解完毕了

Solr中该如何使用IK分词器呢,这是小伙伴们问的频率比较高的一个问题,今晚特此更新此篇博客。其实以前我在其余博客里已经使用了IK分词器,只是我没作详细说明。

       schema.xml配置中其实有不少关于分词器的配置示例,我从中摘录一段配置示例,好比:

Xml代码  

  1. <fieldType name="text_general" class="solr.TextField" positionIncrementGap="100">  
  2.       <analyzer type="index">  
  3.         <tokenizer class="solr.StandardTokenizerFactory"/>  
  4.         <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" />  
  5.         <!-- in this example, we will only use synonyms at query time  
  6.         <filter class="solr.SynonymFilterFactory" synonyms="index_synonyms.txt" ignoreCase="true" expand="false"/>  
  7.         -->  
  8.         <filter class="solr.LowerCaseFilterFactory"/>  
  9.       </analyzer>  
  10.       <analyzer type="query">  
  11.         <tokenizer class="solr.StandardTokenizerFactory"/>  
  12.         <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" />  
  13.         <filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>  
  14.         <filter class="solr.LowerCaseFilterFactory"/>  
  15.       </analyzer>  
  16.     </fieldType>  

    fileType是用来定义域类型的,name即表示域名称,class即表示域类型对应的class类,若是是solr内置的域类型则能够直接使用solr.前缀+域类型的类名便可,若是是你自定义的域类型,则class表示自定义域类型的完整类名(包含完整的包路径),在fileType元素下有analyzer元素,用来配置当前域类型使用什么分词器,你确定很奇怪,为何要配置两个analyzer,其实主要是为了区分两个阶段:索引创建阶段和Query查询阶段,索引创建阶段须要分词毋庸置疑,查询阶段是否须要分词,则取决于你的业务需求,用过Google的知道,用户在查询输入框里输入查询关键字,这时候咱们须要对用户输入的查询关键字进行分词器,这时候咱们就须要配置查询阶段使用什么分词器,为何把分开配置?二者可使用统一配置不行吗,配置两遍不是显得很冗余且繁琐吗?analyzertype元素就表示这两个阶段,之因此要分阶段配置分词器,是为了知足用户潜在的需求,由于查询阶段的分词需求和索引阶段的分词需求不必定是相同的。咱们都知道分词器Analyzer是由一个Tokenizer + NtokenFilter组成,这就是为何analyzer元素下会有tokenizer元素和filter元素,但tokenizer元素只容许有一个,filter元素能够有N个。之因此这样设计是为了为用户提供更细粒度的方式来配置分词器的行为,即你能够任意组合tokenizerfilter来实现你的特定需求,固然你也能够把这种组合写在Analyzer类里,而后直接在analyzer元素的class属性里配置自定义分词器的完整类名,这样就不须要这么繁琐的配置tokenizerfilter,即把实现细节屏蔽在analyzer类内部,但这样作的话,若是你须要更改实现细节,则须要修改Analyzer源码,而后从新打包成jar,相对来讲,比较麻烦点,而使用analyzertokenizer,filter这样来配置,虽然繁琐点,但更灵活。并且采用<analyzer class="xxxxxxxx.IKAnalyzer"这样配置方式,看起来是比较简洁,我想你可能会比较喜欢这种方式,遗憾的是,solr在实现这种方式的时候,考虑不够周全,好比IKAnalyzer分词器,咱们都知道IK分词器的构造器还有个useSmart参数,表示是否开启智能分词,而<analyzer class="xxxxxxxx.IKAnalyzer"这种方式,本质仍是经过SAX方式解析XML,而后获得class类型字符串,而后经过反射去建立Analyzer实例对象,你可能会问我,我为何知道是这样实现的?我看了Solr的源码因此我知道,无码无真相,来看截图:(FieldTypePluginLoader类中)
http://dl2.iteye.com/upload/attachment/0109/5885/8806ad13-0f27-3cf0-9046-626e0ca49c12.png
 
关键点部分我已经使用红色方框标注出来了,class.newInstance()本质就是经过反射的方式去调用类的无参构造函数,这个你们都知道吧,而IKAnalyzer分词器的构造函数代码如图:
http://dl2.iteye.com/upload/attachment/0109/5887/e0d1bbda-575e-356a-ad33-db40dda71fe5.png
 
这意味着useSmart参数永远得不到设置,它永远为false,这就是采用<analyzer class="xxxxxxxx.IKAnalyzer"这种方式进行配置的弊端。它看似很是简洁,但暗藏陷阱,坑爹的Solr。那有没办法解决呢?我能想到的办法就是修改源码从新打包,你可能会问怎么修改?听我慢慢说,不要急。

      FieldTypePluginLoader类中有个readAnalyzer(Node node)方法,其中有一句代码很是关键:

Java代码  

  1. NamedNodeMap attrs = node.getAttributes();  
  2. String analyzerName = DOMUtil.getAttr(attrs,"class");  

   其中node对象即表示当前<analyzer元素节点,而DOMUtil.getAttr(attrs,"class");表示经过DOMUtil工具类来获取<analyzer元素的class属性,这个好理解吧,咱们在schema.xml中多是这样配置的

     <analyzer class="xxxxx.IKAnalyzer",那一句目的就是获取分词器的class类名,知道类名了就能够反射去建立分词器实例对象啊,就这么简单,因此咱们能够本身在<analyzer元素中加一个参数,好比这样:

Xml代码  

  1. <analyzer class="org.wltea.analyzer.lucene.IKAnalyzer" useSmart="true"/>     

   而后咱们在代码里String useSmart = DOMUtil.getAttr(attrs,"useSmart");就能够获取到属性值了,而后就是经过反射把属性值设置到IKAnalyzer类的useSmart属性中了,这是基本的Java反射操做,下面我提供几个反射工具方法:

Java代码  

  1. /** 
  2.      * 循环向上转型获取对象的DeclaredField. 若向上转型到Object仍没法找到返回null. 
  3.      */  
  4.     protected static Field getDeclaredField(final Object object, final String fieldName) {  
  5.         if (null == object || null == fieldName || fieldName.equals("")) {  
  6.             return null;  
  7.         }  
  8.         for (Class<?> superClass = object.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) {  
  9.             try {  
  10.                 return superClass.getDeclaredField(fieldName);  
  11.             } catch (NoSuchFieldException e) {  
  12.                 // Field不在当前类定义,继续向上转型  
  13.                 continue;  
  14.             }  
  15.         }  
  16.         return null;  
  17.     }  
  18.   
  19.   
  20.   
  21. /** 
  22.      * 直接设置对象属性值无视private/protected修饰符不通过setter函数. 
  23.      */  
  24.     public static void setFieldValue(final Object object, final String fieldName, final Object value) {  
  25.         Field field = getDeclaredField(object, fieldName);  
  26.         if (field == null) {  
  27.             throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + object + "]");  
  28.         }  
  29.         makeAccessible(field);  
  30.         try {  
  31.             field.set(object, value);  
  32.         } catch (IllegalAccessException e) {  
  33.             throw new RuntimeException("直接设置对象属性值出现异常", e);  
  34.         }  
  35.     }  
  36.   
  37.   
  38.   
  39.   
  40. /** 
  41.      * 强行设置Field可访问 
  42.      */  
  43.     protected static void makeAccessible(final Field field) {  
  44.         if (!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers())) {  
  45.             field.setAccessible(true);  
  46.         }  
  47.     }  

    直接调用setFieldValue方法便可,好比在Analyzer analyzer = clazz.newInstance();这句下面添加一句

   setFieldValue(analyzer,"useSmart",Boolean.valueOf(useSmart ));

   这样咱们在xml中配置的useSmart参数就设置到Analyzer类中了,这样才能起做用。solr源码如何导入Eclipse上篇博客里我已经介绍过了,至于若是把修改事后的代码打包成jar,直接使用eclipse自带的export功能便可,如图:
http://dl2.iteye.com/upload/attachment/0109/5891/39c3dc91-8adb-32e2-b701-61b267cda4ae.png
http://dl2.iteye.com/upload/attachment/0109/5893/a8557baf-6fda-3bc5-aa1f-b4c858fd9a27.png
http://dl2.iteye.com/upload/attachment/0109/5895/d3c971b8-ebe0-3718-81d8-9a56a3234dce.png
http://dl2.iteye.com/upload/attachment/0109/5897/eedfe76b-6b04-346f-b57b-6ebf0ad9a48c.png
http://dl2.iteye.com/upload/attachment/0109/5899/4a4eabcb-bb0e-338b-bdbe-bfc7e0c9a775.png
 
而后一路Next便可。我只是说说思路,剩下留给大家本身去实践。

     可是采用改源码方式不是很优雅,由于你本地虽然是修改好了,哪天你由Solr5.1.0升级到5.2.0,还要再改一遍,没升级一次就要改一次,你的代码copy给别人用,别人运行代码后看不到效果,增长沟通成本,你还得把你改过源码的jar包共享给别人,这也就是为何有那么多人找我要什么IK jar包。

    Solr中可使用TokenizerFactory方式来解决我刚才提出的问题:IKAnalyzer分词器的useSmart参数没法经过schema.xml配置文件进行设置。我花了点时间扩展了IKTokenizerFactory类,代码以下:

Java代码  

  1. package org.apache.lucene.analysis.ik;  
  2.   
  3. import java.util.Map;  
  4.   
  5. import org.apache.lucene.analysis.Tokenizer;  
  6. import org.apache.lucene.analysis.util.TokenizerFactory;  
  7. import org.apache.lucene.util.AttributeFactory;  
  8. import org.wltea.analyzer.lucene.IKTokenizer;  
  9.   
  10. public class IKTokenizerFactory extends TokenizerFactory {  
  11.     public IKTokenizerFactory(Map<String, String> args) {  
  12.         super(args);  
  13.         useSmart = getBoolean(args, "useSmart"false);  
  14.     }  
  15.     private boolean useSmart;  
  16.   
  17.     @Override  
  18.     public Tokenizer create(AttributeFactory attributeFactory) {  
  19.         Tokenizer tokenizer = new IKTokenizer(attributeFactory,useSmart);  
  20.         return tokenizer;  
  21.     }  
  22. }  

   同时我对IKTokenizer类也稍做了修改,修改后源码以下:

Java代码  

  1. /** 
  2.  * IK分词器 Lucene Tokenizer适配器类 
  3.  * 兼容Lucene 4.0版本 
  4.  */  
  5. public final class IKTokenizer extends Tokenizer {  
  6.       
  7.     //IK分词器实现  
  8.     private IKSegmenter _IKImplement;  
  9.       
  10.     //词元文本属性  
  11.     private final CharTermAttribute termAtt;  
  12.     //词元位移属性  
  13.     private final OffsetAttribute offsetAtt;  
  14.     //词元分类属性(该属性分类参考org.wltea.analyzer.core.Lexeme中的分类常量)  
  15.     private final TypeAttribute typeAtt;  
  16.     //记录最后一个词元的结束位置  
  17.     private int endPosition;  
  18.       
  19.     private Version version = Version.LATEST;  
  20.     /** 
  21.      * Lucene 4.0 Tokenizer适配器类构造函数 
  22.      * @param in 
  23.      * @param useSmart 
  24.      */  
  25.     public IKTokenizer(Reader in , boolean useSmart){  
  26.         //super(in);  
  27.         offsetAtt = addAttribute(OffsetAttribute.class);  
  28.         termAtt = addAttribute(CharTermAttribute.class);  
  29.         typeAtt = addAttribute(TypeAttribute.class);  
  30.         _IKImplement = new IKSegmenter(input , useSmart);  
  31.     }  
  32.       
  33.     public IKTokenizer(AttributeFactory factory, boolean useSmart) {  
  34.         super(factory);  
  35.         offsetAtt = addAttribute(OffsetAttribute.class);  
  36.         termAtt = addAttribute(CharTermAttribute.class);  
  37.         typeAtt = addAttribute(TypeAttribute.class);  
  38.         _IKImplement = new IKSegmenter(input , useSmart);  
  39.     }  
  40.   
  41.     /* (non-Javadoc) 
  42.      * @see org.apache.lucene.analysis.TokenStream#incrementToken() 
  43.      */  
  44.     @Override  
  45.     public boolean incrementToken() throws IOException {  
  46.         //清除全部的词元属性  
  47.         clearAttributes();  
  48.         Lexeme nextLexeme = _IKImplement.next();  
  49.         if(nextLexeme != null){  
  50.             //Lexeme转成Attributes  
  51.             //设置词元文本  
  52.             termAtt.append(nextLexeme.getLexemeText());  
  53.             //设置词元长度  
  54.             termAtt.setLength(nextLexeme.getLength());  
  55.             //设置词元位移  
  56.             offsetAtt.setOffset(nextLexeme.getBeginPosition(), nextLexeme.getEndPosition());  
  57.             //记录分词的最后位置  
  58.             endPosition = nextLexeme.getEndPosition();  
  59.             //记录词元分类  
  60.             typeAtt.setType(nextLexeme.getLexemeTypeString());            
  61.             //返会true告知还有下个词元  
  62.             return true;  
  63.         }  
  64.         //返会false告知词元输出完毕  
  65.         return false;  
  66.     }  
  67.       
  68.     /* 
  69.      * (non-Javadoc) 
  70.      * @see org.apache.lucene.analysis.Tokenizer#reset(java.io.Reader) 
  71.      */  
  72.     @Override  
  73.     public void reset() throws IOException {  
  74.         super.reset();  
  75.         _IKImplement.reset(input);  
  76.     }     
  77.       
  78.     @Override  
  79.     public final void end() {  
  80.         // set final offset  
  81.         int finalOffset = correctOffset(this.endPosition);  
  82.         offsetAtt.setOffset(finalOffset, finalOffset);  
  83.     }  

    修改后从新打包的IKAnalyzer jar请见底下的附件。

    而后我把它打包成了solr-analyzer-ik-5.1.0.jar,只须要把这个jar包复制到你的core\lib目录下便可,而后你就能够像配置StandardTokenizerFactory同样的使用咱们自定义的IKTokenizerFactory类了,而且能配置useSmart参数,这正是我想要的,能灵活的控制分词器参数,so cool。配置示例以下:
http://dl2.iteye.com/upload/attachment/0109/5902/0f84cde8-c977-3976-b0e1-5718b1c8da50.png
而后field域里应用咱们配置的这个text_ik域类型,如图:
http://dl2.iteye.com/upload/attachment/0109/5904/02e08a7a-e8d3-3854-8947-5bd7f51fa982.png
 
而后你还须要把IKAnalyzer jar包以及咱们自定义的IKTokenizerFactoryjarcopy到你当前core\lib目录下,如图:
http://dl2.iteye.com/upload/attachment/0109/5906/149c2b92-a731-3a8b-bc78-ae8168f98fe9.png
 IKAnalyzer jar
建议使用底下附件里我新上传的,由于源码我稍做了修改,上面已经提到过了。而后你须要把IKAnalyzer.cfg.xml配置文件copyE:\apache-tomcat-7.0.55\webapps\solr\WEB-INF\classes目录下,其中E:\apache-tomcat-7.0.55为个人Tomcat安装根目录,请类比成你本身的tomcat安装根目录,你懂的。如图:
http://dl2.iteye.com/upload/attachment/0109/5908/2eabdfe1-9ffe-33c8-80ac-f8a6f9870fd1.png
 IKAnalyzer.cfg.xml
配置如图:
http://dl2.iteye.com/upload/attachment/0109/5910/bcfdfddc-366a-3505-85bc-6601443cee73.png
 ext.dic
IK分词器的自定义扩展词典,内容如图:
http://dl2.iteye.com/upload/attachment/0109/5912/9affdc53-286d-3aea-a7fc-2691e89320fd.png
 
我就在里面加了两个自定义词语。

而后你就能够启动你的tomcat,而后如图进行分词测试了,
http://dl2.iteye.com/upload/attachment/0109/5914/3c0a0ff0-611e-325b-b8a6-c6866d62ded7.png
 
上图是用来测试useSmart参数设置是否有生效,若是你看到如图的效果,说明配置成功了。


http://dl2.iteye.com/upload/attachment/0109/5917/c2107b8f-c1ca-3674-96e4-2e871d36de7b.png
 
上图是用来测试自定义词典是否有生效,由于我在ext.dic自定义词典里添加了 劲爆  * 这两个词,因此IK能分出来,逆袭和白富美没有在自定义扩展词典里添加,因此IK分不出来。若是你能看到如图效果,说明IK的自定义扩展词典也配置成功了

 OK,直接开门见山,不绕弯子啦!基于上篇博客,咱们知道了在Solr中配置分词器有两种方式,一种是直接配置分词器类,好比:

Xml代码  

  1. <fieldType name="text_ik" class="solr.TextField">        
  2.         <analyzer class="org.wltea.analyzer.lucene.IKAnalyzer" />        
  3. </fieldType>  

 一种是配置TokenizerFactory类,因为Solr API中并无内置相似IKAnsj这样的中文分词器的TokenizerFactory类,因此咱们须要本身扩展,不过大家不用担忧,我已经扩展好了。配置样例以下:

Xml代码  

  1. <fieldType name="text_ik" class="solr.TextField">  
  2.         <analyzer type="index">  
  3.             <tokenizer class="org.apache.lucene.analysis.ik.IKTokenizerFactory" useSmart="true"/>  
  4.         </analyzer>  
  5.         <analyzer type="query">  
  6.             <tokenizer class="org.apache.lucene.analysis.ik.IKTokenizerFactory" useSmart="false"/>  
  7.         </analyzer>  
  8. </fieldType>  

    我扩展的AnsjTokenizerFactory源码以下:

Java代码  

  1. public class AnsjTokenizerFactory  extends TokenizerFactory {  
  2.     /**是否查询分词*/  
  3.     private boolean query;  
  4.     /**是否分析词干.进行单复数,时态的转换(只针对英文单词)*/  
  5.     private boolean pstemming;  
  6.     /**自定义停用词词典文件路径*/  
  7.     private String stopwordsDir;  
  8.           
  9.     public AnsjTokenizerFactory(Map<String, String> args) {  
  10.         super(args);  
  11.         query = getBoolean(args, "query"false);  
  12.         pstemming = getBoolean(args, "pstemming"false);  
  13.         stopwordsDir = get(args, "stopwordsDir""");  
  14.     }  
  15.       
  16.     @Override  
  17.     public Tokenizer create(AttributeFactory factory) {  
  18.         if(query) {  
  19.             return new AnsjTokenizer(factory,new ToAnalysis(new Forest[0]),stopwordsDir,pstemming);  
  20.         }  
  21.         return new AnsjTokenizer(factory,new IndexAnalysis(new Forest[0]),stopwordsDir,pstemming);  
  22.     }  
  23. }  

   下面介绍如何在Solr中使用Ansj分词器,首先你须要在Solr_homecore\lib目录下添加依赖的jar

    ansj_seg-2.0.8.jar(戳我试试(*^__^*) 嘻嘻)

    solr-analyzer-ansj-5.1.0.jar(这个jar包体积较小,请在底下的附件里下载)

    nlp-lang-0.2.jar(这是ansj-seg-2.0.8.jar依赖的jar)

    如图:
http://dl2.iteye.com/upload/attachment/0109/6213/490a1251-ec9b-3e5d-8402-493fdcda9487.png
 
而后在schema.xml中添加以下配置:
http://dl2.iteye.com/upload/attachment/0109/6217/77aca6f9-7375-31c8-a79a-1f880175191f.png
 
至于querypstemmingstopwordsDir3个配置参数的含义,请看我在源码中做的注释,如图:
http://dl2.iteye.com/upload/attachment/0109/6219/435f3da5-1301-3408-89fc-660979cde37a.png
 query
参数:分词的两个阶段:创建索引阶段和查询阶段,即表示是否为查询阶段分词,针对不一样的分词阶段采用的分词策略是不同的。具体看源码,如图:
http://dl2.iteye.com/upload/attachment/0109/6221/7a28ffaf-335e-38bf-8a51-3776597ab5b9.png
 pstemming
:表示是否对英文单词进行单复数转换以及时态转换,好比apples还原成appleloved还原成love,broken还原成break,注意pstemming参数仅仅是针对英文,由于只有英文才有单复数和时态形式。

stopwordsDir参数就很好理解了,就是你的自定义停用词词典的存放路径,

上面3个参数是可选的,不是必须配置的。

TokenizerFactory配置好了,而后你就在你的Field中应用此分词器了,如图:
http://dl2.iteye.com/upload/attachment/0109/6223/229ab5c8-dc68-35c9-bc6e-0f6bf5f5a10a.png
 
而后你须要把ansjlibrary.properties配置文件copyE:\apache-tomcat-7.0.55\webapps\solr\WEB-INF\classes路径,如图:
http://dl2.iteye.com/upload/attachment/0109/6225/293aa39a-6bfb-3b4e-9c40-c5ebf3d4421b.png
 library.properties
配置以下:

Xml代码  

  1. #redress dic file path  
  2. ambiguityLibrary=E:/apache-tomcat-7.0.55/webapps/solr/WEB-INF/classes/library/ambiguity.dic  
  3. #path of userLibrary this is default library  
  4. userLibrary=E:/apache-tomcat-7.0.55/webapps/solr/WEB-INF/classes/library  
  5. #set real name  
  6. isRealName=true  

 比较恶心的是,anasj分词器的字典文件加载路径这里只能写死成绝对路径,由于它源码里加载字典文件是直接经过new File(dicPath)这种方式来实现的。当你在eclipse中运行,你的dicPath相对路径是你的项目根目录,而若是你的项目部署到tomcat,那dicPath的相对路径就是tomcat根目录下的bin,这确实比较啃爹,因此我这里干脆直接写成绝对路径,固然你能够把字典文件放到任意目录下,好比C:\Library,不是非要放到tomcat下,这个我必需要澄清下。下图是Ansj分词器在加载字典文件时比较恶心的实现方式:
http://dl2.iteye.com/upload/attachment/0109/6227/9537c120-3c0d-3518-a27c-9165ae667184.png
 
若是改为这样方式加载字典文件会比较好点,我我的以为:

   this.getClass().getClassLoader().getResourceAsStream(dicPath);

这样你的字典文件路径dicPath的相对路径才是当前classPath。不过ansj里你把字典文件配置成绝对路径也能够,不必定非要相对路径,这点仁者见仁智者见智吧!骚年,你怎么看?

接着你须要在E:\apache-tomcat-7.0.55\webapps\solr\WEB-INF\classes目录下新建一个library目录,而后把ansj自带的两个字典文件ambiguity.dicdefault.dic复制进去,而后新建一个ext.dic文件。相关的字典文件和配置文件我待会儿会上传到附件里供大家参考。其中ext.dic是用户自定义扩展字典文件,如图:
http://dl2.iteye.com/upload/attachment/0109/6230/651ce113-9f25-3362-b2c3-152d8b2bb635.png
 
http://dl2.iteye.com/upload/attachment/0109/6232/80b5c34a-6c5c-3b13-a404-91186ce48fe7.png
 
对于相似这种网络新词,ansj分词器默认是分不出来的,这时就须要定义自定义扩展字典。

你应该已经发现了,咱们在配置AnsjTokenizerFactory的时候配置了stopwordsDir="stopwords/stopwords.dic"自定义停用词加载路径,这里的stopwordsDir是相对于当前classpath的即E:\apache-tomcat-7.0.55\webapps\solr\WEB-INF\classes,因此咱们须要在E:\apache-tomcat-7.0.55\webapps\solr\WEB-INF\classes下新建stopwords文件夹,而后在stopwords文件夹下新建一个stopwords.dic字典文件,stopwords.dic内容以下:
http://dl2.iteye.com/upload/attachment/0109/6234/275e017a-a528-303e-bec6-687a24a13014.png
 
这里我只加两个词语做为演示,大家能够根据你本身的需求随意添加你本身的停用词。须要解释下的是,之因此stopwordsDir参数是相对于当前classpath,是由于我在实现AnsjTokenizerFactory时是采用这样的方式来加载词典文件的,如图:
http://dl2.iteye.com/upload/attachment/0109/6236/aed58239-a813-385b-8fad-95d81f6ca6c6.png
 
这一切准备好了,你就开始进行分词测试了,请如图进行测试:
http://dl2.iteye.com/upload/attachment/0109/6238/ece9949a-a990-3f36-af8c-87ef45da71d4.png
 
上图是对自定义新词进行分词测试,么么哒和啪**之因此能被分出来,是由于咱们在library\ext.dic自定义词典文件中添加了那两个词语。


http://dl2.iteye.com/upload/attachment/0109/6242/7a414627-b5fc-32ea-8359-fa1d61388a16.png
 
上图是用来测试pstemming参数即英文单词的单复数转换以及时态转换,loved是过去式,自动被转换成原型love
http://dl2.iteye.com/upload/attachment/0109/6244/5524d971-7e14-3b6c-bdb9-d190c7444820.png
 

上图是用来测试自定义停用词的,若是你看到如图效果,说明你配置成功了!

相关文章
相关标签/搜索