Lucene 实例教程(二)之IKAnalyzer中文分词器

2024-03-16 01:32

本文主要是介绍Lucene 实例教程(二)之IKAnalyzer中文分词器,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

转自作者:永恒の_☆ 地址:http://blog.csdn.net/chenghui0317/article/details/10281311


最近研究数据库模糊查询,发现oracle数据库中虽然可以用instr来替代like提高效率,但是这个效率提高是有瓶颈的,可以用搜索引擎技术来进一步提高查询效率

一、前言

       前面简单介绍了Lucene,以及如何使用Lucene将索引 写入内存,地址:http://blog.csdn.net/chenghui0317/article/details/10052103

       但是其中出现很多问题,具体如下:

       1、使用IndexWriter 写入的索引全部是放在内存中的,一旦程序挂了 也就什么都没有了,并且如果生成的索引很大,那么很容易导致内存溢出。

       2、使用SimpleAnalyzer作为分词器,根据关键字查询的时候 只会匹配根据空格分隔的字符、字母或者数字,并且插入的索引统一变为小写,但是查询的时候没有变为小写,所以检索关键字中出现大写字母 就永远都查不出结果;

       3、之后使用StandardAnalyzer作为分词器,因为它是标准来分中文的,所以也只会对中文有分词的效果。尽管这样,仍然不能满足实际开发需求。

       在一个真实的项目中如果出现这样的情况,非得为每一个索引的词组中前后添加空格来满足查询,并且所有的关键字必须小写,显然用户体验是非常差的,根本不能满足实际开发需求,这样子的话 还不如不用Lucene 直接去模糊查询数据表记录好了。

        接下来介绍一个新的分词器:IKAnalyzer


二、IKAnalyzer的介绍

        IKAnalyzer是一个开源的,基于Java语言开发的轻量级的中文分词语言包,它是以Lucene为应用主体,结合词典分词和文法分析算法的中文词组组件。 从3.0版本开始,IK发展为面向java的公用分词组件,独立Lucene项目,同时提供了对Lucene的默认优化实现。 IKAnalyzer实现了简单的分词歧义排除算法,标志着IK分词器从单独的词典分词想模拟语义化分词衍生。

        IKAnalyzer 的新特性:

        1、.采用了特有的“正向迭代最细粒度切分算法“,支持细粒度和智能分词两种切分模式;  

        2、在系统环境:Core2 i7 3.4G双核,4G内存,window 7 64位, Sun JDK 1.6_29 64位 普通pc环境测试,IK2012具有160万字/秒(3000KB/S)的高速处理能力。 

        3、2012版本的智能分词模式支持简单的分词排歧义处理和数量词合并输出。  

        4、采用了多子处理器分析模式,支持:英文字母、数字、中文词汇等分词处理,兼容韩文、日文字符  

        5、优化的词典存储,更小的内存占用。支持用户词典扩展定义。特别的,在2012版本,词典支持中文,英文,数字混合词语。


三、IKAnalyzer的准备条件

    IKAnalyzer3.2.5table.jar

下载地址:http://download.csdn.net/detail/ch656409110/5971413


四、使用Lucene实战


1、使用Lucene将索引 写入磁盘,IKAnalyzer作为分词器检索索引文件

实现的思路如下:

   <1> 原先使用的是内存目录对象RAMDirectory 对象,Lucene同时还提供了磁盘目录对象SimpleFSDirectory对象,至于索引写入器IndexWriter 还是和以前一样 ;

   <2>利用索引写入器将指定的数据存入磁盘目录对象中;

   <3>创建IndexSearch 索引查询对象,然后根据关键字封装Query查询对象;

   <4>调用search()方法,将查询的结果返回给TopDocs ,迭代里面所有的Document对象,显示查询结果;

   <5>关闭IndexWriter ,关闭directory目录对象。
具体代码如下:

[java]  view plain copy
print ?
  1. package com.lucene.test;  
  2.   
  3. import java.io.File;  
  4. import java.io.IOException;  
  5. import java.io.StringReader;  
  6. import java.util.ArrayList;  
  7. import java.util.List;  
  8.   
  9. import org.apache.lucene.analysis.Analyzer;  
  10. import org.apache.lucene.analysis.SimpleAnalyzer;  
  11. import org.apache.lucene.analysis.TokenStream;  
  12. import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;  
  13. import org.apache.lucene.document.Document;  
  14. import org.apache.lucene.document.Field;  
  15. import org.apache.lucene.index.CorruptIndexException;  
  16. import org.apache.lucene.index.IndexReader;  
  17. import org.apache.lucene.index.IndexWriter;  
  18. import org.apache.lucene.index.IndexWriterConfig;  
  19. import org.apache.lucene.queryParser.MultiFieldQueryParser;  
  20. import org.apache.lucene.queryParser.ParseException;  
  21. import org.apache.lucene.queryParser.QueryParser;  
  22. import org.apache.lucene.search.IndexSearcher;  
  23. import org.apache.lucene.search.Query;  
  24. import org.apache.lucene.search.ScoreDoc;  
  25. import org.apache.lucene.search.Sort;  
  26. import org.apache.lucene.search.SortField;  
  27. import org.apache.lucene.search.TopDocs;  
  28. import org.apache.lucene.search.highlight.Highlighter;  
  29. import org.apache.lucene.search.highlight.InvalidTokenOffsetsException;  
  30. import org.apache.lucene.search.highlight.QueryScorer;  
  31. import org.apache.lucene.search.highlight.SimpleFragmenter;  
  32. import org.apache.lucene.search.highlight.SimpleHTMLFormatter;  
  33. import org.apache.lucene.store.Directory;  
  34. import org.apache.lucene.store.FSDirectory;  
  35. import org.apache.lucene.store.SimpleFSDirectory;  
  36. import org.apache.lucene.util.Version;  
  37. import org.wltea.analyzer.lucene.IKAnalyzer;  
  38.   
  39. import com.lucene.entity.Article;  
  40.   
  41. public class SimpleFSDirectoryDemo {  
  42.     /* 创建简单中文分析器 创建索引使用的分词器必须和查询时候使用的分词器一样,否则查询不到想要的结果 */  
  43.     private Analyzer analyzer = new IKAnalyzer(true);  
  44.     // 索引保存目录  
  45.     private File indexFile = new File("./indexDir/");  
  46.   
  47.     /** 
  48.      * 创建索引文件到磁盘中永久保存 
  49.      */  
  50.     public void createIndexFile() {  
  51.         long startTime = System.currentTimeMillis();  
  52.         System.out.println("*****************创建索引开始**********************");  
  53.         Directory directory = null;  
  54.         IndexWriter indexWriter = null;  
  55.         try {  
  56.             // 创建哪个版本的IndexWriterConfig,根据参数可知lucene是向下兼容的,选择对应的版本就好  
  57.             IndexWriterConfig indexWriterConfig = new IndexWriterConfig(Version.LUCENE_36, analyzer);  
  58.             // 创建磁盘目录对象  
  59.             directory = new SimpleFSDirectory(indexFile);  
  60.             indexWriter = new IndexWriter(directory, indexWriterConfig);  
  61.             // indexWriter = new IndexWriter(directory, analyzer, true,IndexWriter.MaxFieldLength.UNLIMITED);  
  62.             // 这上面是使用内存保存索引的创建索引写入对象的例子,和这里的实现方式不一样,但是效果是一样的  
  63.   
  64.             Article article0 = new Article(1"Simple Analyzer","这个分词是一段一段话进行分 ");  
  65.             Article article1 = new Article(2"Standard Analyzer","标准分词拿来分中文和ChineseAnalyzer一样的效果");  
  66.             Article article2 = new Article(3"PerField AnalyzerWrapper","这个很有意思,可以封装很多分词方式,还可以于先设置field用那个分词分!牛 ");  
  67.             Article article3 = new Article(4"CJK Analyzer","这个分词方式是正向退一分词(二分法分词),同一个字会和它的左边和右边组合成一个次,每个人出现两次,除了首字和末字 ");  
  68.             Article article4 = new Article(5"Chinese Analyzer","这个是专业的中文分词器,一个一个字分 ");  
  69.             Article article5 = new Article(6" BrazilianAnalyzer""巴西语言分词 ");  
  70.             Article article6 = new Article(7" CzechAnalyzer""捷克语言分词 ");  
  71.             Article article7 = new Article(8"DutchAnalyzer""荷兰语言分词 ");  
  72.             Article article8 = new Article(9"FrenchAnalyzer""法国语言分词 ");  
  73.             Article article9 = new Article(10"沪K123""这是一个车牌号,包含中文,字母,数字");  
  74.             Article article10 = new Article(11"沪K345""上海~!@~!@");  
  75.             Article article11 = new Article(12"沪B678""京津沪");  
  76.             Article article12 = new Article(13"沪A3424""沪K345 沪K3 沪K123 沪K111111111 沪ABC");  
  77.             Article article13 = new Article(14"沪 B2222""");  
  78.             Article article14 = new Article(15"沪K3454653""沪K345");  
  79.             Article article15 = new Article(16"123 123 1 2 23 3""沪K123");  
  80.             List<Article> articleList = new ArrayList<Article>();  
  81.             articleList.add(article0);  
  82.             articleList.add(article1);  
  83.             articleList.add(article2);  
  84.             articleList.add(article3);  
  85.             articleList.add(article4);  
  86.             articleList.add(article5);  
  87.             articleList.add(article6);  
  88.             articleList.add(article7);  
  89.             articleList.add(article8);  
  90.             articleList.add(article9);  
  91.             articleList.add(article10);  
  92.             articleList.add(article11);  
  93.             articleList.add(article12);  
  94.             articleList.add(article13);  
  95.             articleList.add(article14);  
  96.             articleList.add(article15);  
  97.             // 为了避免重复插入数据,每次测试前 先删除之前的索引  
  98.             indexWriter.deleteAll();  
  99.             // 获取实体对象  
  100.             for (int i = 0; i < articleList.size(); i++) {  
  101.                 Article article = articleList.get(i);  
  102.                 // indexWriter添加索引  
  103.                 Document doc = new Document();  
  104.                 doc.add(new Field("id", article.getId().toString(),Field.Store.YES, Field.Index.NOT_ANALYZED));  
  105.                 doc.add(new Field("title", article.getTitle().toString(),Field.Store.YES, Field.Index.ANALYZED));  
  106.                 doc.add(new Field("content", article.getContent().toString(),Field.Store.YES, Field.Index.ANALYZED));  
  107.                 // 添加到索引中去  
  108.                 indexWriter.addDocument(doc);  
  109.                 System.out.println("索引添加成功:第" + (i + 1) + "次!!");  
  110.             }  
  111.         } catch (IOException e) {  
  112.             e.printStackTrace();  
  113.         } finally {  
  114.             if (indexWriter != null) {  
  115.                 try {  
  116.                     indexWriter.close();  
  117.                 } catch (IOException e) {  
  118.                     e.printStackTrace();  
  119.                 }  
  120.             }  
  121.             if (directory != null) {  
  122.                 try {  
  123.                     directory.close();  
  124.                 } catch (IOException e) {  
  125.                     e.printStackTrace();  
  126.                 }  
  127.             }  
  128.         }  
  129.         long endTime = System.currentTimeMillis();  
  130.         System.out.println("创建索引文件成功,总共花费" + (endTime - startTime) + "毫秒。");  
  131.         System.out.println("*****************创建索引结束**********************");  
  132.     }  
  133.   
  134.     /** 
  135.      * 直接读取索引文件,查询索引记录 
  136.      *  
  137.      * @throws IOException 
  138.      */  
  139.     public void openIndexFile() {  
  140.         long startTime = System.currentTimeMillis();  
  141.         System.out.println("*****************读取索引开始**********************");  
  142.         List<Article> articles = new ArrayList<Article>();  
  143.         // 得到索引的目录  
  144.         Directory directory = null;  
  145.         IndexReader indexReader = null;  
  146.         try {  
  147.             directory = new SimpleFSDirectory(indexFile);  
  148.             // 根据目录打开一个indexReader  
  149.             indexReader = IndexReader.open(directory);  
  150.             //indexReader = IndexReader.open(directory,false);  
  151.             System.out.println("在索引文件中总共插入了" + indexReader.maxDoc() + "条记录。");  
  152.             // 获取第一个插入的document对象  
  153.             Document minDoc = indexReader.document(0);  
  154.             // 获取最后一个插入的document对象  
  155.             Document maxDoc = indexReader.document(indexReader.maxDoc() - 1);  
  156.             // document对象的get(字段名称)方法获取字段的值  
  157.             System.out.println("第一个插入的document对象的标题是:" + minDoc.get("title"));  
  158.             System.out.println("最后一个插入的document对象的标题是:" + maxDoc.get("title"));  
  159.             //indexReader.deleteDocument(0);  
  160.             int docLength = indexReader.maxDoc();  
  161.             for (int i = 0; i < docLength; i++) {  
  162.                 Document doc = indexReader.document(i);  
  163.                 Article article = new Article();  
  164.                 if (doc.get("id") == null) {  
  165.                     System.out.println("id为空");  
  166.                 } else {  
  167.                     article.setId(Integer.parseInt(doc.get("id")));  
  168.                     article.setTitle(doc.get("title"));  
  169.                     article.setContent(doc.get("content"));  
  170.                     articles.add(article);  
  171.                 }  
  172.             }  
  173.             System.out.println("显示所有插入的索引记录:");  
  174.             for (Article article : articles) {  
  175.                 System.out.println(article);  
  176.             }  
  177.         } catch (IOException e) {  
  178.             e.printStackTrace();  
  179.         } finally {  
  180.             if (indexReader != null) {  
  181.                 try {  
  182.                     indexReader.close();  
  183.                 } catch (IOException e) {  
  184.                     e.printStackTrace();  
  185.                 }  
  186.             }  
  187.             if (directory != null) {  
  188.                 try {  
  189.                     directory.close();  
  190.                 } catch (IOException e) {  
  191.                     e.printStackTrace();  
  192.                 }  
  193.             }  
  194.         }  
  195.         long endTime = System.currentTimeMillis();  
  196.         System.out.println("直接读取索引文件成功,总共花费" + (endTime - startTime) + "毫秒。");  
  197.         System.out.println("*****************读取索引结束**********************");  
  198.     }  
  199.   
  200.     /** 
  201.      * 查看IKAnalyzer 分词器是如何将一个完整的词组进行分词的 
  202.      *  
  203.      * @param text 
  204.      * @param isMaxWordLength 
  205.      */  
  206.     public void splitWord(String text, boolean isMaxWordLength) {  
  207.         try {  
  208.             // 创建分词对象  
  209.             Analyzer analyzer = new IKAnalyzer(isMaxWordLength);  
  210.             StringReader reader = new StringReader(text);  
  211.             // 分词  
  212.             TokenStream ts = analyzer.tokenStream("", reader);  
  213.             CharTermAttribute term = ts.getAttribute(CharTermAttribute.class);  
  214.             // 遍历分词数据  
  215.             System.out.print("IKAnalyzer把关键字拆分的结果是:");  
  216.             while (ts.incrementToken()) {  
  217.                 System.out.print("【" + term.toString() + "】");  
  218.             }  
  219.             reader.close();  
  220.         } catch (IOException e) {  
  221.             e.printStackTrace();  
  222.         }  
  223.         System.out.println();  
  224.     }  
  225.   
  226.     /** 
  227.      * 根据关键字实现全文检索 
  228.      */  
  229.     public void searchIndexFile(String keyword) {  
  230.         long startTime = System.currentTimeMillis();  
  231.         System.out.println("*****************查询索引开始**********************");  
  232.         IndexReader indexReader = null;  
  233.         IndexSearcher indexSearcher = null;  
  234.         List<Article> articleList = new ArrayList<Article>();  
  235.         try {  
  236.             indexReader = IndexReader.open(FSDirectory.open(indexFile));  
  237.             // 创建一个排序对象,其中SortField构造方法中,第一个是排序的字段,第二个是指定字段的类型,第三个是是否升序排列,true:升序,false:降序。  
  238.             Sort sort = new Sort(new SortField[] {new SortField("title", SortField.STRING, false),new SortField("content", SortField.STRING, false) });  
  239.             //Sort sort = new Sort();  
  240.             // 创建搜索类  
  241.             indexSearcher = new IndexSearcher(indexReader);  
  242.             // 下面是创建QueryParser 查询解析器  
  243.             // QueryParser支持单个字段的查询,但是MultiFieldQueryParser可以支持多个字段查询,建议用后者这样可以实现全文检索的功能。  
  244.             // QueryParser queryParser = new QueryParser(Version.LUCENE_36, "title", analyzer);  
  245.             QueryParser queryParser = new MultiFieldQueryParser(Version.LUCENE_36, new String[] { "title""content" },analyzer);  
  246.             // 利用queryParser解析传递过来的检索关键字,完成Query对象的封装  
  247.             Query query = queryParser.parse(keyword);  
  248.             splitWord(keyword, true); // 显示拆分结果  
  249.             // 执行检索操作  
  250.             TopDocs topDocs = indexSearcher.search(query, 5, sort);  
  251.             System.out.println("一共查到:" + topDocs.totalHits + "记录");  
  252.             ScoreDoc[] scoreDoc = topDocs.scoreDocs;  
  253.             // 像百度,谷歌检索出来的关键字如果有,除了显示在列表中之外还会高亮显示。Lucenen也支持高亮功能,正常应该是<font color='red'></font>这里用【】替代,使效果更加明显  
  254.             SimpleHTMLFormatter simpleHtmlFormatter = new SimpleHTMLFormatter("【""】");  
  255.             // 具体怎么实现的不用管,直接拿来用就好了。  
  256.             Highlighter highlighter = new Highlighter(simpleHtmlFormatter,new QueryScorer(query));  
  257.   
  258.             for (int i = 0; i < scoreDoc.length; i++) {  
  259.                 // 内部编号 ,和数据库表中的唯一标识列一样  
  260.                 int doc = scoreDoc[i].doc;  
  261.                 // 根据文档id找到文档  
  262.                 Document mydoc = indexSearcher.doc(doc);  
  263.   
  264.                 String id = mydoc.get("id");  
  265.                 String title = mydoc.get("title");  
  266.                 String content = mydoc.get("content");  
  267.                 TokenStream tokenStream = null;  
  268.                 if (title != null && !title.equals("")) {  
  269.                     tokenStream = analyzer.tokenStream("title",new StringReader(title));  
  270.                     title = highlighter.getBestFragment(tokenStream, title);  
  271.                 }  
  272.                 if (content != null && !content.equals("")) {  
  273.                     tokenStream = analyzer.tokenStream("content",new StringReader(content));  
  274.                     // 传递的长度表示检索之后匹配长度,这个会导致返回的内容不全  
  275.                     //highlighter.setTextFragmenter(new SimpleFragmenter(content.length()));   
  276.                     content = highlighter.getBestFragment(tokenStream, content);  
  277.                 }  
  278.                 // 需要注意的是 如果使用了高亮显示的操作,查询的字段中没有需要高亮显示的内容 highlighter会返回一个null回来。  
  279.                 articleList.add(new Article(Integer.valueOf(id),title == null ? mydoc.get("title") : title,content == null ? mydoc.get("content") : content));  
  280.             }  
  281.         } catch (CorruptIndexException e) {  
  282.             e.printStackTrace();  
  283.         } catch (IOException e) {  
  284.             e.printStackTrace();  
  285.         } catch (InvalidTokenOffsetsException e) {  
  286.             e.printStackTrace();  
  287.         } catch (ParseException e) {  
  288.             e.printStackTrace();  
  289.         } finally {  
  290.             if (indexSearcher != null) {  
  291.                 try {  
  292.                     indexSearcher.close();  
  293.                 } catch (IOException e1) {  
  294.                     e1.printStackTrace();  
  295.                 }  
  296.             }  
  297.             if (indexReader != null) {  
  298.                 try {  
  299.                     indexReader.close();  
  300.                 } catch (IOException e) {  
  301.                     e.printStackTrace();  
  302.                 }  
  303.             }  
  304.         }  
  305.         System.out.println("根据关键字" + keyword + "检索到的结果如下:");  
  306.         for (Article article : articleList) {  
  307.             System.out.println(article);  
  308.         }  
  309.         long endTime = System.currentTimeMillis();  
  310.         System.out.println("全文索引文件成功,总共花费" + (endTime - startTime) + "毫秒。");  
  311.         System.out.println("*****************查询索引结束**********************");  
  312.     }  
  313.   
  314.     public static void main(String[] args) {  
  315.         SimpleFSDirectoryDemo luceneInstance = new SimpleFSDirectoryDemo();  
  316.         // 建立要索引的文件  
  317.         luceneInstance.createIndexFile();  
  318.         // 从索引文件中查询数据  
  319.         // luceneInstance.openIndexFile();  
  320.         // 查看IKAnalyzer分词结果  
  321.         /* 
  322.          * String[] keywords = new 
  323.          * String[]{"IKAnalyzer是一个基于java语言开发的轻量级的中文分词工具包" 
  324.          * ,"我正在学习Lucene3.6,看一下效果如何" 
  325.          * ,"鄂尔多斯"," Java做服务器端时如何接收和处理android客户端base64编码过的图片呢?"}; 
  326.          * luceneInstance.splitWord(keywords[0], true); 
  327.          * luceneInstance.splitWord(keywords[0], false); 
  328.          * luceneInstance.splitWord(keywords[1], true); 
  329.          * luceneInstance.splitWord(keywords[1], false); 
  330.          * luceneInstance.splitWord(keywords[2], true); 
  331.          * luceneInstance.splitWord(keywords[2], false); 
  332.          * luceneInstance.splitWord(keywords[3], true); 
  333.          * luceneInstance.splitWord(keywords[3], false); 
  334.          */  
  335.         // 获得结果,然后交由相关应用程序处理  
  336.         String[] searchKeywords = new String[]{"analyzer","沪B123","沪K123","沪K123 上海","沪K3454653"};  
  337.         luceneInstance.searchIndexFile(searchKeywords[1]);  
  338.     }  
  339. }  

上面代码关联的实体类代码如下:

[java]  view plain copy
print ?
  1. package com.lucene.entity;  
  2.   
  3. public class Article {  
  4.   
  5.     private Integer id;  
  6.     private String title;  
  7.     private String content;  
  8.       
  9.     public Article() {  
  10.         super();  
  11.     }  
  12.     public Article(Integer id, String title, String content) {  
  13.         super();  
  14.         this.id = id;  
  15.         this.title = title;  
  16.         this.content = content;  
  17.     }  
  18.     public synchronized Integer getId() {  
  19.         return id;  
  20.     }  
  21.     public synchronized void setId(Integer id) {  
  22.         this.id = id;  
  23.     }  
  24.     public synchronized String getTitle() {  
  25.         return title;  
  26.     }  
  27.     public synchronized void setTitle(String title) {  
  28.         this.title = title;  
  29.     }  
  30.     public synchronized String getContent() {  
  31.         return content;  
  32.     }  
  33.     public synchronized void setContent(String content) {  
  34.         this.content = content;  
  35.     }  
  36.     @Override  
  37.     public String toString() {  
  38.         return "Article [id=" + id + ", title=" + title + ", content=" + content + "]";  
  39.     }  
  40.       
  41.       
  42. }  

代码有点长,其中包括了如何将索引写入磁盘,以及IKAnalyzer 分词是如何将检索关键字进行分词的,然后如何实现全文检索操作的。

首先看看createIndexFile()这个方法,该方法用于创建索引,将索引写入磁盘,执行该方法的效果如下:


根据提示索引添加成功,然后去当前项目下的indexDir目录中查看索引文件,具体截图如下:


截图中展示全是非文本文件,所以看不到具体存储的什么,但是Lucene知道,我们只需要知道如何利用它去完成自己想要的功能即可。

需要注意的是:

    1、如果添加一次索引,那么该目录的大多文件都会发生改变,如果添加索引的时候发现文件的修改时间没有改变 肯定没有添加成功;

    2、如果在创建索引的时候使用的是IKAnalyzer分词器,那么查询的索引的时候同样也要使用IkAnalyzer分词器,否则查询不到结果。


然后,索引创建成功之后,接下来看看openIndexFile()这个方法,它使用的是IndexWriter直接读取索引文件,查询索引记录。

执行该方法的具体效果如下:


从截图中看出,Lucene添加的索引全是按照索引下标一个一个按照添加顺序添加进去的, 直接根据这个下标就可以返回对应的Document对象。    另外操作IndexReader 获取的Document对象 还蛮简单的,跟Arraylist有点相似,只不过方法名称不一样。

为了验证 是否和Arraylist一样,删除其中的索引之后,后面的下标是否会向前移动。修改代码:

indexReader 的是否只读属性改为false,默认是true,如果不改为false 删除索引会报错:

Exception in thread "main" java.lang.UnsupportedOperationException: This IndexReader cannot make any changes to the index (it was opened with readOnly = true)


具体修改如下: indexReader = IndexReader.open(directory,false);

然后添加 删除索引的代码:indexReader.deleteDocument(0);

实践发现,该方法没有真正删除索引, 我在重新调用openIndexFile()方法一样返回所有的记录, 但是indexDir目录中确实有索引文件被修改的痕迹, 并且如果使用IndexSearch调用search()方法确检索不到,可见indexReader.deleteDocument(0);没有真正删除索引,只不过在使用IndexSearch检索的时候检索不到罢了。

后来发现:使用IndexReader进行Document删除操作时,文档并不会立即被删除,而是把这个删除动作缓存起来,直到调用IndexReader.Close()时,删除操作才会被真正执行。


然后再看下IKAnalyzer分词器到底有什么效果,为什么这么多特性,它是如何实现分词效果的,运行splitWord()方法,具体效果如下:


由截图可见:

    1、IKAnalyzer分词器在分词的时候会把传递过来的字母统一转换成小写,这样子非常有效的避免的添加索引的时候全部小写 而导致大小写不一样检索不到的情况;

     2、IKAnalyzer拆分关键字分两种,分别是“最细粒度切分算法”和“智能切分算法”,分别对应的值是false 和 true ,所以如果不想切分的太细小化就传递true,默认值是false;

源代码:

[java]  view plain copy
print ?
  1. public IKAnalyzer(boolean isMaxWordLength)  
  2. {  
  3.     this.isMaxWordLength = false;  
  4.     setMaxWordLength(isMaxWordLength);  
  5. }  

多尝试几次就知道它的具体好处了。


现在对IKAnalyzer分词已经有所了解了,接下来看看searchIndexFile() 如何实现全文检索的。

传递关键字“沪B123”,执行一下searchIndexFile()方法,具体效果如下图所示:


从截图中看到 共有7条记录,但是只显示了5条,是因为传递的nDocs参数限制了返回的结果数。

但是显示的 content内容只有高亮的部分,其他全部被截去了。


如果把highlighter.setTextFragmenter(new SimpleFragmenter(content.length())); 这行代码注释掉就ok了,具体效果如下:


现在可以看到完整的内容了,但是明明第二条的信息匹配度要高于第一条,却放在了第二行显示,这是因为 传递的sort对象 先按title排序,然后再按内容排序,要达到最有匹配的效果,索性传递一个空的sort 即可,具体效果如下:


图中最后一条记录中出现的“沪”共有6次,"123"也出现了1次,却被排在了最后一行,费解。。。可以看做是当中一个bug, 具体原因还需要慢慢咀嚼。也许是IKAnalyzer中不完善导致的。


另外如果传递的参数是“analyzer”,具体效果如下:


在截图中与插入的数据作对比,发现如果录入索引的内容中的字母以“analyzer”单独为一个词组才可以查询到,就是“analyzer”这个单词的前后有空格,否则查询不到, 但是如果和中文与数字挨在一起却没问题,在上一个例子中可以看到效果。


另外需要说明的是:

如果使用的是SimpleAnalyzer或者StandardAnalyzer作为分词器的话,检索的关键字如果有特殊字符 比如:" \  } 等等,会报错,具体如下:

org.apache.lucene.queryParser.ParseException: Cannot parse '沪B123)': Encountered " ")" ") "" at line 1, column 5.

但是使用IKAnalyzer没有问题,因为它会直接把这些字符过滤掉,不作为检索的条件。


反复多尝试几次,得出如下结论:

<1> 如果录入的索引为字母必须和中文或者数字挨在一起,后者空格隔开分为一个词组 才能查询到,否则IKAnalyzer 会认为是一个整体,不会分词。简而言之,字母与字母挨在一起 会被当做一个完整的词组,数字和数字挨在一起也会被当做一个完整的词组,只有完全匹配才会被检索出来;

<2>如果在检索的时候发现排序不会,最有匹配的并没有放在最上面,这是由于sort排序导致的,它会根据字段的先后顺序和指定的是否升序 来重新排序,多多少少会对实际的效果产生影响。


所以 使用IKAnalyzer 这个中文分词器之后 较以前的检索能力大大的提高了,最优匹配度也比之前好多了,从而提高了用户的体验度。。

这篇关于Lucene 实例教程(二)之IKAnalyzer中文分词器的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/813902

相关文章

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

vscode中文乱码问题,注释,终端,调试乱码一劳永逸版

忘记咋回事突然出现了乱码问题,很多方法都试了,注释乱码解决了,终端又乱码,调试窗口也乱码,最后经过本人不懈努力,终于全部解决了,现在分享给大家我的方法。 乱码的原因是各个地方用的编码格式不统一,所以把他们设成统一的utf8. 1.电脑的编码格式 开始-设置-时间和语言-语言和区域 管理语言设置-更改系统区域设置-勾选Bata版:使用utf8-确定-然后按指示重启 2.vscode

解决Office Word不能切换中文输入

我们在使用WORD的时可能会经常碰到WORD中无法输入中文的情况。因为,虽然我们安装了搜狗输入法,但是到我们在WORD中使用搜狗的输入法的切换中英文的按键的时候会发现根本没有效果,无法将输入法切换成中文的。下面我就介绍一下如何在WORD中把搜狗输入法切换到中文。

sqlite不支持中文排序,采用java排序

方式一 不支持含有重复字段进行排序 /*** sqlite不支持中文排序,改用java排序* 根据指定的对象属性字段,排序对象集合,顺序* @param list* @param field* @return*/public static List sortListByField(List<?> list,String field){List temp = new ArrayList(

彻底解决win10系统Tomcat10控制台输出中文乱码

彻底解决Tomcat10控制台输出中文乱码 首先乱码问题的原因通俗的讲就是读的编码格式和写的解码格式不一致,比如最常见的两种中文编码UTF-8和GBK,UTF-8一个汉字占三个字节,GBK一个汉字占两个字节,所以当编码与解码格式不一致时,输出端当然无法识别这是啥,所以只能以乱码代替。 值得一提的是GBK不是国家标准编码,常用的国标有两,一个是GB2312,一个是GB18030 GB1

【docker】基于docker-compose 安装elasticsearch + kibana + ik分词器(8.10.4版本)

记录下,使用 docker-compose 安装 Elasticsearch 和 Kibana,并配置 IK 分词器,你可以按照以下步骤进行。此过程适用于 Elasticsearch 和 Kibana 8.10.4 版本。 安装 首先,在你的工作目录下创建一个 docker-compose.yml 文件,用于配置 Elasticsearch 和 Kibana 的服务。 version:

matplotlib中文乱码问题

在使用Matplotlib进行数据可视化的过程中,经常会遇到中文乱码的问题。显示乱码是由于编码问题导致的,而matplotlib 默认使用ASCII 编码,但是当使用pyplot时,是支持unicode编码的,只是默认字体是英文字体,导致中文无法正常显示,所以显示中文乱码。 文本使用系统默认字体、手动指定字体、使用字体管理器来解决。 一、系统默认字体(全局设置字体) 在Matplotlib中

Java实现Smartcn中文分词

新建一个Maven项目,修改pom.xml文件内容:注意版本的不同; <!-- https://mvnrepository.com/artifact/org.apache.lucene/lucene-analyzers-smartcn --><dependency><groupId>org.apache.lucene</groupId><artifactId>lucene-analyzers

C++利用jsoncpp库实现写入和读取json文件(含中文处理)

C++利用jsoncpp库实现写入和读取json文件 1 jsoncpp常用类1.1 Json::Value1.2 Json::Reader1.3 Json::Writer 2 json文件3 写json文件3.1 linux存储结果3.2 windows存储结果 3 读json文件4 读json字符串参考文章 在C++中使用跨平台的开源库JsonCpp,实现json的序列化和反序列

解决IntelliJ IDEA 使用 TOMCAT 中文乱码问题

运行tomcat时,控制台乱码 1)打开Run/Debug Configuration,选择你的tomcat 2)然后在 Server > VM options 设置为 -Dfile.encoding=UTF-8 ,重启tomcat