Solr分词器

首先我们来看看我当初使用Lucene5是如何实现的,
https://i-blog.csdnimg.cn/blog_migrate/93a559c61e174d82b6473edbb2434fe1.png
 
Solr5中,我们只需要为IKTokenizer扩展一个IKTokenizerFactory,PinyinTokenFilter扩展一个PinyinTokenFilterFactory,PinyinNGramTokenFilter扩展一个PinyinNGramTokenFilterFactory,其中IKTokenizerFactory我已经扩展过了,剩下需要做的就是自定义PinyinTokenFilterFactoryPinyinNGramTokenFilterFactory了。如果你不知道如何扩展,请参看SolrStopFilterFactory类源码,照葫芦画瓢。OK,我来全程截图示范,我是如何扩展的?

     既然是要扩展PinyinTokenFilterFactory,从类名就知道它是PinyinTokenFilter的工厂类,所以我们首先需要把我之前写的PinyinTokenFilterPinyinNGramTokenFiltercopy到一个新的项目中来,如图:
https://i-blog.csdnimg.cn/blog_migrate/72333de96ec203ef3509bdc71562a6ab.png
 
我新建一个solr-analyzer-extra Java Project,把我之前写的几个类copy到如图红色框住的package中,那几个类你在我Lucene5系列博客中都可以找到源码,或者你到我的GitHub上也可以得到相关源码。我的GitHub地址待会儿我会在博客的结尾处贴出来,敬请关注哦!图片中显示还有ikansj两个package,这就是我为了前面几篇博客扩展的TokenizerFactory,你懂的!然后我们需要添加依赖的Jar包,如图:
https://i-blog.csdnimg.cn/blog_migrate/a8c5a99b6c1bf47d133188daaa5893ab.png
 
之所以分Lucene5Solr5两个包,就是为了方便打包Jar包,这样我就可以把lucene5包下的类单独打包成一个jarsolr5下打包成一个jar,当你仅仅只是在Lucene5下需要使用拼音分词,那solr5包下的类是用不到的,打包成两个jar是为了按需加载类,你懂的!特此说明。

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

https://i-blog.csdnimg.cn/blog_migrate/fec54e208df7362f2425279c8591a4f1.png
 扩展的PinyinNGramTokenFilterFactory源码如图:
https://i-blog.csdnimg.cn/blog_migrate/fc13a1e9dde0a326c4c8a4ed70f6307e.png
 
对应的PinyinNGramTokenFilter类我稍作了修改,主要是添加了nGramNumber参数,用于控制是否对纯数字进行nGram处理,有时候可能并不希望对类似 2011 这样的数字进行nGram,当然如果你需要对纯数字字符串进行nGram处理,请把nGramNumber参数设置为true即可,默认该值为falsePinyinNGramTokenFilter类我修改的地方如下:
https://i-blog.csdnimg.cn/blog_migrate/6640b0f9b9c93bb8067b0dc582da1f64.png
 
https://i-blog.csdnimg.cn/blog_migrate/aaa0fe04707bf22ebdc6d616619b443f.png
 
https://i-blog.csdnimg.cn/blog_migrate/32ff54ddf0f48c6710577061bd464cb6.png
 
https://i-blog.csdnimg.cn/blog_migrate/e91b74911530ad4cf6b02d47fe1a7da9.png

其中定义了一个常量类Constant,就是不想把默认值常量写死在各个类里,所以统一放到了一个常量类里,如图:
https://i-blog.csdnimg.cn/blog_migrate/5121de0315f78a1c8a3e7ce9d7f6a915.png

上面涉及到的所有源码我待会儿都会在底下附件里上传分享给你们。OK,到此该扩展的类都编写完毕了,我们需要将他们打包成jar,如图:
https://i-blog.csdnimg.cn/blog_migrate/656c3f2ebf6756bc49fcd50ec7164dfa.png
 
https://i-blog.csdnimg.cn/blog_migrate/91edad216818c4695f39c86eecc5d4c8.png
 
https://i-blog.csdnimg.cn/blog_migrate/1c41ef0e09dca00ce9a5e2a3d7491a13.png

https://i-blog.csdnimg.cn/blog_migrate/d67c37f9e9b01c24bae8d40db03ce761.png
 
https://i-blog.csdnimg.cn/blog_migrate/44e563c773c67b8333101176ac861a29.png
 
https://i-blog.csdnimg.cn/blog_migrate/510602828c297da2b8023e8bf9f9e178.png
 
https://i-blog.csdnimg.cn/blog_migrate/6c3c6dd1dd86b5a1c1ad1609ba65574e.png
 
https://i-blog.csdnimg.cn/blog_migrate/b589dfda7d38866978648815f0fab443.png
 
然后你就会在你的桌面上看到这个jar包,
https://i-blog.csdnimg.cn/blog_migrate/e76be313b5943641ce46fc1d11244c28.png
 OK,
同理,对solr5包下的类进行打包,提供给使用Solr5的用户使用,如图:
https://i-blog.csdnimg.cn/blog_migrate/c7acb75d2a72db57eefad7cc6de787cb.png
 
https://i-blog.csdnimg.cn/blog_migrate/fa49a59d563480e5ae32394e723b4d2e.png
 
https://i-blog.csdnimg.cn/blog_migrate/6773dffa1ae1eed268587e2fc891dcbb.png
 
https://i-blog.csdnimg.cn/blog_migrate/939107ed09b7a0c87ac7114cfbda9ae6.png
 
然后两个jar包就都打好了,如图:
https://i-blog.csdnimg.cn/blog_migrate/e778c9128f1bcfbf52ab89ca8326f0f2.png
 
接下来,我们就需要把我们打好的jar包导入到我们的corelib目录下,如图:
https://i-blog.csdnimg.cn/blog_migrate/1288abd76ae015a4a6a4cc6c41bfef48.png
 
由于我们的汉字转拼音使用到了pinyin4j类库,所以我们还需要把pinyin4jjar包也复制到当前corelib目录下,如图:
https://i-blog.csdnimg.cn/blog_migrate/72306b2e5069abb5f48cc1202a6fb5ef.png
 
由于我们是先进行中文分词,然后再对分出来的中文词语进行拼音转换,而这里我以IK分词器为例,所以我们还需要把IKjar包也copy进去,如图:
https://i-blog.csdnimg.cn/blog_migrate/71ba5bd2ce6a0d0b2d365f88fd507d8d.png
OK,jar
包导入完毕后,我们需要在我们的schema.xml中定义域类型,配置示例如图:
https://i-blog.csdnimg.cn/blog_migrate/3abba26eda5abd44dc6377f3c7fc3822.png
 
https://i-blog.csdnimg.cn/blog_migrate/da712c71b3ed1ddfee91111869c6ca9b.png
 
这是默认最基本的配置,当然PinyinTokenFilterFactoryPinyinNGramTokenFilterFactory这两个工厂类是有可选的配置参数可以设置的,请看图:
https://i-blog.csdnimg.cn/blog_migrate/924297adc457c5a1c83cd294ec8156e6.png
 
https://i-blog.csdnimg.cn/blog_migrate/73b70c4fc5089d191b9a9b848d26df21.png
 
因此,你也可以这样配置:
https://i-blog.csdnimg.cn/blog_migrate/601b7bec63b4098e91b242b74ccad0ba.png
 
域类型定义好后,你需要在你的某个域上应用这个新定义的text_pinyin域类型,如图:
https://i-blog.csdnimg.cn/blog_migrate/fc1c678bbf1a0ac0cf30a299115a0b32.png
 OK,
启动你的tomcat,开始进行拼音分词测试,如图:
https://i-blog.csdnimg.cn/blog_migrate/e4dd440565822c89231a0fbde08cd5c8.png
 
https://i-blog.csdnimg.cn/blog_migrate/57688db31296bfd0160f5b50a9aa1bcf.png
 OK
,到此关于Solr5中关于拼音分词以及拼音搜索就讲解到这儿了

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

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

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

https://i-blog.csdnimg.cn/blog_migrate/1b70c2a5d569012d8cce91ca8dad5aa3.png
     2.在你的schema.xml中配置fieldType应用上我扩展的MMSegTokenizerFactory类,具体配置看图:
https://i-blog.csdnimg.cn/blog_migrate/c8b0974661b25c44bc12268af4b78307.png
 

Xml代码  0f41823e677b8008d338d2278b05f312895.jpg

  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的,自定义停用词字典文件放置路径请看图:
https://i-blog.csdnimg.cn/blog_migrate/c9b847f042849c8cc35affa18373bec9.png
 
自定义停用词词典文件加载路径配置参数是可选的,不过由于MMSeg4J没有内置停用词功能,所以像空格字符,标点符号等等都会被分出来,所以一般建议添加停用词词典文件。不过要注意的是,自定义的停用词词典文件的编码必须是UTF-8BOM格式,而且在你使用文本编辑软件打开进行编辑的时候,请务必将你的编辑软件的编码设置为UTF-8,否则可能会出现本来是UTF-8BOM编码,你打开编辑保存后编码就改变了。当你发现明明停用词在词典文件里,却很奇怪不起作用时,那十有八九是因为词典文件编码已经被破坏,建议词典文件不要自己新建,可以保留一个dic模版文件,每次直接copy过来修改文件名然后再打开编辑。

 

     3.然后你需要在你的某个field域上应用刚才定义的FieldType(域类型),如图:
https://i-blog.csdnimg.cn/blog_migrate/031db21ccbdc26681240db005d240947.png
        OK
,现在你可以启动你的Tomcat进行分词测试了,如图:
https://i-blog.csdnimg.cn/blog_migrate/1f2ff9ac86bd2dea6a4b419a40f8b018.png
 mmseg-stopwrods
目录下的stopwords.dic停用词词典文件我添加了如下停用词:
https://i-blog.csdnimg.cn/blog_migrate/4dcd158c5021b00f989f8bdaf40ca6ea.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结尾的文件才会被加载

知道上述加载原理,那我们只需要把自定义扩展词典文件如图放置即可:
https://i-blog.csdnimg.cn/blog_migrate/850fe2141120315125de322339908f7a.png
 
https://i-blog.csdnimg.cn/blog_migrate/f1d625877cb47fdf6a9bb851c6d0d637.png
 
https://i-blog.csdnimg.cn/blog_migrate/2619752625a9f94ac06f47707d56396a.png
       
到此,MMSeg4J分词器在Solr5中的使用就讲解完毕了

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

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

Xml代码  d0d25ab1441b05a7795f2d9c0b9f9e2ec54.jpg

  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类中)
https://i-blog.csdnimg.cn/blog_migrate/b7472516a0b2925c69569f37ffafb2c8.png
 
关键点部分我已经使用红色方框标注出来了,class.newInstance()本质就是通过反射的方式去调用类的无参构造函数,这个大家都知道吧,而IKAnalyzer分词器的构造函数代码如图:
https://i-blog.csdnimg.cn/blog_migrate/6d8206c9ee27346af58d68cddc034b14.png
 
这意味着useSmart参数永远得不到设置,它永远为false,这就是采用<analyzer class="xxxxxxxx.IKAnalyzer"这种方式进行配置的弊端。它看似非常简洁,但暗藏陷阱,坑爹的Solr。那有没办法解决呢?我能想到的办法就是修改源码重新打包,你可能会问怎么修改?听我慢慢说,不要急。

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

Java代码  0b22cd404fde7c1404d70136385175a3354.jpg

  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代码  31a59c4c9901bade15a56a6bedb9cdf6962.jpg

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

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

Java代码  a07760cb3823aeedcdb8f6597674f79a2e6.jpg

  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功能即可,如图:
https://i-blog.csdnimg.cn/blog_migrate/32066cfa9e2eec0b43eb624532134245.png
https://i-blog.csdnimg.cn/blog_migrate/b2c19a78b0ff1054dd55c08aaab48f1e.png
https://i-blog.csdnimg.cn/blog_migrate/02a7fc0c3cadd5df49b796a070f9c07d.png
https://i-blog.csdnimg.cn/blog_migrate/5fec74ba6af8e22c68b3179bbd8e8296.png
https://i-blog.csdnimg.cn/blog_migrate/5aaa6cc1ea73ebe15548790a8d48a90f.png
 
然后一路Next即可。我只是说说思路,剩下留给你们自己去实践。

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

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

Java代码  0dc3981e640df5ba2a9b096b4376a7a35b9.jpg

  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代码  6d05b3632036700faa4daa4b52444da1883.jpg

  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。配置示例如下:
https://i-blog.csdnimg.cn/blog_migrate/e3093e52cbec1e0834b9b3ec578bcb02.png
然后field域里应用我们配置的这个text_ik域类型,如图:
https://i-blog.csdnimg.cn/blog_migrate/9bec3ff1c56a058d0d81fb82cc5e2131.png
 
然后你还需要把IKAnalyzer jar包以及我们自定义的IKTokenizerFactoryjarcopy到你当前core\lib目录下,如图:
https://i-blog.csdnimg.cn/blog_migrate/2a3d03d1608b57b4920a39adc9d9050c.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安装根目录,你懂的。如图:
https://i-blog.csdnimg.cn/blog_migrate/60c2b8290babec9c6b773d11c310a703.png
 IKAnalyzer.cfg.xml
配置如图:
https://i-blog.csdnimg.cn/blog_migrate/0bdb5e4e55c9860ddc0b32e641fd362e.png
 ext.dic
IK分词器的自定义扩展词典,内容如图:
https://i-blog.csdnimg.cn/blog_migrate/aca2a6a6728e8ea200d9de0211da934a.png
 
我就在里面加了两个自定义词语。

然后你就可以启动你的tomcat,然后如图进行分词测试了,
https://i-blog.csdnimg.cn/blog_migrate/177afdc027195c52a0a379c11aec83db.png
 
上图是用来测试useSmart参数设置是否有生效,如果你看到如图的效果,说明配置成功了。


https://i-blog.csdnimg.cn/blog_migrate/f694db3185865243f2bb7090b168bed5.png
 
上图是用来测试自定义词典是否有生效,因为我在ext.dic自定义词典里添加了 劲爆  * 这两个词,所以IK能分出来,逆袭和白富美没有在自定义扩展词典里添加,所以IK分不出来。如果你能看到如图效果,说明IK的自定义扩展词典也配置成功了

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

Xml代码  f8a471915b588f164016d9c37b387be574a.jpg

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

 一种是配置TokenizerFactory类,由于Solr API中并没有内置类似IKAnsj这样的中文分词器的TokenizerFactory类,所以我们需要自己扩展,不过你们不用担心,我已经扩展好了。配置样例如下:

Xml代码  39e796c0dd314eb5f8d14cf6cb5feb31221.jpg

  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代码  8d51bfd90b789846938e013b82c97a61648.jpg

  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)

    如图:
https://i-blog.csdnimg.cn/blog_migrate/0caeea6d68cadbb27e44a8602d926c9a.png
 
然后在schema.xml中添加如下配置:
https://i-blog.csdnimg.cn/blog_migrate/616b7a39f94e9fbb0767da10661a5099.png
 
至于querypstemmingstopwordsDir3个配置参数的含义,请看我在源码中作的注释,如图:
https://i-blog.csdnimg.cn/blog_migrate/94210d528b8de829ff1429a2d8963e76.png
 query
参数:分词的两个阶段:建立索引阶段和查询阶段,即表示是否为查询阶段分词,针对不同的分词阶段采用的分词策略是不一样的。具体看源码,如图:
https://i-blog.csdnimg.cn/blog_migrate/a55c78caa8cc66604e85f97811527261.png
 pstemming
:表示是否对英文单词进行单复数转换以及时态转换,比如apples还原成appleloved还原成love,broken还原成break,注意pstemming参数仅仅是针对英文,因为只有英文才有单复数和时态形式。

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

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

TokenizerFactory配置好了,然后你就在你的Field中应用此分词器了,如图:
https://i-blog.csdnimg.cn/blog_migrate/91e292605c5b946ef70b3c138e0b13d8.png
 
然后你需要把ansjlibrary.properties配置文件copyE:\apache-tomcat-7.0.55\webapps\solr\WEB-INF\classes路径,如图:
https://i-blog.csdnimg.cn/blog_migrate/e54f835d9a2a78aa18ce7adf3618eceb.png
 library.properties
配置如下:

Xml代码  334ce575f43623bad1ab17960ecedcbc3d3.jpg

  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分词器在加载字典文件时比较恶心的实现方式:
https://i-blog.csdnimg.cn/blog_migrate/0712c3a754162b7378d52918891de825.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是用户自定义扩展字典文件,如图:
https://i-blog.csdnimg.cn/blog_migrate/f0dc532eded59e2ff94583eb43e65394.png
 
https://i-blog.csdnimg.cn/blog_migrate/a1a7a191cc74fd9fd84042d61e9cb13c.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内容如下:
https://i-blog.csdnimg.cn/blog_migrate/7baa4a19c27a6b5b9c03feb6c6149ca0.png
 
这里我只加两个词语作为演示,你们可以根据你自己的需求随意添加你自己的停用词。需要解释下的是,之所以stopwordsDir参数是相对于当前classpath,是因为我在实现AnsjTokenizerFactory时是采用这样的方式来加载词典文件的,如图:
https://i-blog.csdnimg.cn/blog_migrate/13652aec8c434270feefdd62f92069d5.png
 
这一切准备好了,你就开始进行分词测试了,请如图进行测试:
https://i-blog.csdnimg.cn/blog_migrate/883c146bfe239235ddebd60f1224a0fd.png
 
上图是对自定义新词进行分词测试,么么哒和啪**之所以能被分出来,是因为我们在library\ext.dic自定义词典文件中添加了那两个词语。


https://i-blog.csdnimg.cn/blog_migrate/cb89aaa0291e08fb6ec1fd7b0b661aa9.png
 
上图是用来测试pstemming参数即英文单词的单复数转换以及时态转换,loved是过去式,自动被转换成原型love
https://i-blog.csdnimg.cn/blog_migrate/708f56b43bf383c48d9bd8f369d9b735.png
 

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

 

转载于:https://my.oschina.net/u/147708/blog/1923939

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值