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
 

上圖是用來測試自定義停用詞的,若是你看到如圖效果,說明你配置成功了!

相關文章
相關標籤/搜索