分享

Lucene使用IKAnalyzer分词实例 及 IKAnalyzer扩展词库

本帖最后由 howtodown 于 2015-5-6 01:10 编辑

问题导读

1.IK分词器是否可以通过配置文件扩充词典?
2.IK分词器扩充词典有几种方式?
3.IK分词处理包含哪些过程?







方案一: 基于配置的词典扩充
项目结构图如下:
1.x.png

IK分词器还支持通过配置IKAnalyzer.cfg.xml文件来扩充您的专有词典。谷歌拼音词库下载: http://ishare.iask.sina.com.cn/f/14446921.html?from=like
在web项目的src目录下创建IKAnalyzer.cfg.xml文件,内容如下
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">  
  3. <properties>  
  4.     <comment>IK Analyzer 扩展配置</comment>
  5.     <!-- 用户可以在这里配置自己的扩展字典 -->
  6.      <entry key="ext_dict">/dicdata/use.dic.dic;/dicdata/googlepy.dic</entry>
  7.      <!-- 用户可以在这里配置自己的扩展停止词字典    -->
  8.     <entry key="ext_stopwords">/dicdata/ext_stopword.dic</entry>
  9. </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
  1. org.wltea.analyzer.cfg下Configuration接口中的定义
  2.   getExtDictionarys()  获取扩展字典配置路径
  3.   getExtStopWordDictionarys() 获取扩展停止词典配置路径
  4.   getMainDictionary() 获取主词典路径
  5.   getQuantifierDicionary() 获取量词词典路径
  6. org.wltea.analyzer.cfg.DefualtConfig类是对Configuration接口的实现
复制代码

org.wltea.analyzer.dic下的Directory类中相关的方法

  1. public void addWords(java.util.Collection<java.lang.String> words)     批量加载新词条    参数:words - Collection词条列表
  2. public void disableWords(java.util.Collection<java.lang.String> words) 批量移除(屏蔽)词条
复制代码

Lucene中使用IKAnalyzer分词器实例演示
业务实体
  1. package com.icrate.service.study.demo;
  2. /**
  3. *
  4. *
  5. *  @version : 1.0
  6. *  
  7. *  @author  : 苏若年              <a href="mailto:DennisIT@163.com">发送邮件</a>
  8. *   
  9. *  @since   : 1.0        创建时间:    2013-4-7    下午01:52:49
  10. *     
  11. *  @function: TODO        
  12. *
  13. */
  14. public class Medicine {
  15.     private Integer id;
  16.     private String name;
  17.     private String function;
  18.    
  19.    
  20.     public Medicine() {
  21.         
  22.     }
  23.    
  24.    
  25.     public Medicine(Integer id, String name, String function) {
  26.         super();
  27.         this.id = id;
  28.         this.name = name;
  29.         this.function = function;
  30.     }
  31.     //getter and setter()   
  32.     public String toString(){
  33.         return this.id + "," +this.name + "," + this.function;
  34.     }
  35. }
复制代码

构建模拟数据
  1. package com.icrate.service.study.demo;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. /**
  5. *
  6. *
  7. *  @version : 1.0
  8. *  
  9. *  @author  : 苏若年              <a href="mailto:DennisIT@163.com">发送邮件</a>
  10. *   
  11. *  @since   : 1.0        创建时间:    2013-4-7    下午01:54:34
  12. *     
  13. *  @function: TODO        
  14. *
  15. */
  16. public class DataFactory {
  17.    
  18.     private static DataFactory dataFactory = new DataFactory();
  19.    
  20.     private DataFactory(){
  21.         
  22.     }
  23.    
  24.     public List<Medicine> getData(){
  25.         List<Medicine> list = new ArrayList<Medicine>();
  26.         list.add(new Medicine(1,"银花 感冒颗粒","功能主治:银花感冒颗粒 ,头痛,清热,解表,利咽。"));
  27.         list.add(new Medicine(2,"感冒 止咳糖浆","功能主治:感冒止咳糖浆,解表清热,止咳化痰。"));
  28.         list.add(new Medicine(3,"感冒灵颗粒","功能主治:解热镇痛。头痛 ,清热。"));
  29.         list.add(new Medicine(4,"感冒灵胶囊","功能主治:银花感冒颗粒 ,头痛,清热,解表,利咽。"));
  30.         list.add(new Medicine(5,"仁和 感冒颗粒","功能主治:疏风清热,宣肺止咳,解表清热,止咳化痰。"));
  31.         return list;
  32.         
  33.     }
  34.    
  35.     public static DataFactory getInstance(){
  36.         return dataFactory;
  37.     }
  38. }
复制代码
使用Lucene对模拟数据进行检索
  1. package com.icrate.service.study.demo;
  2. import java.io.File;
  3. import java.io.IOException;
  4. import java.util.ArrayList;
  5. import java.util.List;
  6. import org.apache.lucene.analysis.Analyzer;
  7. import org.apache.lucene.document.Document;
  8. import org.apache.lucene.document.Field;
  9. import org.apache.lucene.index.IndexReader;
  10. import org.apache.lucene.index.IndexWriter;
  11. import org.apache.lucene.index.IndexWriterConfig;
  12. import org.apache.lucene.index.Term;
  13. import org.apache.lucene.queryParser.MultiFieldQueryParser;
  14. import org.apache.lucene.search.IndexSearcher;
  15. import org.apache.lucene.search.Query;
  16. import org.apache.lucene.search.ScoreDoc;
  17. import org.apache.lucene.search.TopDocs;
  18. import org.apache.lucene.search.highlight.Formatter;
  19. import org.apache.lucene.search.highlight.Fragmenter;
  20. import org.apache.lucene.search.highlight.Highlighter;
  21. import org.apache.lucene.search.highlight.QueryScorer;
  22. import org.apache.lucene.search.highlight.Scorer;
  23. import org.apache.lucene.search.highlight.SimpleFragmenter;
  24. import org.apache.lucene.search.highlight.SimpleHTMLFormatter;
  25. import org.apache.lucene.store.Directory;
  26. import org.apache.lucene.store.FSDirectory;
  27. import org.apache.lucene.util.Version;
  28. import org.wltea.analyzer.lucene.IKAnalyzer;
  29. /**
  30. *
  31. *  LuenceProcess.java  
  32. *
  33. *  @version : 1.1
  34. *  
  35. *  @author  : 苏若年    <a href="mailto:DennisIT@163.com">发送邮件</a>
  36. *   
  37. *  @since   : 1.0      创建时间:    Apr 3, 2013        11:48:11 AM
  38. *     
  39. *  TODO     : Luence中使用IK分词器
  40. *
  41. */
  42. public class LuceneIKUtil {
  43.    
  44.     private Directory directory ;
  45.     private Analyzer analyzer ;
  46.    
  47.     /**
  48.      * 带参数构造,参数用来指定索引文件目录
  49.      * @param indexFilePath
  50.      */
  51.     public LuceneIKUtil(String indexFilePath){
  52.         try {
  53.             directory = FSDirectory.open(new File(indexFilePath));
  54.             analyzer = new IKAnalyzer();
  55.         } catch (IOException e) {
  56.             e.printStackTrace();
  57.         }
  58.     }
  59.    
  60.     /**
  61.      * 默认构造,使用系统默认的路径作为索引
  62.      */
  63.     public LuceneIKUtil(){
  64.         this("/luence/index");
  65.     }
  66.    
  67.    
  68.     /**
  69.      * 创建索引
  70.      * Description:
  71.      * @author dennisit@163.com Apr 3, 2013
  72.      * @throws Exception
  73.      */
  74.     public void createIndex()throws Exception{
  75.         IndexWriterConfig indexWriterConfig = new IndexWriterConfig(Version.LUCENE_35,analyzer);
  76.         IndexWriter indexWriter = new IndexWriter(directory,indexWriterConfig);
  77.         indexWriter.deleteAll();
  78.         List<Medicine> list = DataFactory.getInstance().getData();
  79.         for(int i=0; i<list.size(); i++){
  80.             Medicine medicine = list.get(i);
  81.             Document document = addDocument(medicine.getId(), medicine.getName(), medicine.getFunction());
  82.             indexWriter.addDocument(document);
  83.         }
  84.         
  85.         indexWriter.close();
  86.     }
  87.    
  88.     /**
  89.      *
  90.      * Description:
  91.      * @author dennisit@163.com Apr 3, 2013
  92.      * @param id
  93.      * @param title
  94.      * @param content
  95.      * @return
  96.      */
  97.     public Document addDocument(Integer id, String name, String function){
  98.         Document doc = new Document();
  99.         //Field.Index.NO 表示不索引         
  100.         //Field.Index.ANALYZED 表示分词且索引         
  101.         //Field.Index.NOT_ANALYZED 表示不分词且索引
  102.         doc.add(new Field("id",String.valueOf(id),Field.Store.YES,Field.Index.NOT_ANALYZED));
  103.         doc.add(new Field("name",name,Field.Store.YES,Field.Index.ANALYZED));
  104.         doc.add(new Field("function",function,Field.Store.YES,Field.Index.ANALYZED));
  105.         return doc;
  106.     }
  107.    
  108.     /**
  109.      *
  110.      * Description: 更新索引
  111.      * @author dennisit@163.com Apr 3, 2013
  112.      * @param id
  113.      * @param title
  114.      * @param content
  115.      */
  116.     public void update(Integer id,String title, String content){
  117.         try {
  118.             IndexWriterConfig indexWriterConfig = new IndexWriterConfig(Version.LUCENE_35,analyzer);
  119.             IndexWriter indexWriter = new IndexWriter(directory,indexWriterConfig);
  120.             Document document = addDocument(id, title, content);
  121.             Term term = new Term("id",String.valueOf(id));
  122.             indexWriter.updateDocument(term, document);
  123.             indexWriter.close();
  124.         } catch (Exception e) {
  125.             e.printStackTrace();
  126.         }
  127.     }
  128.    
  129.     /**
  130.      *
  131.      * Description:按照ID进行索引
  132.      * @author dennisit@163.com Apr 3, 2013
  133.      * @param id
  134.      */
  135.     public void delete(Integer id){
  136.         try {
  137.             IndexWriterConfig indexWriterConfig = new IndexWriterConfig(Version.LUCENE_35,analyzer);
  138.             IndexWriter indexWriter = new IndexWriter(directory,indexWriterConfig);
  139.             Term term = new Term("id",String.valueOf(id));
  140.             indexWriter.deleteDocuments(term);
  141.             indexWriter.close();
  142.         } catch (Exception e) {
  143.             e.printStackTrace();
  144.         }
  145.     }
  146.    
  147.     /**
  148.      *
  149.      * Description:查询
  150.      * @author dennisit@163.com Apr 3, 2013
  151.      * @param where 查询条件
  152.      * @param scoreDoc 分页时用
  153.      */
  154.     public List<Medicine> search(String[] fields,String keyword){
  155.         
  156.         IndexSearcher indexSearcher = null;
  157.         List<Medicine> result = new ArrayList<Medicine>();
  158.         
  159.         
  160.         try {
  161.             //创建索引搜索器,且只读
  162.             IndexReader indexReader = IndexReader.open(directory,true);
  163.             indexSearcher = new IndexSearcher(indexReader);
  164.             MultiFieldQueryParser queryParser =new MultiFieldQueryParser(Version.LUCENE_35, fields,analyzer);
  165.             Query query = queryParser.parse(keyword);
  166.             
  167.             //返回前number条记录
  168.             TopDocs topDocs = indexSearcher.search(query, 10);
  169.             //信息展示
  170.             int totalCount = topDocs.totalHits;
  171.             System.out.println("共检索出 "+totalCount+" 条记录");
  172.             
  173.             
  174.             //高亮显示
  175.             /*  
  176.                   创建高亮器,使搜索的结果高亮显示
  177.                 SimpleHTMLFormatter:用来控制你要加亮的关键字的高亮方式
  178.                 此类有2个构造方法
  179.                 1:SimpleHTMLFormatter()默认的构造方法.加亮方式:<B>关键字</B>
  180.                 2:SimpleHTMLFormatter(String preTag, String postTag).加亮方式:preTag关键字postTag
  181.              */
  182.             Formatter formatter = new SimpleHTMLFormatter("<font color='red'>","</font>");   
  183.             /*
  184.                  QueryScorer
  185.                 QueryScorer 是内置的计分器。计分器的工作首先是将片段排序。QueryScorer使用的项是从用户输入的查询中得到的;
  186.                 它会从原始输入的单词、词组和布尔查询中提取项,并且基于相应的加权因子(boost factor)给它们加权。
  187.                 为了便于QueryScoere使用,还必须对查询的原始形式进行重写。
  188.                 比如,带通配符查询、模糊查询、前缀查询以及范围查询 等,都被重写为BoolenaQuery中所使用的项。
  189.                 在将Query实例传递到QueryScorer之前,可以调用Query.rewrite (IndexReader)方法来重写Query对象
  190.              */
  191.             Scorer fragmentScorer = new QueryScorer(query);
  192.             Highlighter highlighter = new Highlighter(formatter,fragmentScorer);
  193.             Fragmenter fragmenter = new SimpleFragmenter(100);
  194.             /*   
  195.                 Highlighter利用Fragmenter将原始文本分割成多个片段。
  196.                       内置的SimpleFragmenter将原始文本分割成相同大小的片段,片段默认的大小为100个字符。这个大小是可控制的。
  197.              */
  198.             highlighter.setTextFragmenter(fragmenter);
  199.             
  200.             ScoreDoc[] scoreDocs = topDocs.scoreDocs;
  201.             
  202.             for(ScoreDoc scDoc : scoreDocs){
  203.                 Document  document = indexSearcher.doc(scDoc.doc);
  204.                 Integer id = Integer.parseInt(document.get("id"));
  205.                 String name = document.get("name");
  206.                 String function = document.get("function");
  207.                 //float score = scDoc.score; //相似度
  208.                
  209.                 String lighterName = highlighter.getBestFragment(analyzer, "name", name);
  210.                 if(null==lighterName){
  211.                     lighterName = name;
  212.                 }
  213.                
  214.                 String lighterFunciton = highlighter.getBestFragment(analyzer, "function", function);
  215.                 if(null==lighterFunciton){
  216.                     lighterFunciton = function;
  217.                 }
  218.                
  219.                 Medicine medicine = new Medicine();
  220.                
  221.                 medicine.setId(id);
  222.                 medicine.setName(lighterName);
  223.                 medicine.setFunction(lighterFunciton);
  224.                
  225.                 result.add(medicine);
  226.                             }
  227.         } catch (Exception e) {
  228.             e.printStackTrace();
  229.         }finally{
  230.             try {
  231.                 indexSearcher.close();
  232.             } catch (IOException e) {
  233.                 e.printStackTrace();
  234.             }
  235.         }
  236.    
  237.         return result;
  238.     }
  239.    
  240.     public static void main(String[] args) {
  241.         LuceneIKUtil luceneProcess = new LuenceIKUtil("F:/index");
  242.         try {
  243.             luceneProcess.createIndex();
  244.         } catch (Exception e) {
  245.             e.printStackTrace();
  246.         }
  247.         //修改测试
  248.         luceneProcess.update(2, "测试内容", "修改测试。。。");
  249.         
  250.         //查询测试
  251.         String [] fields = {"name","function"};
  252.         List<Medicine> list = luenceProcess.search(fields,"感冒");
  253.         for(int i=0; i<list.size(); i++){
  254.             Medicine medicine = list.get(i);
  255.             System.out.println("("+medicine.getId()+")"+medicine.getName() + "\t" + medicine.getFunction());
  256.         }
  257.         //删除测试
  258.         //luenceProcess.delete(1);
  259.         
  260.     }
  261. }
复制代码

程序运行结果
  1. 加载扩展词典:/dicdata/use.dic.dic
  2. 加载扩展词典:/dicdata/googlepy.dic
  3. 加载扩展停止词典:/dicdata/ext_stopword.dic
  4. 共检索出 4 条记录
  5. (1)银花 <font color='red'>感冒</font>颗粒    功能主治:银花<font color='red'>感冒</font>颗粒 ,头痛,清热,解表,利咽。
  6. (4)<font color='red'>感冒</font>灵胶囊    功能主治:银花<font color='red'>感冒</font>颗粒 ,头痛,清热,解表,利咽。
  7. (3)<font color='red'>感冒</font>灵颗粒    功能主治:解热镇痛。头痛 ,清热。
  8. (5)仁和 <font color='red'>感冒</font>颗粒    功能主治:疏风清热,宣肺止咳,解表清热,止咳化痰。
复制代码
如何判断索引是否存在
  1. /**
  2.      * 判断是否已经存在索引文件
  3.      * @param indexPath
  4.      * @return
  5.      */
  6.     private  boolean isExistIndexFile(String indexPath) throws Exception{
  7.         File file = new File(indexPath);
  8.         if (!file.exists()) {
  9.             file.mkdirs();
  10.         }
  11.         String indexSufix="/segments.gen";
  12.          //根据索引文件segments.gen是否存在判断是否是第一次创建索引   
  13.         File indexFile=new File(indexPath+indexSufix);
  14.         return indexFile.exists();
  15.     }
复制代码

附录: 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中。






http://www.cnblogs.com/dennisit/archive/2013/04/07/3005847.html]

已有(2)人评论

跳转到指定楼层
西山东 发表于 2015-5-6 09:04:21
回复

使用道具 举报

QQo0oBIQ 发表于 2015-5-6 09:16:30
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

推荐上一条 /2 下一条