Lucene全文检索学习笔记

全文索引 介绍Lucene的作者:Lucene的贡献者Doug Cutting是 一位资深全文索引/检索专家,曾经是V-Twin搜索引擎(Apple的Copland操作系统的成就之一)的主要开发者,后在Excite担任高级系统架构设计师,目前从事于一些INTERNET底层架构的研究。他贡献出的Lucene的目标是为各种中小型应用程序加入全文检索功能。原理lucene的检索算法属于索引检索,即用空间来换取时间,对需要检索的文件、字符流进行全文索引,在检索的时候对索引进行快速的检索,得到检索位置,这个位置记录检索词出现的文件路径或者某个关键词。

在使用数据库的项目中,不使用数据库进行检索的原因主要是:数据库在非精确查询的时候使用查询语言“like %keyword%”,对数据库进行查询是对所有记录遍历,并对字段进行“%keyword%”匹配,在数据库的数据庞大以及某个字段存储的数据量庞大的时候,这种遍历是致命的,它需要对所有的记录进行匹配查询。因此,lucene主要适用于文档集的全文检索,以及海量数据库的模糊检索,特别是对数据库的xml或者大数据的字符类型。

一、       搭建环境

a)      导包:

  1. IKAnalyzer3.2.0Stable.jar    //中文分词器
  2. lucene-analyzers-3.0.1.jar   //英文分词器
  3. lucene-core-3.0.1.jar          //核心包
  4. lucene-highlighter-3.0.1.jar       //关键字高亮显示
  5. lucene-memory-3.0.1.jar    //缓存机制

二、       建立索引

a)      步骤:

  1. 创建索引的javaBean类文件,如Article.java
  2. 创建全文检索库,Directory directory = FSDirectory.open(new File("./indexDir"));
  3. 创建分词器,Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_30);//在这里使用的是Lucene自带的分词器,也可以使用上面的IKAnalyzer中文分词
  4. 创建IndexWriter对象,IndexWriter indexWriter = new IndexWriter(directory, analyzer,       MaxFieldLength.UNLIMITED);
  5. 创建Document对象,Document document = new Document();
  6. 将Article对象转换为Document对象,Field idField = new Field("id", article.getId().toString(), Store.YES,       Index.NOT_ANALYZED);
  7. 将Field添加到Document中,document.add(idField);
  8. 将Document对象写入到索引库中,indexWriter.addDocument(document);
  9. 最后,切记关闭IndexWriter对象,否则会产生一个锁文件,导致下面无法执行。indexWriter.close();

三、       如何删除索引

a)      步骤:

  1. lucene提供了两种从索引中删除document的方法,一种是void deleteDocument(int docNum)这种方法是根据document在索引中的编号来删除,每个document加进索引后都会有个唯一编号,所以根据编号删除是一种精确删除,但是这个编号是索引的内部结构,一般我们不会知道某个文件的编号到底是几,所以用处不大。另一种是void deleteDocuments(Term term)这种方法实际上是首先根据参数term执行一个搜索操作,然后把搜索到的结果批量删除了。我们可以通过这个方法提供一个严格的查询条件,达到删除指定document的目的。 下面给出一个例子: Directory dir = FSDirectory.getDirectory(PATH, false);  IndexReader reader = IndexReader.open(dir);  Term term = new Term(field, key);  reader.deleteDocuments(term);  reader.close();

四、       更新索引

a)      步骤:

  1. lucene并没有提供专门的索引更新方法,我们需要先将相应的document删除,然后再将新的document加入索引。例如: Directory dir = FSDirectory.getDirectory(PATH, false);  IndexReader reader = IndexReader.open(dir);  Term term = new Term(“title”, “lucene introduction”);  reader.deleteDocuments(term);  reader.close(); IndexWriter writer = new IndexWriter(dir, new StandardAnalyzer(), true);  Document doc = new Document();  doc.add(new Field("title", "lucene introduction", Field.Store.YES, Field.Index.TOKENIZED));  doc.add(new Field("content", "lucene is funny", Field.Store.YES, Field.Index.TOKENIZED));  writer.addDocument(doc);  writer.optimize();  writer.close();

五、       根据关键字进行搜索

a)      步骤:

  1. 创建全文检索库,Directory directory = FSDirectory.open(new File("./indexDir"));
  2. 创建IndexSearcher对象,IndexSearcher indexSearcher = new IndexSearcher(directory);
  3. 创建分词器,Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_30);//在这里使用的是Lucene自带的分词器,也可以使用上面的IKAnalyzer中文分词
  4. 创建QueryParser对象,QueryParser queryParser = new QueryParser(Version.LUCENE_30, "title", analyzer);
  5. 创建Query对象,Query query = queryParser.parse("lucene");//lucene为关键词
  6. 获取查询对象:
    1. TopDocs topDocs = indexSearcher.search(query, 10);
    2. int count = topDocs.totalHits;// 总的记录数
    3. ScoreDoc[] scoreDocs = topDocs.scoreDocs;
    4. For循环遍历scoreDocs数组,将查询出来的每一个Document对象转换为Article对象,并装进List集合中。
    5. 关闭IndexSearcher对象

六、       分页处理

a)      步骤:

  1. 通过两个参数来实现Lucene的分页处理,一个是:开始的记录,二是:每页显示的记录条数。
  2. 在Lucene中每次查询都是全部检索,所以可以通过这个方法获取总记录数,然后用每页显示的条件将其进行分开。int count = topDocs.totalHits;// 总的记录数
  3. 如:每页显示5条,索引从0开始,现在要查询第二页,那就是从第5开始到第9条数据。具体写法: for (int i = startResult; i < len; i++) {}

七、       高亮显示

a)      步骤:

  1. 在进行了查询以后,开始配置高亮器TopDocs topDocs = indexSearcher.search(query, 25);
  2. 在关键字的前后加前缀和后缀,用于使关键词变色Formatter formatter = new SimpleHTMLFormatter("<font color='red'>","</font>");
  3. 封装了关键字Scorer scorer = new QueryScorer(query);
  4. Highlighter highlighter = new Highlighter(formatter,scorer); //摘要
  5. Fragmenter fragmenter = new SimpleFragmenter(5);
  6. highlighter.setTextFragmenter(fragmenter);
  7. 在循环scoreDocs中为关键字进行高亮String text = highlighter.getBestFragment(LuceneUtils.analyzer, "title",document.get("title"));
  8. 如果高亮完成则会返回高亮后的字符串,如果不成功则会返回Null

八、       Lucene优化

介绍:

Lucene 会为每一次addDocument(document) 是在索引库目录下新增一个文件".cfs"后缀的文件。这意味我们的程序将便对越来越多的文件(但是默认情况下,最多也不会超过10个,当文件达到10个的时候,lucene 会将它们合并为一个大的文件)。意味着 IO 流的打开和关闭也将越来越多。这对效率的影响是比较大的。

方式是调用 IndexWriter 的 IndexWriter.optimize() 或 indexWriter.setMergeFactor(int),IndexWriter.optimize() 被调用后,lucene 会立即将索引库目录下所有 ".cfs" 后缀的文件合并为一个大的文件。但是它的数据是不会改变的。indexWriter.setMergeFactor(int) 接收一个整型参数表示当 ".cfs" 文件达到多少数量时就自动合并。

a)      具体信息:

  1. 调用commit方法,会生成一个.cfs文件,调用一次生成一个。
  2. 单独调用optimize()方法,会额外生成一个合并了多个.cfs文件的大.cfs文件,此时如果不close(),则索引文件大小翻倍。单独close()方法无效。
  3. 添加文档后,手动GC一次,内存释放快。
  4. 设置合并因子,根据.cfs文件个数合并。
  5. 添加一次document则消耗一定内存,然后释放,内存回升。
  6. 不进行提交操作,内存逐渐增长,若进行提交且内存占用多,则一次性回升大,然后降低至最低。
  7. 暂时未发现改变内存缓冲大小setRAMBufferSizeMB对索引性能的影响。
  8. 如果合并索引文件,则剩余的碎片会在commit或者是close之后自动删除。
  9. 合并式的索引效率没有提交式的索引效率高。
  10. 加载索引,首先读入段信息,然后看一下有几个段:如果只有一个,那么可能是优化过的,直接读取这一个段就可以;否则需要一次读入各个段,然后再拼成一个MultiReader。
  11. 增强索引的实时性,利用内存索引存放document一段时间,然后写入磁盘索引。搜索的时候提供内存跟磁盘索引多级目录。合并的时候,需要创建第三个索引,用于存放新增加的document,搜索时需要遍历这三个索引。
  12. 分布式的处理, 需要将相应的类,按照同样的路径打包,否则出现问题。
  13. 分布式的注册类,实现UnicastRemoteObject类后就不需要专门生成RMI类。
  14. 采用多线程,对每一个线程都有一个对应的文档集处理对象,因此可以并行的进行索引,虽然对文档的处理过程可以并行,但是将文档写入索引文件却必须串行进行。
  15. setMergeFactor设置lucene的合并参数,可以由setMaxMergeDocs来控制一个.cfs文件里面的document个数,如果超过了,则合并因子失效了。
  16. 所谓的优化就是对整个目录内未合并的segment进行的合并。同设置合并因子之后的合并。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏pangguoming

MyBatis动态SQL foreach标签实现批量插入

需求:查出给定id的记录: <select id="getEmpsByConditionForeach" resultType="com.test.beans....

4743
来自专栏IMWeb前端团队

webpack2 的 tree-shaking 好用吗?

代码压缩的现状 下面是一个使用 react 的业务的代码依赖,但是实际上业务代码中并没有对依赖图中标识的模块,也就是说构建工具将不需要的代码打包到了最终的代码当...

2615
来自专栏同步博客

Redis命令总结及其基础知识讲述

  Redis拥有其他数据库不具备的数据结构,又拥有内存存储(这使得redis的速度非常快),远程操作(使得redis可以与多个客户端和服务器进行连接)、持久化...

673
来自专栏服务端技术杂谈

从org.springframework.dao.DuplicateKeyException说起

通常在dao层将所有异常转嫁到Spring的RuntimeException体系(org.springframework.dao.DataAccessExcep...

1244
来自专栏杨建荣的学习笔记

关于order by中的数据排序(r4笔记第6天)

今天开发的一个同事找到我,说碰到一个比较奇怪的问题,说两个等价的查询类似下面的形式。 select *from test where account_id=xx...

2626
来自专栏岑玉海

Spark源码系列(六)Shuffle的过程解析

Spark大会上,所有的演讲嘉宾都认为shuffle是最影响性能的地方,但是又无可奈何。之前去百度面试hadoop的时候,也被问到了这个问题,直接回答了不知道。...

3616
来自专栏北京马哥教育

10分钟学会理解和解决MySQL乱码问题

本文将详细介绍MySQL乱码的成因和具体的解决方案。在阅读本文之前,强烈建议对字符集编码概念还比较模糊的同学 阅读下博主之前对相关概念的一篇科普:十分钟搞清字符...

2658
来自专栏Java技术栈

去 BAT 面试,总结了这 55 道 MySQL 面试题!

Mysql数据库软件是一个客户端或服务器系统,其中包括:支持各种客户端程序和库的多线程SQL服务器、不同的后端、广泛的应用程序编程接口和管理工具。

602
来自专栏Spark生态圈

[spark] Shuffle Read解析 (Sort Based Shuffle)

本文将讲解shuffle Reduce部分,shuffle的下游Stage的第一个rdd是ShuffleRDD,通过其compute方法来获取上游Stage S...

821
来自专栏difcareer的技术笔记

Android Linker学习笔记[转]

Linker是Android系统动态库so的加载器/链接器,要想轻松地理解Android linker的运行机制,我们需要先熟悉ELF的文件结构,再了解ELF文...

1174

扫码关注云+社区