文章轉載自:http://www.cnblogs.com/dennisit/archive/2013/04/07/3005847.htmlhtml
方案一: 基於配置的詞典擴充java
項目結構圖以下:
IK分詞器還支持經過配置IKAnalyzer.cfg.xml文件來擴充您的專有詞典。谷歌拼音詞庫下載: http://ishare.iask.sina.com.cn/f/14446921.html?from=like
在web項目的src目錄下建立IKAnalyzer.cfg.xml文件,內容以下web
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> <properties> <comment>IK Analyzer 擴展配置</comment> <!-- 用戶能夠在這裏配置本身的擴展字典 --> <entry key="ext_dict">/dicdata/use.dic.dic;/dicdata/googlepy.dic</entry> <!-- 用戶能夠在這裏配置本身的擴展中止詞字典 --> <entry key="ext_stopwords">/dicdata/ext_stopword.dic</entry> </properties>
詞典文件的編輯與部署
分詞器的詞典文件格式是無BOM 的UTF-8 編碼的中文文本文件,文件擴展名不限。詞典中,每一箇中文詞彙獨立佔一行,使用\r\n 的DOS 方式換行。(注,若是您不瞭解什麼是無BOM 的UTF-8 格式, 請保證您的詞典使用UTF-8 存儲,並在文件的頭部添加一空行)。您能夠參考分詞器源碼org.wltea.analyzer.dic 包下的.dic 文件。詞典文件應部署在Java 的資源路徑下,即ClassLoader 可以加載的路徑中。(推薦同IKAnalyzer.cfg.xml 放在一塊兒).算法
方案二:基於API的詞典擴充數據庫
在IKAnalyzer的與詞條相關的操做
1.org.wltea.analyzer.cfg
2.org.wltea.analyzer.dic apache
org.wltea.analyzer.cfg下Configuration接口中的定義 getExtDictionarys() 獲取擴展字典配置路徑 getExtStopWordDictionarys() 獲取擴展中止詞典配置路徑 getMainDictionary() 獲取主詞典路徑 getQuantifierDicionary() 獲取量詞詞典路徑 org.wltea.analyzer.cfg.DefualtConfig類是對Configuration接口的實現
org.wltea.analyzer.dic下的Directory類中相關的方法數組
public void addWords(java.util.Collection<java.lang.String> words) 批量加載新詞條 參數:words - Collection詞條列表 public void disableWords(java.util.Collection<java.lang.String> words) 批量移除(屏蔽)詞條
Lucene中使用IKAnalyzer分詞器實例演示
業務實體數據結構
package com.icrate.service.study.demo; /** * * * @version : 1.0 * * @author : 蘇若年 <a href="mailto:DennisIT@163.com">發送郵件</a> * * @since : 1.0 建立時間: 2013-4-7 下午01:52:49 * * @function: TODO * */ public class Medicine { private Integer id; private String name; private String function; public Medicine() { } public Medicine(Integer id, String name, String function) { super(); this.id = id; this.name = name; this.function = function; } //getter and setter() public String toString(){ return this.id + "," +this.name + "," + this.function; } }
構建模擬數據post
package com.icrate.service.study.demo; import java.util.ArrayList; import java.util.List; /** * * * @version : 1.0 * * @author : 蘇若年 <a href="mailto:DennisIT@163.com">發送郵件</a> * * @since : 1.0 建立時間: 2013-4-7 下午01:54:34 * * @function: TODO * */ public class DataFactory { private static DataFactory dataFactory = new DataFactory(); private DataFactory(){ } public List<Medicine> getData(){ List<Medicine> list = new ArrayList<Medicine>(); list.add(new Medicine(1,"銀花 感冒顆粒","功能主治:銀花感冒顆粒 ,頭痛,清熱,解表,利咽。")); list.add(new Medicine(2,"感冒 止咳糖漿","功能主治:感冒止咳糖漿,解表清熱,止咳化痰。")); list.add(new Medicine(3,"感冒靈顆粒","功能主治:解熱鎮痛。頭痛 ,清熱。")); list.add(new Medicine(4,"感冒靈膠囊","功能主治:銀花感冒顆粒 ,頭痛,清熱,解表,利咽。")); list.add(new Medicine(5,"仁和 感冒顆粒","功能主治:疏風清熱,宣肺止咳,解表清熱,止咳化痰。")); return list; } public static DataFactory getInstance(){ return dataFactory; } }
使用Lucene對模擬數據進行檢索測試
package com.icrate.service.study.demo; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriterConfig; import org.apache.lucene.index.Term; import org.apache.lucene.queryParser.MultiFieldQueryParser; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.Query; import org.apache.lucene.search.ScoreDoc; import org.apache.lucene.search.TopDocs; import org.apache.lucene.search.highlight.Formatter; import org.apache.lucene.search.highlight.Fragmenter; import org.apache.lucene.search.highlight.Highlighter; import org.apache.lucene.search.highlight.QueryScorer; import org.apache.lucene.search.highlight.Scorer; import org.apache.lucene.search.highlight.SimpleFragmenter; import org.apache.lucene.search.highlight.SimpleHTMLFormatter; import org.apache.lucene.store.Directory; import org.apache.lucene.store.FSDirectory; import org.apache.lucene.util.Version; import org.wltea.analyzer.lucene.IKAnalyzer; /** * * LuenceProcess.java * * @version : 1.1 * * @author : 蘇若年 <a href="mailto:DennisIT@163.com">發送郵件</a> * * @since : 1.0 建立時間: Apr 3, 2013 11:48:11 AM * * TODO : Luence中使用IK分詞器 * */ public class LuceneIKUtil { private Directory directory ; private Analyzer analyzer ; /** * 帶參數構造,參數用來指定索引文件目錄 * @param indexFilePath */ public LuceneIKUtil(String indexFilePath){ try { directory = FSDirectory.open(new File(indexFilePath)); analyzer = new IKAnalyzer(); } catch (IOException e) { e.printStackTrace(); } } /** * 默認構造,使用系統默認的路徑做爲索引 */ public LuceneIKUtil(){ this("/luence/index"); } /** * 建立索引 * Description: * @author dennisit@163.com Apr 3, 2013 * @throws Exception */ public void createIndex()throws Exception{ IndexWriterConfig indexWriterConfig = new IndexWriterConfig(Version.LUCENE_35,analyzer); IndexWriter indexWriter = new IndexWriter(directory,indexWriterConfig); indexWriter.deleteAll(); List<Medicine> list = DataFactory.getInstance().getData(); for(int i=0; i<list.size(); i++){ Medicine medicine = list.get(i); Document document = addDocument(medicine.getId(), medicine.getName(), medicine.getFunction()); indexWriter.addDocument(document); } indexWriter.close(); } /** * * Description: * @author dennisit@163.com Apr 3, 2013 * @param id * @param title * @param content * @return */ public Document addDocument(Integer id, String name, String function){ Document doc = new Document(); //Field.Index.NO 表示不索引 //Field.Index.ANALYZED 表示分詞且索引 //Field.Index.NOT_ANALYZED 表示不分詞且索引 doc.add(new Field("id",String.valueOf(id),Field.Store.YES,Field.Index.NOT_ANALYZED)); doc.add(new Field("name",name,Field.Store.YES,Field.Index.ANALYZED)); doc.add(new Field("function",function,Field.Store.YES,Field.Index.ANALYZED)); return doc; } /** * * Description: 更新索引 * @author dennisit@163.com Apr 3, 2013 * @param id * @param title * @param content */ public void update(Integer id,String title, String content){ try { IndexWriterConfig indexWriterConfig = new IndexWriterConfig(Version.LUCENE_35,analyzer); IndexWriter indexWriter = new IndexWriter(directory,indexWriterConfig); Document document = addDocument(id, title, content); Term term = new Term("id",String.valueOf(id)); indexWriter.updateDocument(term, document); indexWriter.close(); } catch (Exception e) { e.printStackTrace(); } } /** * * Description:按照ID進行索引 * @author dennisit@163.com Apr 3, 2013 * @param id */ public void delete(Integer id){ try { IndexWriterConfig indexWriterConfig = new IndexWriterConfig(Version.LUCENE_35,analyzer); IndexWriter indexWriter = new IndexWriter(directory,indexWriterConfig); Term term = new Term("id",String.valueOf(id)); indexWriter.deleteDocuments(term); indexWriter.close(); } catch (Exception e) { e.printStackTrace(); } } /** * * Description:查詢 * @author dennisit@163.com Apr 3, 2013 * @param where 查詢條件 * @param scoreDoc 分頁時用 */ public List<Medicine> search(String[] fields,String keyword){ IndexSearcher indexSearcher = null; List<Medicine> result = new ArrayList<Medicine>(); try { //建立索引搜索器,且只讀 IndexReader indexReader = IndexReader.open(directory,true); indexSearcher = new IndexSearcher(indexReader); MultiFieldQueryParser queryParser =new MultiFieldQueryParser(Version.LUCENE_35, fields,analyzer); Query query = queryParser.parse(keyword); //返回前number條記錄 TopDocs topDocs = indexSearcher.search(query, 10); //信息展現 int totalCount = topDocs.totalHits; System.out.println("共檢索出 "+totalCount+" 條記錄"); //高亮顯示 /* 建立高亮器,使搜索的結果高亮顯示 SimpleHTMLFormatter:用來控制你要加亮的關鍵字的高亮方式 此類有2個構造方法 1:SimpleHTMLFormatter()默認的構造方法.加亮方式:<B>關鍵字</B> 2:SimpleHTMLFormatter(String preTag, String postTag).加亮方式:preTag關鍵字postTag */ Formatter formatter = new SimpleHTMLFormatter("<font color='red'>","</font>"); /* QueryScorer QueryScorer 是內置的計分器。計分器的工做首先是將片斷排序。QueryScorer使用的項是從用戶輸入的查詢中獲得的; 它會從原始輸入的單詞、詞組和布爾查詢中提取項,而且基於相應的加權因子(boost factor)給它們加權。 爲了便於QueryScoere使用,還必須對查詢的原始形式進行重寫。 好比,帶通配符查詢、模糊查詢、前綴查詢以及範圍查詢 等,都被重寫爲BoolenaQuery中所使用的項。 在將Query實例傳遞到QueryScorer以前,能夠調用Query.rewrite (IndexReader)方法來重寫Query對象 */ Scorer fragmentScorer = new QueryScorer(query); Highlighter highlighter = new Highlighter(formatter,fragmentScorer); Fragmenter fragmenter = new SimpleFragmenter(100); /* Highlighter利用Fragmenter將原始文本分割成多個片斷。 內置的SimpleFragmenter將原始文本分割成相同大小的片斷,片斷默認的大小爲100個字符。這個大小是可控制的。 */ highlighter.setTextFragmenter(fragmenter); ScoreDoc[] scoreDocs = topDocs.scoreDocs; for(ScoreDoc scDoc : scoreDocs){ Document document = indexSearcher.doc(scDoc.doc); Integer id = Integer.parseInt(document.get("id")); String name = document.get("name"); String function = document.get("function"); //float score = scDoc.score; //類似度 String lighterName = highlighter.getBestFragment(analyzer, "name", name); if(null==lighterName){ lighterName = name; } String lighterFunciton = highlighter.getBestFragment(analyzer, "function", function); if(null==lighterFunciton){ lighterFunciton = function; } Medicine medicine = new Medicine(); medicine.setId(id); medicine.setName(lighterName); medicine.setFunction(lighterFunciton); result.add(medicine); } } catch (Exception e) { e.printStackTrace(); }finally{ try { indexSearcher.close(); } catch (IOException e) { e.printStackTrace(); } } return result; } public static void main(String[] args) { LuceneIKUtil luceneProcess = new LuenceIKUtil("F:/index"); try { luceneProcess.createIndex(); } catch (Exception e) { e.printStackTrace(); } //修改測試 luceneProcess.update(2, "測試內容", "修改測試。。。"); //查詢測試 String [] fields = {"name","function"}; List<Medicine> list = luenceProcess.search(fields,"感冒"); for(int i=0; i<list.size(); i++){ Medicine medicine = list.get(i); System.out.println("("+medicine.getId()+")"+medicine.getName() + "\t" + medicine.getFunction()); } //刪除測試 //luenceProcess.delete(1); } }
程序運行結果
加載擴展詞典:/dicdata/use.dic.dic 加載擴展詞典:/dicdata/googlepy.dic 加載擴展中止詞典:/dicdata/ext_stopword.dic 共檢索出 4 條記錄 (1)銀花 <font color='red'>感冒</font>顆粒 功能主治:銀花<font color='red'>感冒</font>顆粒 ,頭痛,清熱,解表,利咽。 (4)<font color='red'>感冒</font>靈膠囊 功能主治:銀花<font color='red'>感冒</font>顆粒 ,頭痛,清熱,解表,利咽。 (3)<font color='red'>感冒</font>靈顆粒 功能主治:解熱鎮痛。頭痛 ,清熱。 (5)仁和 <font color='red'>感冒</font>顆粒 功能主治:疏風清熱,宣肺止咳,解表清熱,止咳化痰。
如何判斷索引是否存在
/** * 判斷是否已經存在索引文件 * @param indexPath * @return */ private boolean isExistIndexFile(String indexPath) throws Exception{ File file = new File(indexPath); if (!file.exists()) { file.mkdirs(); } String indexSufix="/segments.gen"; //根據索引文件segments.gen是否存在判斷是不是第一次建立索引 File indexFile=new File(indexPath+indexSufix); return indexFile.exists(); }
附錄: IK分詞處理過程
IK的整個分詞處理過程首先,介紹一下IK的整個分詞處理過程:
1. Lucene的分詞基類是Analyzer,因此IK提供了Analyzer的一個實現類IKAnalyzer。首先,咱們要實例化一個IKAnalyzer,它有一個構造方法接收一個參數isMaxWordLength,這個參數是標識IK是否採用最大詞長分詞,仍是採用最細粒度切分兩種分詞算法。實際兩種算法的實現,最大詞長切分是對最細粒度切分的一種後續處理,是對最細粒度切分結果的過濾,選擇出最長的分詞結果。
2. IKAnalyzer類重寫了Analyzer的tokenStream方法,這個方法接收兩個參數,field name和輸入流reader,其中filed name是Lucene的屬性列,是對文本內容進行過度詞處理和建立索引以後,索引對應的一個名稱,相似數據庫的列名。由於IK僅僅涉及分詞處理,因此對field name沒有進行任何處理,因此此處不作任何討論。
3. tokenStream方法在Lucene對文本輸入流reader進行分詞處理時被調用,在IKAnalyzer的tokenStream方法裏面僅僅實例化了一個IKTokenizer類,該類繼承了Lucene的Tokenizer類。並重寫了incrementToken方法,該方法的做用是處理文本輸入流生成token,也就是Lucene的最小詞元term,在IK裏面叫作Lexeme。
4. 在IKtokenizer的構造方法裏面實例化了IK裏面最終要的分詞類IKSegmentation,也稱爲主分詞器。它的構造方法接收兩個參數,reader和isMaxWordLength。
5. IKsegmentation的構造方法裏面,主要作了三個工做,建立上下文對象Context,加載詞典,建立子分詞器。
6. Contex主要是存儲分詞結果集和記錄分詞處理的遊標位置。
7. 詞典是做爲一個單例被建立的,主要有量詞詞典、主詞典和停詞詞典。詞典是被存儲在字典片斷類DictSegment 這個字典核心類裏面的。DictSegment有一個靜態的存儲結構charMap,是公共詞典表,用來存儲全部漢字,key和value都是一箇中文漢字,目前IK裏面的charMap大概有7100多的鍵值對。另外,DictSegment還有兩個最重要的數據結構,是用來存儲字典樹的,一個是DictSegment的數組childrenArray,另外一個是key爲單個漢字(每一個詞條的第一個漢字),value是DictSegment的HashMap childrenMap。這兩個數據結構兩者取其一,用來存儲字典樹。
8. 子分詞器纔是真正的分詞類,IK裏面有三個子分詞器,量詞分詞器,CJK分詞器(處理中文),停詞分詞器。主分詞器IKSegmentation遍歷這三個分詞器對文本輸入流進行分詞處理。
9. IKTokenizer的incrementToken方法調用了IKSegmentation的next方法,next的做用是得到下一個分詞結果。next在第一次被調用的時候,須要加載文本輸入流,並將其讀入buffer,此時便遍歷子分詞器,對buffer種的文本內容進行分詞處理,而後把分詞結果添加到context的lexemeSet中。