【Java】Lucene检索引擎详解

基于Java的全文索引/检索引擎——Lucene

  Lucene不是一个完整的全文索引应用,而是是一个用Java写的全文索引引擎工具包,它可以方便的嵌入到各种应用中实现针对应用的全文索引/检索功能。

  Lucene的作者:Lucene的贡献者Doug Cutting是一位资深全文索引/检索专家,曾经是V-Twin搜索引擎(Apple的Copland操作系统的成就之一)的主要开发者,后在Excite担任高级系统架构设计师,目前从事于一些INTERNET底层架构的研究。他贡献出的Lucene的目标是为各种中小型应用程序加入全文检索功能。

  Lucene的发展历程:早先发布在作者自己的www.lucene.com,后来发布在SourceForge,2001年年底成为APACHE基金会jakarta的一个子项目:http://jakarta.apache.org/lucene/

全文检索的实现机制

  Lucene的API接口设计的比较通用,输入输出结构都很像数据库的表==>记录==>字段,所以很多传统的应用的文件、数据库等都可以比较方便的映射到Lucene的存储结构/接口中。总体上看:可以先把Lucene当成一个支持全文索引的数据库系统

  比较一下Lucene和数据库:

Lucene 数据库
索引数据源:doc(field1,field2...) doc(field1,field2...)
\ indexer /
_____________
| Lucene Index|
--------------
/ searcher \
结果输出:Hits(doc(field1,field2) doc(field1...))
 索引数据源:record(field1,field2...) record(field1..)
\ SQL: insert/
_____________
| DB Index |
-------------
/ SQL: select \
结果输出:results(record(field1,field2..) record(field1...))
Document:一个需要进行索引的“单元”
一个Document由多个字段组成
Record:记录,包含多个字段
Field:字段 Field:字段
Hits:查询结果集,由匹配的Document组成 RecordSet:查询结果集,由多个Record组成

  通常比较厚的书籍后面常常附关键词索引表(比如:北京:12, 34页, 上海:3,77页……),它能够帮助读者比较快地找到相关内容的页码。

  而数据库索引能够大大提高查询的速度原理也是一样,想像一下通过书后面的索引查找的速度要比一页一页地翻内容高多少倍……而索引之所以效率高,另外一个原因是它是排好序的。对于检索系统来说核心是一个排序问题。

  建立一个高效检索系统的关键是建立一个类似于科技索引一样的反向索引机制,将数据源(比如多篇文章)排序顺序存储的同时,有另外一个排好序的关键词列表,用于存储关键词==>文章映射关系,利用这样的映射关系索引:[关键词==>出现关键词的文章编号,出现次数(甚至包括位置:起始偏移量,结束偏移量),出现频率],检索过程就是把模糊查询变成多个可以利用索引的精确查询的逻辑组合的过程。从而大大提高了多关键词查询的效率,所以,全文检索问题归结到最后是一个排序问题。

   全文检索和数据库应用最大的不同在于:让最相关的头100条结果满足98%以上用户的需求

  Lucene的创新之处:

  大部分的搜索(数据库)引擎都是用B树结构来维护索引,索引的更新会导致大量的IO操作,Lucene在实现中,对此稍微有所改进:不是维护一个索引文件,而是在扩展索引的时候不断创建新的索引文件,然后定期的把这些新的小索引文件合并到原先的大索引中(针对不同的更新策略,批次的大小可以调整),这样在不影响检索的效率的前提下,提高了索引的效率。

  Lucene和其他一些全文检索系统/应用的比较:

  Lucene 其他开源全文检索系统
增量索引和批量索引 可以进行增量的索引(Append),可以对于大量数据进行批量索引,并且接口设计用于优化批量索引和小批量的增量索引。 很多系统只支持批量的索引,有时数据源有一点增加也需要重建索引。
数据源 Lucene没有定义具体的数据源,而是一个文档的结构,因此可以非常灵活的适应各种应用(只要前端有合适的转换器把数据源转换成相应结构), 很多系统只针对网页,缺乏其他格式文档的灵活性。
索引内容抓取 Lucene的文档是由多个字段组成的,甚至可以控制那些字段需要进行索引,那些字段不需要索引,近一步索引的字段也分为需要分词和不需要分词的类型:
   需要进行分词的索引,比如:标题,文章内容字段
   不需要进行分词的索引,比如:作者/日期字段
缺乏通用性,往往将文档整个索引了
语言分析 通过语言分析器的不同扩展实现:
可以过滤掉不需要的词:an the of 等,
西文语法分析:将jumps jumped jumper都归结成jump进行索引/检索
非英文支持:对亚洲语言,阿拉伯语言的索引支持
缺乏通用接口实现
查询分析 通过查询分析接口的实现,可以定制自己的查询语法规则:
比如: 多个关键词之间的 + - and or关系等
 
并发访问 能够支持多用户的使用  

关于亚洲语言的的切分词问题(Word Segment)

  对于中文来说,全文索引首先还要解决一个语言分析的问题,对于英文来说,语句中单词之间是天然通过空格分开的,但亚洲语言的中日韩文语句中的字是一个字挨一个,所以,首先要把语句中按“词”进行索引的话,这个词如何切分出来就是一个很大的问题。

  首先,肯定不能用单个字符作(si-gram)为索引单元,否则查“上海”时,不能让含有“海上”也匹配。

  但一句话:“北京天安门”,计算机如何按照中文的语言习惯进行切分呢?
  “北京 天安门” 还是“北 京 天安门”?让计算机能够按照语言习惯进行切分,往往需要机器有一个比较丰富的词库才能够比较准确的识别出语句中的单词。

  另外一个解决的办法是采用自动切分算法:将单词按照2元语法(bigram)方式切分出来,比如:"北京天安门" ==> "北京 京天 天安 安门"。

  这样,在查询的时候,无论是查询"北京" 还是查询"天安门",将查询词组按同样的规则进行切分:"北京","天安安门",多个关键词之间按与"and"的关系组合,同样能够正确地映射到相应的索引中。

  这种方式对于其他亚洲语言:韩文,日文都是通用的。

  基于自动切分的最大优点是没有词表维护成本,实现简单,缺点是索引效率低,但对于中小型应用来说,基于2元语法的切分还是够用的。

  基于2元切分后的索引一般大小和源文件差不多,而对于英文,索引文件一般只有原文件的30%-40%不同,

  自动切分 词表切分
实现 实现非常简单 实现复杂
查询 增加了查询分析的复杂程度, 适于实现比较复杂的查询语法规则
存储效率 索引冗余大,索引几乎和原文一样大 索引效率高,为原文大小的30%左右
维护成本 无词表维护成本 词表维护成本非常高:中日韩等语言需要分别维护。
还需要包括词频统计等内容
适用领域 嵌入式系统:运行环境资源有限
分布式系统:无词表同步问题
多语言环境:无词表维护成本
对查询和存储效率要求高的专业搜索引擎

  目前比较大的搜索引擎的语言分析算法一般是基于以上2个机制的结合。

lucene的组成结构

  对于外部应用来说索引模块(index)和检索模块(search)是主要的外部应用入口

org.apache.Lucene.search/ 搜索入口
org.apache.Lucene.index/ 索引入口
org.apache.Lucene.analysis/ 语言分析器
org.apache.Lucene.queryParser/ 查询分析器
org.apache.Lucene.document/ 存储结构
org.apache.Lucene.store/  底层IO/存储结构
org.apache.Lucene.util/ 一些公用的数据结构

  索引过程:从命令行读取文件名(多个),将文件分路径(path字段)和内容(body字段)2个字段进行存储,并对内容进行全文索引:

  索引的单位是Document对象,每个Document对象包含多个字段Field对象,针对不同的字段属性和数据输出的需求,对字段还可以选择不同的索引/存储字段规则,

  列表如下:

方法切词索引存储用途
Field.Text(String name, String value) Yes Yes Yes 切分词索引并存储,比如:标题,内容字段
Field.Text(String name, Reader value) Yes Yes No 切分词索引不存储,比如:META信息,
不用于返回显示,但需要进行检索内容
Field.Keyword(String name, String value) No Yes Yes 不切分索引并存储,比如:日期字段
Field.UnIndexed(String name, String value) No No Yes 不索引,只存储,比如:文件路径
Field.UnStored(String name, String value) Yes Yes No 只全文索引,不存储

  索引过程中可以看到:

  • 语言分析器提供了抽象的接口,因此语言分析(Analyser)是可以定制的,虽然lucene缺省提供了2个比较通用的分析器SimpleAnalyser和StandardAnalyser,这2个分析器缺省都不支持中文,所以要加入对中文语言的切分规则,需要修改这2个分析器。
  • Lucene并没有规定数据源的格式,而只提供了一个通用的结构(Document对象)来接受索引的输入,因此输入的数据源可以是:数据库,WORD文档,PDF文档,HTML文档……只要能够设计相应的解析转换器将数据源构造成成Docuement对象即可进行索引。
  • 对于大批量的数据索引,还可以通过调整IndexerWrite的文件合并频率属性(mergeFactor)来提高批量索引的效率。

  检索过程和结果显示:

  • 搜索结果返回的是Hits对象,可以通过它再访问Document==>Field中的内容。
  • 假设根据body字段进行全文检索,可以将查询结果的path字段和相应查询的匹配度(score)打印出来
  • 在整个检索过程中,语言分析器,查询分析器,甚至搜索器(Searcher)都是提供了抽象的接口,可以根据需要进行定制。

简化的查询分析器

  目前LUCENE支持的语法:

Query ::= ( Clause )*
Clause ::= ["+", "-"] [<TERM> ":"] ( <TERM> | "(" Query ")")

  中间的逻辑包括:and or + - &&||等符号,而且还有"短语查询"和针对西文的前缀/模糊查询等,对于一般应用来说,这些功能有一些华而不实,其实能够实现目前类似于Google的查询语句分析功能其实对于大多数用户来说已经够了。

  所以,Lucene早期版本的QueryParser仍是比较好的选择。

添加修改删除指定记录(Document)

  Lucene提供了索引的扩展机制,因此索引的动态扩展应该是没有问题的,而指定记录的修改也似乎只能通过记录的删除,然后重新加入实现。

  如何删除指定的记录呢?

  删除的方法也很简单,只是需要在索引时根据数据源中的记录ID专门另建索引,然后利用IndexReader.delete(Termterm)方法通过这个记录ID删除相应的Document。

根据某个字段值的排序功能

  lucene缺省是按照自己的相关度算法(score)进行结果排序的,但能够根据其他字段进行结果排序是一个在LUCENE的开发邮件列表中经常提到的问题,很多原先基于数据库应用都需要除了基于匹配度(score)以外的排序功能。

  而从全文检索的原理我们可以了解到,任何不基于索引的搜索过程效率都会导致效率非常的低,如果基于其他字段的排序需要在搜索过程中访问存储字段,速度回大大降低,因此非常是不可取的。

  但这里也有一个折中的解决方法:在搜索过程中能够影响排序结果的只有索引中已经存储的docID和score这2个参数,所以,基于score以外的排序,其实可以通过将数据源预先排好序,然后根据docID进行排序来实现。这样就避免了在LUCENE搜索结果外对结果再次进行排序和在搜索过程中访问不在索引中的某个字段值。

更通用的输入输出接口

  虽然lucene没有定义一个确定的输入文档格式,但越来越多的人想到使用一个标准的中间格式作为Lucene的数据导入接口,然后其他数据,比如PDF只需要通过解析器转换成标准的中间格式就可以进行数据索引了。这个中间格式主要以XML为主,类似实现已经不下4,5个:

数据源: WORD       PDF     HTML    DB       other
\ | | | /
XML中间格式
|
Lucene INDEX

索引过程优化

  索引一般分2种情况,一种是小批量的索引扩展,一种是大批量的索引重建。

  在索引过程中,并不是每次新的DOC加入进去索引都重新进行一次索引文件的写入操作(文件I/O是一件非常消耗资源的事情)。

  Lucene先在内存中进行索引操作,并根据一定的批量进行文件的写入。

  这个批次的间隔越大,文件的写入次数越少,但占用内存会很多。反之占用内存少,但文件IO操作频繁,索引速度会很慢。

  在IndexWriter中有一个MERGE_FACTOR参数可以帮助你在构造索引器后根据应用环境的情况充分利用内存减少文件的操作。

  根据经验:缺省Indexer是每20条记录索引后写入一次,每将MERGE_FACTOR增加50倍,索引速度可以提高1倍左右。

搜索过程优化

  Lucene面向全文检索的优化在于首次索引检索后,并不把所有的记录(Document)具体内容读取出来,而起只将所有结果中匹配度最高的头100条结果(TopDocs)的ID放到结果集缓存中并返回,这里可以比较一下数据库检索:如果是一个10,000条的数据库检索结果集,数据库是一定要把所有记录内容都取得以后再开始返回给应用结果集的。

  所以即使检索匹配总数很多,Lucene的结果集占用的内存空间也不会很多。对于一般的模糊检索应用是用不到这么多的结果的,头100条已经可以满足90%以上的检索需求。

  如果首批缓存结果数用完后还要读取更后面的结果时Searcher会再次检索并生成一个上次的搜索缓存数大1倍的缓存,并再重新向后抓取。

  所以如果构造一个Searcher去查1-120条结果,Searcher其实是进行了2次搜索过程:头100条取完后,缓存结果用完,Searcher重新检索再构造一个200条的结果缓存,依此类推,400条缓存,800条缓存。

  由于每次Searcher对象消失后,这些缓存也访问不到那了,你有可能想将结果记录缓存下来,缓存数尽量保证在100以下以充分利用首次的结果缓存,不让Lucene浪费多次检索,而且可以分级进行结果缓存。

  Lucene的另外一个特点是在收集结果的过程中将匹配度低的结果自动过滤掉了。这也是和数据库应用需要将搜索的结果全部返回不同之处。

从Lucene学到更多

  Luene的确是一个面向对象设计的典范

  • 所有的问题都通过一个额外抽象层来方便以后的扩展和重用:你可以通过重新实现来达到自己的目的,而对其他模块则不需要;
  • 简单的应用入口Searcher, Indexer,并调用底层一系列组件协同的完成搜索任务;
  • 所有的对象的任务都非常专一:比如搜索过程:QueryParser分析将查询语句转换成一系列的精确查询的组合(Query),通过底层的索引读取结构IndexReader进行索引的读取,并用相应的打分器给搜索结果进行打分/排序等。所有的功能模块原子化程度非常高,因此可以通过重新实现而不需要修改其他模块。 
  • 除了灵活的应用接口设计,Lucene还提供了一些适合大多数应用的语言分析器实现(SimpleAnalyser,StandardAnalyser),这也是新用户能够很快上手的重要原因之一。

  这些优点都是非常值得在以后的开发中学习借鉴的。作为一个通用工具包,Lunece的确给予了需要将全文检索功能嵌入到应用中的开发者很多的便利。

  此外,通过对Lucene的学习和使用,理解了为什么很多数据库优化设计中要求,比如:

  • 尽可能对字段进行索引来提高查询速度,但过多的索引会对数据库表的更新操作变慢,而对结果过多的排序条件,实际上往往也是性能的杀手之一。
  • 很多商业数据库对大批量的数据插入操作会提供一些优化参数,这个作用和索引器的merge_factor的作用是类似的,
  • 20%/80%原则:查的结果多并不等于质量好,尤其对于返回结果集很大,如何优化这头几十条结果的质量往往才是最重要的。
  • 尽可能让应用从数据库中获得比较小的结果集,因为即使对于大型数据库,对结果集的随机访问也是一个非常消耗资源的操作。

Lucene学习示例

  1.HelloWorld入门

  1 import java.io.BufferedReader;
  2 import java.io.File;
  3 import java.io.FileReader;
  4 import java.io.IOException;
  5 
  6 import org.apache.lucene.analysis.standard.StandardAnalyzer;
  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.queryParser.QueryParser;
 13 import org.apache.lucene.search.IndexSearcher;
 14 import org.apache.lucene.search.Query;
 15 import org.apache.lucene.search.ScoreDoc;
 16 import org.apache.lucene.search.TopDocs;
 17 import org.apache.lucene.store.Directory;
 18 import org.apache.lucene.store.FSDirectory;
 19 import org.apache.lucene.util.Version;
 20 
 21 /* 【Lucene3.6.2入门系列】第01节_HelloWord 
 22  * @see 这里只需用到一个lucene-core-3.6.2.jar 
 23  * @see Lucene官网:http://lucene.apache.org 
 24  * @see Lucene下载:http://archive.apache.org/dist/lucene/java/ 
 25  * @see Lucene文档:http://wiki.apache.org/lucene-java/ 
 26  * @see ------------------------------------------------------------------------------------------------------------- 
 27  * @see 1)对于全文搜索工具,都是由索引、分词、搜索三部分组成 
 28  * @see 2)被存储和被索引,是两个独立的概念 
 29  * @see ------------------------------------------------------------------------------------------------------------- 
 30  * @see 域的存储选项 
 31  * @see Field.Store.YES--会把该域中的内容存储到文件中,方便进行文本的还原 
 32  * @see Field.Store.NO---表示该域中的内容不存储到文件中,但允许被索引,且内容无法完全还原(doc.get("##")) 
 33  * @see ------------------------------------------------------------------------------------------------------------- 
 34  * @see 域的索引选项 
 35  * @see Field.Index.ANALYZED----------------进行分词和索引,适用于标题、内容等 
 36  * @see Field.Index.NOT_ANALYZED------------进行索引但不分词(如身份证号、姓名、ID等),适用于精确搜索 
 37  * @see Field.Index.ANALYZED_NOT_NORMS------进行分词但是不存储norms信息,这个norms中包括了创建索引的时间和权值等信息 
 38  * @see Field.Index.NOT_ANALYZED_NOT_NORMS--即不进行分词也不存储norms信息 
 39  * @see Field.Index.NO----------------------不进行索引 
 40  * @see norms:当数据被搜索出来后,便涉及到排序的问题,而排序是有一些评分规则的,于是NORMS中就存储了这些排序的信息 
 41  * @see -------------------------------------------------------------------------------------------------------------  
 42  * @see 域选项最佳实践 
 43  * @see Field.Store   Field.Index              域值 
 44  * @see       YES     NOT_ANALYZED_NOT_NORMS   标识符(主键、文件名),电话号码,身份证号,姓名,日期 
 45  * @see       YES     ANALYZED                 文档标题和摘要 
 46  * @see        NO     ANALYZED                 文档正文 
 47  * @see        NO     NOT_ANALYZED             隐藏关键字 
 48  * @see       YES     NO                       文档类型,数据库主键(不进行索引) 
 49  * @see ------------------------------------------------------------------------------------------------------------- 
 50  * @create Jun 29, 2012 4:20:19 PM 
 51  * @author 玄玉<http://blog.csdn.net/jadyer> 
 52  */
 53 public class Lucene_01_HelloWord {
 54     private static final String PATH_OF_FILE = "E:/lucene_test/01_file/";   // 待索引文件的目录
 55     private static final String PATH_OF_INDEX = "E:/lucene_test/01_index/"; // 存放索引文件的目录2
 56 
 57     /**
 58      * 测试时,要在E:/lucene_test/01_file/文件夹中准备几个包含内容的文件(比如txt格式的)
 59      * 然后先执行createIndex()方法,再执行searchFile()方法,最后观看控制台输出即可
 60      */
 61     public static void main(String[] args) {
 62         Lucene_01_HelloWord instance = new Lucene_01_HelloWord();
 63         instance.createIndex();
 64         instance.searchFile();
 65     }
 66 
 67     /**
 68      * 创建索引
 69      * 
 70      * @see ---------------------------------------------------------------------------------------------------------
 71      * @see 1、创建Directory-----------------指定索引被保存的位置
 72      * @see 2、创建IndexWriter---------------通过IndexWriter写索引
 73      * @see 3、创建Document对象---------------我们索引的有可能是一段文本or数据库中的一张表
 74      * @see 4、为Document添加Field------------相当于Document的标题、大小、内容、路径等等,二者类似于数据库表中每条记录和字段的关系
 75      * @see 5、通过IndexWriter添加文档到索引中
 76      * @see 6、关闭IndexWriter----------------用完IndexWriter之后,必须关闭之
 77      * @see ---------------------------------------------------------------------------------------------------------
 78      * @see _0.fdt和_0.fdx文件--保存域中所存储的数据(Field.Store.YES条件下的)
 79      * @see _0.fnm文件----------保存域选项的数据(即new Field(name, value)中的name)
 80      * @see _0.frq文件----------记录相同的文件(或查询的关键字)出现的次数,它是用来做评分和排序的
 81      * @see _0.nrm文件----------存储一些评分信息
 82      * @see _0.prx文件----------记录偏移量
 83      * @see _0.tii和_0.tis文件--存储索引里面的所有内容信息
 84      * @see segments_1文件------它是段文件,Lucene首先会到段文件中查找相应的索引信息
 85      * @see ---------------------------------------------------------------------------------------------------------
 86      */
 87     private void createIndex() {
 88         Directory directory = null;
 89         IndexWriter writer = null;
 90         Document doc = null;
 91         try {
 92             // FSDirectory会根据当前的运行环境打开一个合理的基于File的Directory(若在内存中创建索引则new RAMDirectory())
 93             // 这里是在硬盘上"E:/lucene_test/01_index/"文件夹中创建索引
 94             directory = FSDirectory.open(new File(PATH_OF_INDEX));
 95             // 由于Lucene2.9之后,其索引的格式就不会再兼容Lucene的所有版本了,所以在创建索引前,要指定其所匹配的Lucene版本号
 96             // 这里通过IndexWriterConfig()构造方法的Version.LUCENE_36参数值指明索引所匹配的版本号,并使用了Lucene的标准分词器
 97             writer = new IndexWriter(directory, new IndexWriterConfig(Version.LUCENE_36, new StandardAnalyzer(Version.LUCENE_36)));
 98             for (File file : new File(PATH_OF_FILE).listFiles()) {
 99                 doc = new Document();
100                 // 把内容添加到索引域中,即为该文档存储信息,供将来搜索时使用(下面的写法,其默认为Field.Store.NO和Field.Index.ANALYZED)
101                 // 如果我们想把content的内容也存储到硬盘上,那就需要先把file转换成字符串,然后按照"fileName"的存储方式加到Field中
102                 // 我们可以用commons-io-2.3.jar提供的FileUtils.readFileToString(file),这是很方便的工具包,有了它几乎都不用手写任何的IO方法了
103                 // doc.add(new Field("content", FileUtils.readFileToString(file), Field.Store.YES, Field.Index.ANALYZED));
104                 doc.add(new Field("content", new FileReader(file)));
105                 // Field.Store.YES-----------这里是将文件的全名存储到硬盘中
106                 // Field.Index.NOT_ANALYZED--这里是不对文件名进行分词
107                 doc.add(new Field("fileName", file.getName(), Field.Store.YES, Field.Index.NOT_ANALYZED));
108                 doc.add(new Field("filePath", file.getAbsolutePath(), Field.Store.YES, Field.Index.NOT_ANALYZED));
109                 // 通过IndexWriter添加文档到索引中
110                 writer.addDocument(doc);
111             }
112         }
113         catch (Exception e) {
114             System.out.println("创建索引的过程中遇到异常,堆栈轨迹如下");
115             e.printStackTrace();
116         }
117         finally {
118             if (null != writer) {
119                 try {
120                     writer.close(); // IndexWriter在用完之后一定要关闭
121                 }
122                 catch (IOException ce) {
123                     System.out.println("关闭IndexWriter时遇到异常,堆栈轨迹如下");
124                     ce.printStackTrace();
125                 }
126             }
127         }
128     }
129 
130     private String getContentFromFile(File myFile) {
131         StringBuffer sb = new StringBuffer();
132         if (!myFile.exists()) {
133             return "";
134         }
135         try {
136             BufferedReader in = new BufferedReader(new FileReader(myFile));
137             String str;
138             while ((str = in.readLine()) != null) {
139                 sb.append(str);
140             }
141             in.close();
142         }
143         catch (IOException e) {
144             e.getStackTrace();
145         }
146         return sb.toString();
147     }
148 
149     /**
150      * 搜索文件
151      * 
152      * @see 1、创建Directory
153      * @see 2、创建IndexReader
154      * @see 3、根据IndexReader创建IndexSearcher
155      * @see 4、创建搜索的Query
156      * @see 5、根据searcher搜索并返回TopDocs
157      * @see 6、根据TopDocs获取ScoreDoc对象
158      * @see 7、根据searcher和ScoreDoc对象获取具体的Document对象
159      * @see 8、根据Document对象获取需要的值
160      * @see 9、关闭IndexReader
161      */
162     private void searchFile() {
163         IndexReader reader = null;
164         try {
165             reader = IndexReader.open(FSDirectory.open(new File(PATH_OF_INDEX)));
166             IndexSearcher searcher = new IndexSearcher(reader);
167             // 创建基于Parser搜索的Query,创建时需指定其"搜索的版本,默认搜索的域,分词器"....这里的域指的是创建索引时Field的名字
168             QueryParser parser = new QueryParser(Version.LUCENE_36, "content", new StandardAnalyzer(Version.LUCENE_36));
169             Query query = parser.parse("java");       // 指定==>搜索域为content(即上一行代码指定的"content")中包含"java"的文档
170             TopDocs tds = searcher.search(query, 10); // 第二个参数指定搜索后显示的条数,若查到5条则显示为5条,查到15条则只显示10条
171             ScoreDoc[] sds = tds.scoreDocs;           // TopDocs中存放的并不是我们的文档,而是文档的ScoreDoc对象
172             for (ScoreDoc sd : sds) {                   // ScoreDoc对象相当于每个文档的ID号,我们就可以通过ScoreDoc来遍历文档
173                 Document doc = searcher.doc(sd.doc);  // sd.doc得到的是文档的序号
174                 System.out.println(doc.get("fileName") + "[" + doc.get("filePath") + "]"); // 输出该文档所存储的信息
175             }
176         }
177         catch (Exception e) {
178             System.out.println("搜索文件的过程中遇到异常,堆栈轨迹如下");
179             e.printStackTrace();
180         }
181         finally {
182             if (null != reader) {
183                 try {
184                     reader.close();
185                 }
186                 catch (IOException e) {
187                     System.out.println("关闭IndexReader时遇到异常,堆栈轨迹如下");
188                     e.printStackTrace();
189                 }
190             }
191         }
192     }
193 
194 }
View Code

  2.针对索引文件的CRUD

  1 import java.io.File;
  2 import java.io.IOException;
  3 import java.text.SimpleDateFormat;
  4 import java.util.Date;
  5 
  6 import org.apache.lucene.analysis.standard.StandardAnalyzer;
  7 import org.apache.lucene.document.Document;
  8 import org.apache.lucene.document.Field;
  9 import org.apache.lucene.document.NumericField;
 10 import org.apache.lucene.index.IndexReader;
 11 import org.apache.lucene.index.IndexWriter;
 12 import org.apache.lucene.index.IndexWriterConfig;
 13 import org.apache.lucene.index.Term;
 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.TermQuery;
 18 import org.apache.lucene.search.TopDocs;
 19 import org.apache.lucene.store.Directory;
 20 import org.apache.lucene.store.FSDirectory;
 21 import org.apache.lucene.util.Version;
 22 
 23 public class Lucene_02_HelloIndex {
 24     /**
 25      * 【Lucene3.6.2入门系列】第02节_针对索引文件的CRUD
 26      * 
 27      * @see =============================================================================================================
 28      * @see Lucene官网:http://lucene.apache.org
 29      * @see Lucene下载:http://archive.apache.org/dist/lucene/java/
 30      * @see Lucene文档:http://wiki.apache.org/lucene-java/
 31      * @see =============================================================================================================
 32      * @see 使用Luke查看分词信息(http://code.google.com/p/luke/)
 33      * @see 1)引言:每一个Lucene版本都会有一个相应的Luke文件
 34      * @see 2)打开:双击或java -jar lukeall-3.5.0.jar
 35      * @see 3)选择索引的存放目录后点击OK即可
 36      * @see 7)如果我们的索引有改变,可以点击右侧的Re-open按钮重新载入索引
 37      * @see 4)Luke界面右下角的Top ranking terms窗口中显示的就是分词信息。其中Rank列表示出现频率
 38      * @see 5)Luke菜单下的Documents选项卡中显示的就是文档信息,我们可以根据文档序号来浏览(点击向左和向右的方向箭头)
 39      * @see 6)Luke菜单下的Search选项卡中可以根据我们输入的表达式来查文档内容
 40      * @see 比如在Enter search expression here:输入content:my,再在右侧点击一个黑色粗体字的Search大按钮即可
 41      * @see =============================================================================================================
 42      * @create Jun 30, 2012 4:34:09 PM
 43      * @author 玄玉<http://blog.csdn.net/jadyer>
 44      */
 45     /*
 46      * 定义一组数据,用来演示搜索(这里有一封邮件为例)
 47      * 假设每一个变量代表一个Document,这里就定义了6个Document
 48      */
 49     // 邮件编号
 50     private String[] ids = { "1", "2", "3", "4", "5", "6" };
 51     // 邮件主题
 52     private String[] names = { "Michael", "Scofield", "Tbag", "Jack", "Jade", "Jadyer" };
 53     // 邮件地址
 54     private String[] emails = { "aa@jadyer.us", "bb@jadyer.cn", "cc@jadyer.cc", "dd@jadyer.tw", "ee@jadyer.hk", "ff@jadyer.me" };
 55     // 邮件内容
 56     private String[] contents = { "my blog", "my website", "my name", "I am JavaDeveloper", "I am from Haerbin", "I like Lucene" };
 57     // 邮件附件(为数字和日期加索引,与,字符串加索引的方式不同)
 58     private int[] attachs = { 9, 3, 5, 4, 1, 2 };
 59     // 邮件日期
 60     private Date[] dates = new Date[ids.length];
 61     // 它的创建是比较耗时耗资源的,所以这里只让它创建一次,此时reader处于整个生命周期中,实际应用中也可能直接放到ApplicationContext里面
 62     private static IndexReader reader = null;
 63     private Directory directory = null;
 64 
 65     public static void main(String[] args) {
 66         Lucene_02_HelloIndex instance = new Lucene_02_HelloIndex();
 67         instance.createIndex();
 68         instance.searchFile();
 69         instance.updateIndex();
 70         instance.getDocsCount();
 71     }
 72 
 73     public Lucene_02_HelloIndex() {
 74         SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
 75         try {
 76             dates[0] = (Date) sdf.parse("20120601");
 77             dates[1] = (Date) sdf.parse("20120603");
 78             dates[2] = (Date) sdf.parse("20120605");
 79             dates[3] = (Date) sdf.parse("20120607");
 80             dates[4] = (Date) sdf.parse("20120609");
 81             dates[5] = (Date) sdf.parse("20120611");
 82             directory = FSDirectory.open(new File("E:/lucene_test/01_index/"));
 83         }
 84         catch (Exception e) {
 85             e.printStackTrace();
 86         }
 87     }
 88 
 89     /**
 90      * 获取IndexReader实例
 91      */
 92     private IndexReader getIndexReader() {
 93         try {
 94             if (reader == null) {
 95                 reader = IndexReader.open(directory);
 96             }
 97             else {
 98                 // if the index was changed since the provided reader was opened, open and return a new reader; else,return null
 99                 // 如果当前reader在打开期间index发生改变,则打开并返回一个新的IndexReader,否则返回null
100                 IndexReader ir = IndexReader.openIfChanged(reader);
101                 if (ir != null) {
102                     reader.close(); // 关闭原reader
103                     reader = ir;    // 赋予新reader
104                 }
105             }
106             return reader;
107         }
108         catch (Exception e) {
109             e.printStackTrace();
110         }
111         return null; // 发生异常则返回null
112     }
113 
114     /**
115      * 通过IndexReader获取文档数量
116      */
117     public void getDocsCount() {
118         System.out.println("maxDocs:" + this.getIndexReader().maxDoc());
119         System.out.println("numDocs:" + this.getIndexReader().numDocs());
120         System.out.println("deletedDocs:" + this.getIndexReader().numDeletedDocs());
121     }
122 
123     /**
124      * 创建索引
125      */
126     public void createIndex() {
127         IndexWriter writer = null;
128         Document doc = null;
129         try {
130             writer = new IndexWriter(directory, new IndexWriterConfig(Version.LUCENE_36, new StandardAnalyzer(Version.LUCENE_36)));
131             writer.deleteAll();              // 创建索引之前,先把文档清空掉
132             for (int i = 0; i < ids.length; i++) { // 遍历ID来创建文档
133                 doc = new Document();
134                 doc.add(new Field("id", ids[i], Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS));
135                 doc.add(new Field("name", names[i], Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS));
136                 doc.add(new Field("email", emails[i], Field.Store.YES, Field.Index.NOT_ANALYZED));
137                 doc.add(new Field("content", contents[i], Field.Store.NO, Field.Index.ANALYZED));
138                 doc.add(new NumericField("attach", Field.Store.YES, true).setIntValue(attachs[i]));        // 为数字加索引(第三个参数指定是否索引)
139                 doc.add(new NumericField("date", Field.Store.YES, true).setLongValue(dates[i].getTime())); // 为日期加索引
140                 /*
141                  * 建立索引时加权
142                  * 定义排名规则,即加权,这里是为指定邮件名结尾的emails加权
143                  */
144                 if (emails[i].endsWith("jadyer.cn")) {
145                     doc.setBoost(2.0f);
146                 }
147                 else if (emails[i].endsWith("jadyer.me")) {
148                     doc.setBoost(1.5f); // 为文档加权....默认为1.0,权值越高则排名越高,显示得就越靠前
149                 }
150                 else {
151                     doc.setBoost(0.5f); // 注意它的参数类型是Float
152                 }
153                 writer.addDocument(doc);
154             }
155         }
156         catch (Exception e) {
157             e.printStackTrace();
158         }
159         finally {
160             if (null != writer) {
161                 try {
162                     writer.close();
163                 }
164                 catch (IOException ce) {
165                     ce.printStackTrace();
166                 }
167             }
168         }
169     }
170 
171     /**
172      * 搜索文件
173      */
174     public void searchFile() {
175         IndexSearcher searcher = new IndexSearcher(this.getIndexReader());
176         Query query = new TermQuery(new Term("content", "my")); // 精确搜索:搜索"content"中包含"my"的文档
177         try {
178             TopDocs tds = searcher.search(query, 10);
179             for (ScoreDoc sd : tds.scoreDocs) {
180                 Document doc = searcher.doc(sd.doc); // sd.doc得到的是文档的序号
181                 // doc.getBoost()得到的权值与创建索引时设置的权值之间是不相搭的,创建索引时的权值的查看需要使用Luke工具
182                 // 之所以这样,是因为这里的Document对象(是获取到的)与创建索引时的Document对象,不是同一个对象
183                 // sd.score得到的是该文档的评分,该评分规则的公式是比较复杂的,它主要与文档的权值和出现次数成正比
184                 System.out.print("(" + sd.doc + "|" + doc.getBoost() + "|" + sd.score + ")" + doc.get("name") + "[" + doc.get("email") + "]-->");
185                 System.out.println(doc.get("id") + "," + doc.get("attach") + "," + new SimpleDateFormat("yyyyMMdd").format(new Date(Long.parseLong(doc.get("date")))));
186             }
187         }
188         catch (Exception e) {
189             e.printStackTrace();
190         }
191         finally {
192             if (null != searcher) {
193                 try {
194                     searcher.close();
195                 }
196                 catch (IOException e) {
197                     e.printStackTrace();
198                 }
199             }
200         }
201     }
202 
203     /**
204      * 更新索引
205      * 
206      * @see Lucene其实并未提供更新索引的方法,这里的更新操作内部是先删除再添加的方式
207      * @see 因为Lucene认为更新索引的代价,与删除后重建索引的代价,二者是差不多的
208      */
209     public void updateIndex() {
210         IndexWriter writer = null;
211         Document doc = new Document();
212         try {
213             writer = new IndexWriter(directory, new IndexWriterConfig(Version.LUCENE_36, new StandardAnalyzer(Version.LUCENE_36)));
214             doc.add(new Field("id", "1111", Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS));
215             doc.add(new Field("name", names[0], Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS));
216             doc.add(new Field("email", emails[0], Field.Store.YES, Field.Index.NOT_ANALYZED));
217             doc.add(new Field("content", contents[0], Field.Store.NO, Field.Index.ANALYZED));
218             doc.add(new NumericField("attach", Field.Store.YES, true).setIntValue(attachs[0]));
219             doc.add(new NumericField("date", Field.Store.YES, true).setLongValue(dates[0].getTime()));
220             // 其实它会先删除索引文档中id为1的文档,然后再将这里的doc对象重新索引,所以即便这里的1!=1111,但它并不会报错
221             // 所以在执行完该方法后:maxDocs=7,numDocs=6,deletedDocs=1,就是因为Lucene会先删除再添加
222             writer.updateDocument(new Term("id", "1"), doc);
223         }
224         catch (Exception e) {
225             e.printStackTrace();
226         }
227         finally {
228             if (null != writer) {
229                 try {
230                     writer.close();
231                 }
232                 catch (IOException ce) {
233                     ce.printStackTrace();
234                 }
235             }
236         }
237     }
238 
239     /**
240      * 删除索引
241      * 
242      * @see -----------------------------------------------------------------------------------------------------
243      * @see 在执行完该方法后,再执行本类的searchFile()方法,得知numDocs=5,maxDocs=6,deletedDocs=1
244      * @see 这说明此时删除的文档并没有被完全删除,而是存储在一个回收站中,它是可以恢复的
245      * @see -----------------------------------------------------------------------------------------------------
246      * @see 从回收站中清空索引IndexWriter
247      * @see 对于清空索引,Lucene3.5之前叫做优化,调用的是IndexWriter.optimize()方法,但该方法已被禁用
248      * @see 因为optimize时它会全部更新索引,这一过程所涉及到的负载是很大的,于是弃用了该方法,使用forceMerge代替
249      * @see 使用IndexWriter.forceMergeDeletes()方法可以强制清空回收站中的内容
250      * @see 另外IndexWriter.forceMerge(3)方法会将索引合并为3段,这3段中的被删除的数据也会被清空
251      * @see 但其在Lucene3.5之后不建议使用,因为其会消耗大量的开销,而Lucene会根据情况自动处理的
252      * @see -----------------------------------------------------------------------------------------------------
253      */
254     public void deleteIndex() {
255         IndexWriter writer = null;
256         try {
257             writer = new IndexWriter(directory, new IndexWriterConfig(Version.LUCENE_36, new StandardAnalyzer(Version.LUCENE_36)));
258             // 其参数可以传Query或Term....Query指的是可以查询出一系列的结果并将其全部删掉,而Term属于精确查找
259             writer.deleteDocuments(new Term("id", "1")); // 删除索引文档中id为1的文档
260         }
261         catch (Exception e) {
262             e.printStackTrace();
263         }
264         finally {
265             if (null != writer) {
266                 try {
267                     writer.close();
268                 }
269                 catch (IOException ce) {
270                     ce.printStackTrace();
271                 }
272             }
273         }
274     }
275 
276     /**
277      * 恢复索引
278      * 
279      * @see 建议弃用
280      */
281     @Deprecated
282     public void unDeleteIndex() {
283         IndexReader reader = null;
284         try {
285             // IndexReader.open(directory)此时该IndexReader默认的readOnly=true,而在恢复索引时应该指定其为非只读的
286             reader = IndexReader.open(directory, false);
287             // Deprecated. Write support will be removed in Lucene 4.0. There will be no replacement for this method.
288             reader.undeleteAll();
289         }
290         catch (Exception e) {
291             e.printStackTrace();
292         }
293         finally {
294             if (null != reader) {
295                 try {
296                     reader.close();
297                 }
298                 catch (IOException e) {
299                     e.printStackTrace();
300                 }
301             }
302         }
303     }
304 }
View Code

  3.简述Lucene中常见的搜索功能

  1 import java.io.File;
  2 import java.io.IOException;
  3 import java.text.SimpleDateFormat;
  4 import java.util.Date;
  5 
  6 import org.apache.lucene.analysis.standard.StandardAnalyzer;
  7 import org.apache.lucene.document.Document;
  8 import org.apache.lucene.document.Field;
  9 import org.apache.lucene.document.NumericField;
 10 import org.apache.lucene.index.IndexReader;
 11 import org.apache.lucene.index.IndexWriter;
 12 import org.apache.lucene.index.IndexWriterConfig;
 13 import org.apache.lucene.index.Term;
 14 import org.apache.lucene.queryParser.ParseException;
 15 import org.apache.lucene.queryParser.QueryParser;
 16 import org.apache.lucene.search.BooleanClause.Occur;
 17 import org.apache.lucene.search.BooleanQuery;
 18 import org.apache.lucene.search.FuzzyQuery;
 19 import org.apache.lucene.search.IndexSearcher;
 20 import org.apache.lucene.search.NumericRangeQuery;
 21 import org.apache.lucene.search.PhraseQuery;
 22 import org.apache.lucene.search.PrefixQuery;
 23 import org.apache.lucene.search.Query;
 24 import org.apache.lucene.search.ScoreDoc;
 25 import org.apache.lucene.search.TermQuery;
 26 import org.apache.lucene.search.TermRangeQuery;
 27 import org.apache.lucene.search.TopDocs;
 28 import org.apache.lucene.search.WildcardQuery;
 29 import org.apache.lucene.store.Directory;
 30 import org.apache.lucene.store.FSDirectory;
 31 import org.apache.lucene.util.Version;
 32 
 33 /**
 34  * 【Lucene3.6.2入门系列】第03节_简述Lucene中常见的搜索功能
 35  * 
 36  * @create Aug 1, 2013 3:54:27 PM
 37  * @author 玄玉<http://blog.csdn.net/jadyer>
 38  */
 39 public class Lucene_03_HelloSearch {
 40     private Directory directory;
 41     private IndexReader reader;
 42     private String[] ids = { "1", "2", "3", "4", "5", "6" };
 43     private String[] names = { "Michael", "Scofield", "Tbag", "Jack", "Jade", "Jadyer" };
 44     private String[] emails = { "aa@jadyer.us", "bb@jadyer.cn", "cc@jadyer.cc", "dd@jadyer.tw", "ee@jadyer.hk", "ff@jadyer.me" };
 45     private String[] contents = { "my java blog is http://blog.csdn.net/jadyer", "my website is http://www.jadyer.cn", "my name is jadyer", "I am JavaDeveloper", "I am from Haerbin", "I like Lucene" };
 46     private int[] attachs = { 9, 3, 5, 4, 1, 2 };
 47     private Date[] dates = new Date[ids.length];
 48 
 49     static Lucene_03_HelloSearch instance = new Lucene_03_HelloSearch();
 50 
 51     public static void main(String[] args) {
 52         instance.searchByTerm("content", "my");
 53         instance.searchByTermRange("name", "M", "o");// 范围,M~O
 54         instance.searchByNumericRange("attach", 2, 5);
 55         instance.searchByPrefix("content", "b");
 56         instance.searchByWildcard("name", "Ja??er");
 57         instance.searchByFuzzy("name", "Jadk");
 58         instance.searchByPhrase();
 59         instance.searchByQueryParse();
 60         instance.searchPage();
 61         instance.searchPageByAfter();
 62     }
 63 
 64     public void searchPage() {
 65         for (File file : new File("E:/lucene_test/01_index/").listFiles()) {
 66             file.delete();
 67         }
 68         instance = new Lucene_03_HelloSearch(true);
 69         instance.searchPage("mycontent:javase", 2, 10);
 70     }
 71 
 72     public void searchPageByAfter() {
 73         for (File file : new File("E:/lucene_test/01_index/").listFiles()) {
 74             file.delete();
 75         }
 76         instance = new Lucene_03_HelloSearch(true);
 77         instance.searchPageByAfter("mycontent:javase", 3, 10);
 78     }
 79 
 80     public Lucene_03_HelloSearch() {
 81         IndexWriter writer = null;
 82         Document doc = null;
 83         SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
 84         try {
 85             dates[0] = sdf.parse("20120601");
 86             dates[1] = sdf.parse("20120603");
 87             dates[2] = sdf.parse("20120605");
 88             dates[3] = sdf.parse("20120607");
 89             dates[4] = sdf.parse("20120609");
 90             dates[5] = sdf.parse("20120611");
 91             directory = FSDirectory.open(new File("E:/lucene_test/01_index/"));
 92             writer = new IndexWriter(directory, new IndexWriterConfig(Version.LUCENE_36, new StandardAnalyzer(Version.LUCENE_36)));
 93             writer.deleteAll();              // 创建索引之前,先把文档清空掉
 94             for (int i = 0; i < ids.length; i++) { // 遍历ID来创建文档
 95                 doc = new Document();
 96                 doc.add(new Field("id", ids[i], Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS));
 97                 doc.add(new Field("name", names[i], Field.Store.YES, Field.Index.ANALYZED_NO_NORMS));
 98                 doc.add(new Field("email", emails[i], Field.Store.YES, Field.Index.NOT_ANALYZED));
 99                 doc.add(new Field("email", "test" + i + "" + i + "@jadyer.com", Field.Store.YES, Field.Index.NOT_ANALYZED));
100                 doc.add(new Field("content", contents[i], Field.Store.YES, Field.Index.ANALYZED));
101                 doc.add(new NumericField("attach", Field.Store.YES, true).setIntValue(attachs[i]));        // 为数字加索引(第三个参数指定是否索引)
102                 doc.add(new NumericField("attach", Field.Store.YES, true).setIntValue((i + 1) * 100));         // 假设有多个附件
103                 doc.add(new NumericField("date", Field.Store.YES, true).setLongValue(dates[i].getTime())); // 为日期加索引
104                 writer.addDocument(doc);
105             }
106         }
107         catch (Exception e) {
108             e.printStackTrace();
109         }
110         finally {
111             if (null != writer) {
112                 try {
113                     writer.close();
114                 }
115                 catch (IOException ce) {
116                     ce.printStackTrace();
117                 }
118             }
119         }
120     }
121 
122     /**
123      * 针对分页搜索创建索引
124      */
125     public Lucene_03_HelloSearch(boolean pageFlag) {
126         String[] myNames = new String[50];
127         String[] myContents = new String[50];
128         for (int i = 0; i < 50; i++) {
129             myNames[i] = "file(" + i + ")";
130             myContents[i] = "I love JavaSE, also love Lucene(" + i + ")";
131         }
132         IndexWriter writer = null;
133         Document doc = null;
134         try {
135             directory = FSDirectory.open(new File("E:/lucene_test/01_index/"));
136             writer = new IndexWriter(directory, new IndexWriterConfig(Version.LUCENE_36, new StandardAnalyzer(Version.LUCENE_36)));
137             writer.deleteAll();
138             for (int i = 0; i < myNames.length; i++) {
139                 doc = new Document();
140                 doc.add(new Field("myname", myNames[i], Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS));
141                 doc.add(new Field("mycontent", myContents[i], Field.Store.YES, Field.Index.ANALYZED));
142                 writer.addDocument(doc);
143             }
144         }
145         catch (IOException e) {
146             e.printStackTrace();
147         }
148         finally {
149             if (null != writer) {
150                 try {
151                     writer.close();
152                 }
153                 catch (IOException ce) {
154                     ce.printStackTrace();
155                 }
156             }
157         }
158     }
159 
160     /**
161      * 获取IndexSearcher实例
162      */
163     private IndexSearcher getIndexSearcher() {
164         try {
165             if (reader == null) {
166                 reader = IndexReader.open(directory);
167             }
168             else {
169                 // if the index was changed since the provided reader was opened, open and return a new reader; else,return null
170                 // 如果当前reader在打开期间index发生改变,则打开并返回一个新的IndexReader,否则返回null
171                 IndexReader ir = IndexReader.openIfChanged(reader);
172                 if (ir != null) {
173                     reader.close(); // 关闭原reader
174                     reader = ir;    // 赋予新reader
175                 }
176             }
177             return new IndexSearcher(reader);
178         }
179         catch (Exception e) {
180             e.printStackTrace();
181         }
182         return null; // 发生异常则返回null
183     }
184 
185     /**
186      * 执行搜索操作
187      * 
188      * @param query
189      *            (搜索的Query对象)
190      */
191     private void doSearch(Query query) {
192         IndexSearcher searcher = this.getIndexSearcher();
193         try {
194             // 第二个参数指定搜索后显示的最多的记录数,其与tds.totalHits没有联系
195             TopDocs tds = searcher.search(query, 10);
196             System.out.println("本次搜索到[" + tds.totalHits + "]条记录");
197             for (ScoreDoc sd : tds.scoreDocs) {
198                 Document doc = searcher.doc(sd.doc);
199                 System.out.println("content =" + doc.get("content") + "  ");
200                 System.out.print("文档编号=" + sd.doc + "  文档权值=" + doc.getBoost() + "  文档评分=" + sd.score + "    ");
201                 System.out.print("id=" + doc.get("id") + "  email=" + doc.get("email") + "  name=" + doc.get("name") + "  ");
202                 // 获取多个同名域的方式
203                 String[] attachValues = doc.getValues("attach");
204                 for (String attach : attachValues) {
205                     System.out.print("attach=" + attach + "  ");
206                 }
207                 System.out.println();
208             }
209         }
210         catch (IOException e) {
211             e.printStackTrace();
212         }
213         finally {
214             if (null != searcher) {
215                 try {
216                     searcher.close(); // 记得关闭IndexSearcher
217                 }
218                 catch (IOException e) {
219                     e.printStackTrace();
220                 }
221             }
222         }
223     }
224 
225     /**
226      * 精确匹配搜索
227      * 
228      * @param fieldName
229      *            域名(相当于表的字段名)
230      * @param keyWords
231      *            搜索的关键字
232      */
233     public void searchByTerm(String fieldName, String keyWords) {
234         Query query = new TermQuery(new Term(fieldName, keyWords));
235         this.doSearch(query);
236     }
237 
238     /**
239      * 基于范围的搜索
240      * 
241      * @param fieldName
242      *            域名(相当于表的字段名)
243      * @param start
244      *            开始字符
245      * @param end
246      *            结束字符
247      */
248     public void searchByTermRange(String fieldName, String start, String end) {
249         Query query = new TermRangeQuery(fieldName, start, end, true, true); // 后面两个参数用于指定开区间或闭区间
250         this.doSearch(query);
251     }
252 
253     /**
254      * 针对数字的搜索
255      */
256     public void searchByNumericRange(String fieldName, int min, int max) {
257         Query query = NumericRangeQuery.newIntRange(fieldName, min, max, true, true);
258         this.doSearch(query);
259     }
260 
261     /**
262      * 基于前缀的搜索
263      * 
264      * @see 它是对Field分词后的结果进行前缀查找的结果
265      */
266     public void searchByPrefix(String fieldName, String prefix) {
267         Query query = new PrefixQuery(new Term(fieldName, prefix));
268         this.doSearch(query);
269     }
270 
271     /**
272      * 基于通配符的搜索
273      * 
274      * @see *-->任意多个字符
275      * @see ?-->一个字符
276      */
277     public void searchByWildcard(String fieldName, String wildcard) {
278         Query query = new WildcardQuery(new Term(fieldName, wildcard));
279         this.doSearch(query);
280     }
281 
282     /**
283      * 模糊搜索
284      * 
285      * @see 与通配符搜索不同
286      */
287     public void searchByFuzzy(String fieldName, String fuzzy) {
288         Query query = new FuzzyQuery(new Term(fieldName, fuzzy));
289         this.doSearch(query);
290     }
291 
292     /**
293      * 多条件搜索
294      * 
295      * @see 本例中搜索name值中以Ja开头,且content中包含am的内容
296      * @see Occur.MUST------表示此条件必须为true
297      * @see Occur.MUST_NOT--表示此条件必须为false
298      * @see Occur.SHOULD----表示此条件非必须
299      */
300     public void searchByBoolean() {
301         BooleanQuery query = new BooleanQuery();
302         query.add(new WildcardQuery(new Term("name", "Ja*")), Occur.MUST);
303         query.add(new TermQuery(new Term("content", "am")), Occur.MUST);
304         this.doSearch(query);
305     }
306 
307     /**
308      * 短语搜索
309      * 
310      * @see 很遗憾的是短语查询对中文搜索没有太大的作用,但对英文搜索是很好用的,但它的开销比较大,尽量少用
311      */
312     public void searchByPhrase() {
313         PhraseQuery query = new PhraseQuery();
314         query.setSlop(1);                          // 设置跳数
315         query.add(new Term("content", "am"));      // 第一个Term
316         query.add(new Term("content", "Haerbin")); // 产生距离之后的第二个Term
317         this.doSearch(query);
318     }
319 
320     /**
321      * 基于QueryParser的搜索
322      */
323     public void searchByQueryParse() {
324         QueryParser parser = new QueryParser(Version.LUCENE_36, "content", new StandardAnalyzer(Version.LUCENE_36));
325         Query query = null;
326         try {
327             // query = parser.parse("Haerbin"); //搜索content中包含[Haerbin]的记录
328             // query = parser.parse("I AND Haerbin"); //搜索content中包含[I]和[Haerbin]的记录
329             // query = parser.parse("Lucene OR Haerbin"); //搜索content中包含[Lucene]或者[Haerbin]的记录
330             // query = parser.parse("Lucene Haerbin"); //搜索content中包含[Lucene]或者[Haerbin]的记录
331             // parser.setDefaultOperator(Operator.AND); //将空格的默认操作OR修改为AND
332             // //1)如果name域在索引时,不进行分词,那么无论这里写成[name:Jadyer]还是[name:jadyer],最后得到的都是0条记录
333             // //2)由于name原值为大写[J],若索引时不对name分词,除非修改name原值为小写[j],并且搜索[name:jadyer]才能得到记录
334             // query = parser.parse("name:Jadyer"); //修改搜索域为name=Jadyer的记录
335             // query = parser.parse("name:Ja*"); //支持通配符
336             // query = parser.parse("\"I am\""); //搜索content中包含[I am]的记录(注意不能使用parse("content:'I am'"))
337             // parser.setAllowLeadingWildcard(true); //设置允许[*]或[?]出现在查询字符的第一位,即[name:*de],否则[name:*de]会报异常
338             // query = parser.parse("name:*de"); //Lucene默认的第一个字符不允许为通配符,因为这样效率比较低
339             // //parse("+am +name:Jade")--------------搜索content中包括[am]的,并且name=Jade的记录
340             // //parse("am AND NOT name:Jade")--------搜索content中包括[am]的,并且nam不是Jade的记录
341             // //parse("(blog OR am) AND name:Jade")--搜索content中包括[blog]或者[am]的,并且name=Jade的记录
342             // query = parser.parse("-name:Jack +I"); //搜索content中包括[I]的,并且name不是Jack的记录(加减号要放到域说明的前面)
343             // query = parser.parse("id:[1 TO 3]"); //搜索id值从1到3的记录(TO必须大写,且这种方式没有办法匹配数字)
344             // query = parser.parse("id:{1 TO 3}"); //搜索id=2的记录
345             query = parser.parse("name:Jadk~");        // 模糊搜索
346         }
347         catch (ParseException e) {
348             e.printStackTrace();
349         }
350         this.doSearch(query);
351     }
352 
353     /**
354      * 普通的分页搜索
355      * 
356      * @see 适用于lucene3.5之前
357      * @param expr
358      *            搜索表达式
359      * @param pageIndex
360      *            页码
361      * @param pageSize
362      *            分页大小
363      */
364     public void searchPage(String expr, int pageIndex, int pageSize) {
365         IndexSearcher searcher = this.getIndexSearcher();
366         QueryParser parser = new QueryParser(Version.LUCENE_36, "mycontent", new StandardAnalyzer(Version.LUCENE_36));
367         try {
368             Query query = parser.parse(expr);
369             TopDocs tds = searcher.search(query, pageIndex * pageSize);
370             ScoreDoc[] sds = tds.scoreDocs;
371             for (int i = (pageIndex - 1) * pageSize; i < pageIndex * pageSize; i++) {
372                 Document doc = searcher.doc(sds[i].doc);
373                 System.out.println("文档编号:" + sds[i].doc + "-->" + doc.get("myname") + "-->" + doc.get("mycontent"));
374             }
375         }
376         catch (Exception e) {
377             e.printStackTrace();
378         }
379         finally {
380             if (null != searcher) {
381                 try {
382                     searcher.close();
383                 }
384                 catch (IOException e) {
385                     e.printStackTrace();
386                 }
387             }
388         }
389     }
390 
391     /**
392      * 基于searchAfter的分页搜索
393      * 
394      * @see 适用于Lucene3.5
395      * @param expr
396      *            搜索表达式
397      * @param pageIndex
398      *            页码
399      * @param pageSize
400      *            分页大小
401      */
402     public void searchPageByAfter(String expr, int pageIndex, int pageSize) {
403         IndexSearcher searcher = this.getIndexSearcher();
404         QueryParser parser = new QueryParser(Version.LUCENE_36, "mycontent", new StandardAnalyzer(Version.LUCENE_36));
405         try {
406             Query query = parser.parse(expr);
407             TopDocs tds = searcher.search(query, (pageIndex - 1) * pageSize);
408             // 使用IndexSearcher.searchAfter()搜索,该方法第一个参数为上一页记录中的最后一条记录
409             if (pageIndex > 1) {
410                 tds = searcher.searchAfter(tds.scoreDocs[(pageIndex - 1) * pageSize - 1], query, pageSize);
411             }
412             else {
413                 tds = searcher.searchAfter(null, query, pageSize);
414             }
415             for (ScoreDoc sd : tds.scoreDocs) {
416                 Document doc = searcher.doc(sd.doc);
417                 System.out.println("文档编号:" + sd.doc + "-->" + doc.get("myname") + "-->" + doc.get("mycontent"));
418             }
419         }
420         catch (Exception e) {
421             e.printStackTrace();
422         }
423         finally {
424             if (null != searcher) {
425                 try {
426                     searcher.close();
427                 }
428                 catch (IOException e) {
429                     e.printStackTrace();
430                 }
431             }
432         }
433     }
434 }
View Code

  4.中文分词器

  1 import java.io.IOException;
  2 import java.io.StringReader;
  3 
  4 import org.apache.lucene.analysis.Analyzer;
  5 import org.apache.lucene.analysis.SimpleAnalyzer;
  6 import org.apache.lucene.analysis.StopAnalyzer;
  7 import org.apache.lucene.analysis.TokenStream;
  8 import org.apache.lucene.analysis.WhitespaceAnalyzer;
  9 import org.apache.lucene.analysis.standard.StandardAnalyzer;
 10 import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
 11 import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;
 12 import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
 13 import org.apache.lucene.analysis.tokenattributes.TypeAttribute;
 14 import org.apache.lucene.util.Version;
 15 
 16 import com.chenlb.mmseg4j.analysis.ComplexAnalyzer;
 17 import com.chenlb.mmseg4j.analysis.MMSegAnalyzer;
 18 
 19 /**
 20  * 【Lucene3.6.2入门系列】第04节_中文分词器
 21  * 
 22  * @see -----------------------------------------------------------------------------------------------------------------------
 23  * @see Lucene3.5推荐的四大分词器:SimpleAnalyzer,StopAnalyzer,WhitespaceAnalyzer,StandardAnalyzer
 24  * @see 这四大分词器有一个共同的抽象父类,此类有个方法public final TokenStream tokenStream(),即分词的一个流
 25  * @see 假设有这样的文本"how are you thank you",实际它是以一个java.io.Reader传进分词器中
 26  * @see Lucene分词器处理完毕后,会把整个分词转换为TokenStream,这个TokenStream中就保存所有的分词信息
 27  * @see TokenStream有两个实现类,分别为Tokenizer和TokenFilter
 28  * @see Tokenizer---->用于将一组数据划分为独立的语汇单元(即一个一个的单词)
 29  * @see TokenFilter-->过滤语汇单元
 30  * @see -----------------------------------------------------------------------------------------------------------------------
 31  * @see 分词流程
 32  * @see 1)将一组数据流java.io.Reader交给Tokenizer,由其将数据转换为一个个的语汇单元
 33  * @see 2)通过大量的TokenFilter对已经分好词的数据进行过滤操作,最后产生TokenStream
 34  * @see 3)通过TokenStream完成索引的存储
 35  * @see -----------------------------------------------------------------------------------------------------------------------
 36  * @see Tokenizer的一些子类
 37  * @see KeywordTokenizer-----不分词,传什么就索引什么
 38  * @see StandardTokenizer----标准分词,它有一些较智能的分词操作,诸如将'jadyer@yeah.net'中的'yeah.net'当作一个分词流
 39  * @see CharTokenizer--------针对字符进行控制的,它还有两个子类WhitespaceTokenizer和LetterTokenizer
 40  * @see WhitespaceTokenizer--使用空格进行分词,诸如将'Thank you,I am jadyer'会被分为4个词
 41  * @see LetterTokenizer------基于文本单词的分词,它会根据标点符号来分词,诸如将'Thank you,I am jadyer'会被分为5个词
 42  * @see LowerCaseTokenizer---它是LetterTokenizer的子类,它会将数据转为小写并分词
 43  * @see -----------------------------------------------------------------------------------------------------------------------
 44  * @see TokenFilter的一些子类
 45  * @see StopFilter--------它会停用一些语汇单元
 46  * @see LowerCaseFilter---将数据转换为小写
 47  * @see StandardFilter----对标准输出流做一些控制
 48  * @see PorterStemFilter--还原一些数据,比如将coming还原为come,将countries还原为country
 49  * @see -----------------------------------------------------------------------------------------------------------------------
 50  * @see eg:'how are you thank you'会被分词为'how','are','you','thank','you'合计5个语汇单元
 51  * @see 那么应该保存什么东西,才能使以后在需要还原数据时保证正确的还原呢???其实主要保存三个东西,如下所示
 52  * @see CharTermAttribute(Lucene3.5以前叫TermAttribute),OffsetAttribute,PositionIncrementAttribute
 53  * @see 1)CharTermAttribute-----------保存相应的词汇,这里保存的就是'how','are','you','thank','you'
 54  * @see 2)OffsetAttribute-------------保存各词汇之间的偏移量(大致理解为顺序),比如'how'的首尾字母偏移量为0和3,'are'为4和7,'thank'为12和17
 55  * @see 3)PositionIncrementAttribute--保存词与词之间的位置增量,比如'how'和'are'增量为1,'are'和'you'之间的也是1,'you'和'thank'的也是1
 56  * @see 但假设'are'是停用词(StopFilter的效果),那么'how'和'you'之间的位置增量就变成了2
 57  * @see 当我们查找某一个元素时,Lucene会先通过位置增量来取这个元素,但如果两个词的位置增量相同,会发生什么情况呢
 58  * @see 假设还有一个单词'this',它的位置增量和'how'是相同的,那么当我们在界面中搜索'this'时
 59  * @see 也会搜到'how are you thank you',这样就可以有效的做同义词了,目前非常流行的一个叫做WordNet的东西,就可以做同义词的搜索
 60  * @see -----------------------------------------------------------------------------------------------------------------------
 61  * @see 中文分词器
 62  * @see Lucene默认提供的众多分词器完全不适用中文
 63  * @see 1)Paoding--庖丁解牛分词器,官网为http://code.google.com/p/paoding(貌似已托管在http://git.oschina.net/zhzhenqin/paoding-analysis)
 64  * @see 2)MMSeg4j--据说它使用的是搜狗的词库,官网为https://code.google.com/p/mmseg4j(另外还有一个https://code.google.com/p/jcseg)
 65  * @ses 3)IK-------https://code.google.com/p/ik-analyzer/
 66  * @see -----------------------------------------------------------------------------------------------------------------------
 67  * @see MMSeg4j的使用
 68  * @see 1)下载mmseg4j-1.8.5.zip并引入mmseg4j-all-1.8.5-with-dic.jar
 69  * @see 2)在需要指定分词器的位置编写new MMSegAnalyzer()即可
 70  * @see 注1)由于使用的mmseg4j-all-1.8.5-with-dic.jar中已自带了词典,故直接new MMSegAnalyzer()即可
 71  * @see 注2)若引入的是mmseg4j-all-1.8.5.jar,则应指明词典目录,如new MMSegAnalyzer("D:\\Develop\\mmseg4j-1.8.5\\data")
 72  * @see 但若非要使用new MMSegAnalyzer(),则要将mmseg4j-1.8.5.zip自带的data目录拷入classpath下即可
 73  * @see 总结:直接引入mmseg4j-all-1.8.5-with-dic.jar就行了
 74  * @see -----------------------------------------------------------------------------------------------------------------------
 75  * @create Aug 2, 2013 5:30:45 PM
 76  * @author 玄玉<http://blog.csdn.net/jadyer>
 77  */
 78 public class Lucene_04_HelloChineseAnalyzer {
 79     /**
 80      * 查看分词信息
 81      * 
 82      * @see TokenStream还有两个属性,分别为FlagsAttribute和PayloadAttribute,都是开发时用的
 83      * @see FlagsAttribute----标注位属性
 84      * @see PayloadAttribute--做负载的属性,用来检测是否已超过负载,超过则可以决定是否停止搜索等等
 85      * @param txt
 86      *            待分词的字符串
 87      * @param analyzer
 88      *            所使用的分词器
 89      * @param displayAll
 90      *            是否显示所有的分词信息
 91      */
 92     public static void displayTokenInfo(String txt, Analyzer analyzer, boolean displayAll) {
 93         // 第一个参数没有任何意义,可以随便传一个值,它只是为了显示分词
 94         // 这里就是使用指定的分词器将'txt'分词,分词后会产生一个TokenStream(可将分词后的每个单词理解为一个Token)
 95         TokenStream stream = analyzer.tokenStream("此参数无意义", new StringReader(txt));
 96         // 用于查看每一个语汇单元的信息,即分词的每一个元素
 97         // 这里创建的属性会被添加到TokenStream流中,并随着TokenStream而增加(此属性就是用来装载每个Token的,即分词后的每个单词)
 98         // 当调用TokenStream.incrementToken()时,就会指向到这个单词流中的第一个单词,即此属性代表的就是分词后的第一个单词
 99         // 可以形象的理解成一只碗,用来盛放TokenStream中每个单词的碗,每调用一次incrementToken()后,这个碗就会盛放流中的下一个单词
100         CharTermAttribute cta = stream.addAttribute(CharTermAttribute.class);
101         // 用于查看位置增量(指的是语汇单元之间的距离,可理解为元素与元素之间的空格,即间隔的单元数)
102         PositionIncrementAttribute pia = stream.addAttribute(PositionIncrementAttribute.class);
103         // 用于查看每个语汇单元的偏移量
104         OffsetAttribute oa = stream.addAttribute(OffsetAttribute.class);
105         // 用于查看使用的分词器的类型信息
106         TypeAttribute ta = stream.addAttribute(TypeAttribute.class);
107         try {
108             if (displayAll) {
109                 // 等价于while(stream.incrementToken())
110                 for (; stream.incrementToken();) {
111                     System.out.println(ta.type() + " " + pia.getPositionIncrement() + " [" + oa.startOffset() + "-" + oa.endOffset() + "] [" + cta + "]");
112                 }
113             }
114             else {
115                 System.out.println();
116                 while (stream.incrementToken()) {
117                     System.out.print("[" + cta + "]");
118                 }
119             }
120         }
121         catch (IOException e) {
122             e.printStackTrace();
123         }
124     }
125 
126     /**
127      * 测试一下中文分词的效果
128      */
129     public static void main(String[] args) {
130         String txt = "测试一下中文分词的效果";
131         // displayTokenInfo(txt, new StandardAnalyzer(Version.LUCENE_36), false);
132         // displayTokenInfo(txt, new StopAnalyzer(Version.LUCENE_36), false);
133         // displayTokenInfo(txt, new SimpleAnalyzer(Version.LUCENE_36), false);
134         // displayTokenInfo(txt, new WhitespaceAnalyzer(Version.LUCENE_36), false);
135         displayTokenInfo(txt, new MMSegAnalyzer(), true);
136         // displayTokenInfo(txt, new SimpleAnalyzer(), false);
137         // displayTokenInfo(txt, new ComplexAnalyzer(), false);
138     }
139 }
View Code

  5.高级搜索之排序

  1 import java.io.File;
  2 import java.io.IOException;
  3 import java.text.SimpleDateFormat;
  4 import java.util.Date;
  5 
  6 import org.apache.lucene.analysis.standard.StandardAnalyzer;
  7 import org.apache.lucene.document.Document;
  8 import org.apache.lucene.document.Field;
  9 import org.apache.lucene.document.NumericField;
 10 import org.apache.lucene.index.IndexReader;
 11 import org.apache.lucene.index.IndexWriter;
 12 import org.apache.lucene.index.IndexWriterConfig;
 13 import org.apache.lucene.queryParser.QueryParser;
 14 import org.apache.lucene.search.IndexSearcher;
 15 import org.apache.lucene.search.ScoreDoc;
 16 import org.apache.lucene.search.Sort;
 17 import org.apache.lucene.search.SortField;
 18 import org.apache.lucene.search.TopDocs;
 19 import org.apache.lucene.store.Directory;
 20 import org.apache.lucene.store.FSDirectory;
 21 import org.apache.lucene.util.Version;
 22 
 23 /**
 24  * 【Lucene3.6.2入门系列】第06节_高级搜索之排序
 25  * 
 26  * @create Aug 19, 2013 10:38:19 AM
 27  * @author 玄玉<http://blog.csdn.net/jadyer>
 28  */
 29 public class Lucene_05_AdvancedSearchBySort {
 30     private Directory directory;
 31     private IndexReader reader;
 32 
 33     public Lucene_05_AdvancedSearchBySort() {
 34         /** 文件大小 */
 35         int[] sizes = { 90, 10, 20, 10, 60, 50 };
 36         /** 文件名 */
 37         String[] names = { "Michael.java", "Scofield.ini", "Tbag.txt", "Jack", "Jade", "Jadyer" };
 38         /** 文件内容 */
 39         String[] contents = { "my java blog is http://blog.csdn.net/jadyer", "my Java Website is http://www.jadyer.cn", "my name is jadyer", "I am a Java Developer", "I am from Haerbin", "I like java of Lucene" };
 40         /** 文件日期 */
 41         Date[] dates = new Date[sizes.length];
 42         SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd HH:mm:ss");
 43         IndexWriter writer = null;
 44         Document doc = null;
 45         try {
 46             dates[0] = sdf.parse("20130407 15:25:30");
 47             dates[1] = sdf.parse("20130407 16:30:45");
 48             dates[2] = sdf.parse("20130213 11:15:25");
 49             dates[3] = sdf.parse("20130808 09:30:55");
 50             dates[4] = sdf.parse("20130526 13:54:22");
 51             dates[5] = sdf.parse("20130701 17:35:34");
 52             directory = FSDirectory.open(new File("E:/lucene_test/01_index/"));
 53             writer = new IndexWriter(directory, new IndexWriterConfig(Version.LUCENE_36, new StandardAnalyzer(Version.LUCENE_36)));
 54             writer.deleteAll();
 55             for (int i = 0; i < sizes.length; i++) {
 56                 doc = new Document();
 57                 doc.add(new NumericField("size", Field.Store.YES, true).setIntValue(sizes[i]));
 58                 doc.add(new Field("name", names[i], Field.Store.YES, Field.Index.ANALYZED_NO_NORMS));
 59                 doc.add(new Field("content", contents[i], Field.Store.YES, Field.Index.ANALYZED));
 60                 doc.add(new NumericField("date", Field.Store.YES, true).setLongValue(dates[i].getTime()));
 61                 writer.addDocument(doc);
 62             }
 63         }
 64         catch (Exception e) {
 65             e.printStackTrace();
 66         }
 67         finally {
 68             if (null != writer) {
 69                 try {
 70                     writer.close();
 71                 }
 72                 catch (IOException ce) {
 73                     ce.printStackTrace();
 74                 }
 75             }
 76         }
 77     }
 78 
 79     /**
 80      * 获取IndexReader实例
 81      */
 82     private IndexReader getIndexReader() {
 83         try {
 84             if (reader == null) {
 85                 reader = IndexReader.open(directory);
 86             }
 87             else {
 88                 // if the index was changed since the provided reader was opened, open and return a new reader; else,return null
 89                 // 如果当前reader在打开期间index发生改变,则打开并返回一个新的IndexReader,否则返回null
 90                 IndexReader ir = IndexReader.openIfChanged(reader);
 91                 if (ir != null) {
 92                     reader.close(); // 关闭原reader
 93                     reader = ir;    // 赋予新reader
 94                 }
 95             }
 96             return reader;
 97         }
 98         catch (Exception e) {
 99             e.printStackTrace();
100         }
101         return null; // 发生异常则返回null
102     }
103 
104     /**
105      * 搜索排序
106      * 
107      * @see 关于Sort参数的可输入规则,如下所示
108      * @see 1)Sort.INDEXORDER--使用文档编号从小到大的顺序进行排序
109      * @see 2)Sort.RELEVANCE---使用文档评分从大到小的顺序进行排序,也是默认的排序规则,等价于search(query, 10)
110      * @see 3)new Sort(new SortField("size", SortField.INT))-----------使用文件大小从小到大的顺序排序
111      * @see 4)new Sort(new SortField("date", SortField.LONG))----------使用文件日期从以前到现在的顺序排序
112      * @see 5)new Sort(new SortField("name", SortField.STRING))--------使用文件名从A到Z的顺序排序
113      * @see 6)new Sort(new SortField("name", SortField.STRING, true))--使用文件名从Z到A的顺序排序
114      * @see 7)new Sort(new SortField("size", SortField.INT), SortField.FIELD_SCORE)--先按照文件大小排序,再按照文档评分排序(可以指定多个排序规则)
115      * @see 注意:以上7个Sort再打印文档评分时都是NaN,只有search(query, 10)才会正确打印文档评分
116      * @param expr
117      *            搜索表达式
118      * @param sort
119      *            排序规则
120      */
121     public void searchBySort(String expr, Sort sort) {
122         IndexSearcher searcher = new IndexSearcher(this.getIndexReader());
123         QueryParser parser = new QueryParser(Version.LUCENE_36, "content", new StandardAnalyzer(Version.LUCENE_36));
124         TopDocs tds = null;
125         try {
126             if (null == sort) {
127                 tds = searcher.search(parser.parse(expr), 10);
128             }
129             else {
130                 tds = searcher.search(parser.parse(expr), 10, sort);
131             }
132             for (ScoreDoc sd : tds.scoreDocs) {
133                 Document doc = searcher.doc(sd.doc);
134                 System.out.println("content=" + doc.get("content"));
135                 System.out.print("文档编号=" + sd.doc + "  文档权值=" + doc.getBoost() + "  文档评分=" + sd.score + "    ");
136                 System.out.println("size=" + doc.get("size") + "  date=" + new SimpleDateFormat("yyyyMMdd HH:mm:ss").format(new Date(Long.parseLong(doc.get("date")))) + "  name=" + doc.get("name"));
137             }
138         }
139         catch (Exception e) {
140             e.printStackTrace();
141         }
142         finally {
143             if (searcher != null) {
144                 try {
145                     searcher.close();
146                 }
147                 catch (IOException e) {
148                     e.printStackTrace();
149                 }
150             }
151         }
152     }
153 
154     /**
155      * 测试一下排序效果
156      */
157     public static void main(String[] args) {
158         Lucene_05_AdvancedSearchBySort advancedSearch = new Lucene_05_AdvancedSearchBySort();
159         // //使用文档评分从大到小的顺序进行排序,也是默认的排序规则
160         // advancedSearch.searchBySort("Java", null);
161         // advancedSearch.searchBySort("Java", Sort.RELEVANCE);
162         // //使用文档编号从小到大的顺序进行排序
163         // advancedSearch.searchBySort("Java", Sort.INDEXORDER);
164         // //使用文件大小从小到大的顺序排序
165         // advancedSearch.searchBySort("Java", new Sort(new SortField("size", SortField.INT)));
166         // //使用文件日期从以前到现在的顺序排序
167         // advancedSearch.searchBySort("Java", new Sort(new SortField("date", SortField.LONG)));
168         // //使用文件名从A到Z的顺序排序
169         // advancedSearch.searchBySort("Java", new Sort(new SortField("name", SortField.STRING)));
170         // //使用文件名从Z到A的顺序排序
171         // advancedSearch.searchBySort("Java", new Sort(new SortField("name", SortField.STRING, true)));
172         // 先按照文件大小排序,再按照文档评分排序(可以指定多个排序规则)
173         advancedSearch.searchBySort("Java", new Sort(new SortField("size", SortField.INT), SortField.FIELD_SCORE));
174     }
175 }
View Code

  6.高级搜索之普通Filter和自定义Filter

  1 import java.io.File;
  2 import java.io.IOException;
  3 import java.text.ParseException;
  4 import java.text.SimpleDateFormat;
  5 import java.util.Date;
  6 
  7 import org.apache.lucene.analysis.standard.StandardAnalyzer;
  8 import org.apache.lucene.document.Document;
  9 import org.apache.lucene.document.Field;
 10 import org.apache.lucene.document.NumericField;
 11 import org.apache.lucene.index.IndexReader;
 12 import org.apache.lucene.index.IndexWriter;
 13 import org.apache.lucene.index.IndexWriterConfig;
 14 import org.apache.lucene.index.Term;
 15 import org.apache.lucene.index.TermDocs;
 16 import org.apache.lucene.queryParser.QueryParser;
 17 import org.apache.lucene.search.DocIdSet;
 18 import org.apache.lucene.search.Filter;
 19 import org.apache.lucene.search.IndexSearcher;
 20 import org.apache.lucene.search.NumericRangeFilter;
 21 import org.apache.lucene.search.ScoreDoc;
 22 import org.apache.lucene.search.TopDocs;
 23 import org.apache.lucene.store.Directory;
 24 import org.apache.lucene.store.FSDirectory;
 25 import org.apache.lucene.util.OpenBitSet;
 26 import org.apache.lucene.util.Version;
 27 
 28 /**
 29  * 【Lucene3.6.2入门系列】第07节_高级搜索之普通Filter和自定义Filter
 30  * 
 31  * @create Aug 19, 2013 11:13:40 AM
 32  * @author 玄玉<http://blog.csdn.net/jadyer>
 33  */
 34 public class Lucene_06_AdvancedSearchByFilter {
 35     private Directory directory;
 36     private IndexReader reader;
 37 
 38     /**
 39      * 测试一下过滤效果
 40      */
 41     public static void main(String[] args) throws ParseException {
 42         Lucene_06_AdvancedSearchByFilter advancedSearch = new Lucene_06_AdvancedSearchByFilter();
 43         // //过滤文件名首字母从'h'到'n'的记录(注意hn要小写)
 44         // advancedSearch.searchByFilter("Java", new TermRangeFilter("name", "h", "n", true, true));
 45         // //过滤文件大小在30到80以内的记录
 46         // advancedSearch.searchByFilter("Java", NumericRangeFilter.newIntRange("size", 30, 80, true, true));
 47         // //过滤文件日期在20130701 00:00:00到20130808 23:59:59之间的记录
 48         // Long min = Long.valueOf(new SimpleDateFormat("yyyyMMdd").parse("20130701").getTime());
 49         // Long max = Long.valueOf(new SimpleDateFormat("yyyyMMdd HH:mm:ss").parse("20130808 23:59:59").getTime());
 50         // advancedSearch.searchByFilter("Java", NumericRangeFilter.newLongRange("date", min, max, true, true));
 51         // //过滤文件名以'ja'打头的(注意ja要小写)
 52         // advancedSearch.searchByFilter("Java", new QueryWrapperFilter(new WildcardQuery(new Term("name", "ja*"))));
 53         // 自定义Filter
 54         advancedSearch.searchByFilter("Java", advancedSearch.new MyFilter());
 55     }
 56 
 57     public Lucene_06_AdvancedSearchByFilter() {
 58         /** 文件大小 */
 59         int[] sizes = { 90, 10, 20, 10, 60, 50 };
 60         /** 文件名 */
 61         String[] names = { "Michael.java", "Scofield.ini", "Tbag.txt", "Jack", "Jade", "Jadyer" };
 62         /** 文件内容 */
 63         String[] contents = { "my java blog is http://blog.csdn.net/jadyer", "my Java Website is http://www.jadyer.cn", "my name is jadyer", "I am a Java Developer", "I am from Haerbin", "I like java of Lucene" };
 64         /** 文件日期 */
 65         Date[] dates = new Date[sizes.length];
 66         SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd HH:mm:ss");
 67         IndexWriter writer = null;
 68         Document doc = null;
 69         try {
 70             dates[0] = sdf.parse("20130407 15:25:30");
 71             dates[1] = sdf.parse("20130407 16:30:45");
 72             dates[2] = sdf.parse("20130213 11:15:25");
 73             dates[3] = sdf.parse("20130808 09:30:55");
 74             dates[4] = sdf.parse("20130526 13:54:22");
 75             dates[5] = sdf.parse("20130701 17:35:34");
 76             directory = FSDirectory.open(new File("myExample/01_index/"));
 77             writer = new IndexWriter(directory, new IndexWriterConfig(Version.LUCENE_36, new StandardAnalyzer(Version.LUCENE_36)));
 78             writer.deleteAll();
 79             for (int i = 0; i < sizes.length; i++) {
 80                 doc = new Document();
 81                 doc.add(new NumericField("size", Field.Store.YES, true).setIntValue(sizes[i]));
 82                 doc.add(new Field("name", names[i], Field.Store.YES, Field.Index.ANALYZED_NO_NORMS));
 83                 doc.add(new Field("content", contents[i], Field.Store.NO, Field.Index.ANALYZED));
 84                 doc.add(new NumericField("date", Field.Store.YES, true).setLongValue(dates[i].getTime()));
 85                 // 为每个文档添加一个fileID(与ScoreDoc.doc不同),专门在自定义Filter时使用
 86                 doc.add(new Field("fileID", String.valueOf(i), Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS));
 87                 writer.addDocument(doc);
 88             }
 89         }
 90         catch (Exception e) {
 91             e.printStackTrace();
 92         }
 93         finally {
 94             if (null != writer) {
 95                 try {
 96                     writer.close();
 97                 }
 98                 catch (IOException ce) {
 99                     ce.printStackTrace();
100                 }
101             }
102         }
103     }
104 
105     /**
106      * 获取IndexReader实例
107      */
108     private IndexReader getIndexReader() {
109         try {
110             if (reader == null) {
111                 reader = IndexReader.open(directory);
112             }
113             else {
114                 // if the index was changed since the provided reader was opened, open and return a new reader; else,return null
115                 // 如果当前reader在打开期间index发生改变,则打开并返回一个新的IndexReader,否则返回null
116                 IndexReader ir = IndexReader.openIfChanged(reader);
117                 if (ir != null) {
118                     reader.close(); // 关闭原reader
119                     reader = ir;    // 赋予新reader
120                 }
121             }
122             return reader;
123         }
124         catch (Exception e) {
125             e.printStackTrace();
126         }
127         return null; // 发生异常则返回null
128     }
129 
130     /**
131      * 搜索过滤
132      */
133     public void searchByFilter(String expr, Filter filter) {
134         IndexSearcher searcher = new IndexSearcher(this.getIndexReader());
135         QueryParser parser = new QueryParser(Version.LUCENE_36, "content", new StandardAnalyzer(Version.LUCENE_36));
136         TopDocs tds = null;
137         try {
138             if (null == filter) {
139                 tds = searcher.search(parser.parse(expr), 10);
140             }
141             else {
142                 tds = searcher.search(parser.parse(expr), filter, 10);
143             }
144             for (ScoreDoc sd : tds.scoreDocs) {
145                 Document doc = searcher.doc(sd.doc);
146                 System.out.print("文档编号=" + sd.doc + "  文档权值=" + doc.getBoost() + "  文档评分=" + sd.score + "    ");
147                 System.out.println("fileID=" + doc.get("fileID") + "  size=" + doc.get("size") + "  date=" + new SimpleDateFormat("yyyyMMdd HH:mm:ss").format(new Date(Long.parseLong(doc.get("date")))) + "  name=" + doc.get("name"));
148             }
149         }
150         catch (Exception e) {
151             e.printStackTrace();
152         }
153         finally {
154             if (searcher != null) {
155                 try {
156                     searcher.close();
157                 }
158                 catch (IOException e) {
159                     e.printStackTrace();
160                 }
161             }
162         }
163     }
164 
165     /**
166      * 自定义Filter
167      * 
168      * @see ------------------------------------------------------------------------------------------
169      * @see 本例的应用场景
170      * @see 假设很多的数据,然后删除了其中的某几条数据,此时在接受搜索请求时为保证不会搜索到已删除的数据
171      * @see 那么可以更新索引,但更新索引会消耗很多时间(因为数据量大),而又要保证已删除的数据不会被搜索到
172      * @see 此时就可以自定义Filter,原理即搜索过程中,当发现此记录为已删除记录,则不添加到返回的搜索结果集中
173      * @see ------------------------------------------------------------------------------------------
174      * @see 自定义Filter步骤如下
175      * @see 1)继承Filter类并重写getDocIdSet()方法
176      * @see 2)根据实际过滤要求返回新的DocIdSet对象
177      * @see ------------------------------------------------------------------------------------------
178      * @see DocIdSet小解
179      * @see 这里Filter干的活其实就是创建一个DocIdSet,而DocIdSet其实就是一个数组,可以理解为其中只存放0或1的值
180      * @see 每个搜索出来的Document都有一个文档编号,所以搜索出来多少个Document,那么DocIdSet中就会有多少条记录
181      * @see 而DocIdSet中每一条记录的索引号与文档编号是一一对应的
182      * @see 所以当DocIdSet中的记录为1时,则对应文档编号的Document就会被添加到TopDocs中,为0就会被过滤掉
183      * @see ------------------------------------------------------------------------------------------
184      * @create Aug 6, 2013 7:28:53 PM
185      * @author 玄玉<http://blog.csdn.net/jadyer>
186      */
187     class MyFilter extends Filter {
188         private static final long serialVersionUID = -8955061358165068L;
189 
190         // 假设这是已删除记录的fileID值的集合
191         private String[] deleteFileIDs = { "1", "3" };
192 
193         @Override
194         public DocIdSet getDocIdSet(IndexReader reader) throws IOException {
195             // 创建一个DocIdSet的子类OpenBitSet(创建之后默认所有元素都是0),传的参数就是本次"搜索到的"元素数目
196             OpenBitSet obs = new OpenBitSet(reader.maxDoc());
197             // 先把元素填满,即全部设置为1
198             obs.set(0, reader.maxDoc());
199             // 用于保存已删除元素的文档编号
200             int[] docs = new int[1];
201             for (String deleteDataID : deleteFileIDs) {
202                 // 获取已删除元素对应的TermDocs
203                 TermDocs tds = reader.termDocs(new Term("fileID", deleteDataID));
204                 // 将已删除元素的文档编号放到docs中,将其出现的频率放到freqs中,最后返回查询出来的元素数目
205                 int count = tds.read(docs, new int[1]);
206                 if (count == 1) {
207                     // 将这个位置docs[0]的元素删除
208                     obs.clear(docs[0]);
209                 }
210             }
211             return obs;
212         }
213     }
214 
215 }
View Code

  7.高级搜索之自定义QueryParser

  1 import java.io.File;
  2 import java.io.IOException;
  3 import java.text.SimpleDateFormat;
  4 import java.util.Date;
  5 import java.util.regex.Pattern;
  6 
  7 import org.apache.lucene.analysis.Analyzer;
  8 import org.apache.lucene.analysis.standard.StandardAnalyzer;
  9 import org.apache.lucene.document.Document;
 10 import org.apache.lucene.document.Field;
 11 import org.apache.lucene.document.NumericField;
 12 import org.apache.lucene.index.IndexReader;
 13 import org.apache.lucene.index.IndexWriter;
 14 import org.apache.lucene.index.IndexWriterConfig;
 15 import org.apache.lucene.queryParser.ParseException;
 16 import org.apache.lucene.queryParser.QueryParser;
 17 import org.apache.lucene.search.IndexSearcher;
 18 import org.apache.lucene.search.NumericRangeQuery;
 19 import org.apache.lucene.search.Query;
 20 import org.apache.lucene.search.ScoreDoc;
 21 import org.apache.lucene.search.TopDocs;
 22 import org.apache.lucene.store.Directory;
 23 import org.apache.lucene.store.FSDirectory;
 24 import org.apache.lucene.util.Version;
 25 
 26 /**
 27  * 【Lucene3.6.2入门系列】第09节_高级搜索之自定义QueryParser
 28  * 
 29  * @create Aug 19, 2013 2:07:32 PM
 30  * @author 玄玉<http://blog.csdn.net/jadyer>
 31  */
 32 public class Lucene_07_AdvancedSearch {
 33     private Directory directory;
 34     private IndexReader reader;
 35 
 36     /**
 37      * 测试一下搜索效果
 38      */
 39     public static void main(String[] args) {
 40         Lucene_07_AdvancedSearch advancedSearch = new Lucene_07_AdvancedSearch();
 41         advancedSearch.searchByCustomQueryParser("name:Jadk~");
 42         advancedSearch.searchByCustomQueryParser("name:Ja??er");
 43         System.out.println("------------------------------------------------------------------------");
 44         advancedSearch.searchByCustomQueryParser("name:Jade");
 45         System.out.println("------------------------------------------------------------------------");
 46         advancedSearch.searchByCustomQueryParser("name:[h TO n]");
 47         System.out.println("------------------------------------------------------------------------");
 48         advancedSearch.searchByCustomQueryParser("size:[20 TO 80]");
 49         System.out.println("------------------------------------------------------------------------");
 50         advancedSearch.searchByCustomQueryParser("date:[20130407 TO 20130701]");
 51     }
 52 
 53     public Lucene_07_AdvancedSearch() {
 54         /** 文件大小 */
 55         int[] sizes = { 90, 10, 20, 10, 60, 50 };
 56         /** 文件名 */
 57         String[] names = { "Michael.java", "Scofield.ini", "Tbag.txt", "Jack", "Jade", "Jadyer" };
 58         /** 文件内容 */
 59         String[] contents = { "my java blog is http://blog.csdn.net/jadyer", "my Java Website is http://www.jadyer.cn", "my name is jadyer", "I am a Java Developer", "I am from Haerbin", "I like java of Lucene" };
 60         /** 文件日期 */
 61         Date[] dates = new Date[sizes.length];
 62         SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd HH:mm:ss");
 63         IndexWriter writer = null;
 64         Document doc = null;
 65         try {
 66             dates[0] = sdf.parse("20130407 15:25:30");
 67             dates[1] = sdf.parse("20130407 16:30:45");
 68             dates[2] = sdf.parse("20130213 11:15:25");
 69             dates[3] = sdf.parse("20130808 09:30:55");
 70             dates[4] = sdf.parse("20130526 13:54:22");
 71             dates[5] = sdf.parse("20130701 17:35:34");
 72             directory = FSDirectory.open(new File("E:/lucene_test/01_index/"));
 73             writer = new IndexWriter(directory, new IndexWriterConfig(Version.LUCENE_36, new StandardAnalyzer(Version.LUCENE_36)));
 74             writer.deleteAll();
 75             for (int i = 0; i < sizes.length; i++) {
 76                 doc = new Document();
 77                 doc.add(new NumericField("size", Field.Store.YES, true).setIntValue(sizes[i]));
 78                 doc.add(new Field("name", names[i], Field.Store.YES, Field.Index.ANALYZED_NO_NORMS));
 79                 doc.add(new Field("content", contents[i], Field.Store.NO, Field.Index.ANALYZED));
 80                 doc.add(new NumericField("date", Field.Store.YES, true).setLongValue(dates[i].getTime()));
 81                 writer.addDocument(doc);
 82             }
 83         }
 84         catch (Exception e) {
 85             e.printStackTrace();
 86         }
 87         finally {
 88             if (null != writer) {
 89                 try {
 90                     writer.close();
 91                 }
 92                 catch (IOException ce) {
 93                     ce.printStackTrace();
 94                 }
 95             }
 96         }
 97     }
 98 
 99     /**
100      * 获取IndexReader实例
101      */
102     private IndexReader getIndexReader() {
103         try {
104             if (reader == null) {
105                 reader = IndexReader.open(directory);
106             }
107             else {
108                 // if the index was changed since the provided reader was opened, open and return a new reader; else,return null
109                 // 如果当前reader在打开期间index发生改变,则打开并返回一个新的IndexReader,否则返回null
110                 IndexReader ir = IndexReader.openIfChanged(reader);
111                 if (ir != null) {
112                     reader.close(); // 关闭原reader
113                     reader = ir;    // 赋予新reader
114                 }
115             }
116             return reader;
117         }
118         catch (Exception e) {
119             e.printStackTrace();
120         }
121         return null; // 发生异常则返回null
122     }
123 
124     /**
125      * 自定义QueryParser的搜索
126      * 
127      * @param expr
128      *            搜索的表达式
129      */
130     public void searchByCustomQueryParser(String expr) {
131         IndexSearcher searcher = new IndexSearcher(this.getIndexReader());
132         QueryParser parser = new MyQueryParser(Version.LUCENE_36, "content", new StandardAnalyzer(Version.LUCENE_36));
133         try {
134             Query query = parser.parse(expr);
135             TopDocs tds = searcher.search(query, 10);
136             for (ScoreDoc sd : tds.scoreDocs) {
137                 Document doc = searcher.doc(sd.doc);
138                 System.out.print("文档编号=" + sd.doc + "  文档权值=" + doc.getBoost() + "  文档评分=" + sd.score + "    ");
139                 System.out.println("size=" + doc.get("size") + "  date=" + new SimpleDateFormat("yyyyMMdd HH:mm:ss").format(new Date(Long.parseLong(doc.get("date")))) + "  name=" + doc.get("name"));
140             }
141         }
142         catch (ParseException e) {
143             System.err.println(e.getMessage());
144         }
145         catch (Exception e) {
146             e.printStackTrace();
147         }
148         finally {
149             if (null != searcher) {
150                 try {
151                     searcher.close(); // 记得关闭IndexSearcher
152                 }
153                 catch (IOException e) {
154                     e.printStackTrace();
155                 }
156             }
157         }
158     }
159 
160     /**
161      * 自定义QueryParser
162      * 
163      * @see --------------------------------------------------------------------------------------------------
164      * @see 实际使用QueryParser的过程中,通常会考虑两个问题
165      * @see 1)限制性能低的QueryParser--对于某些QueryParser在搜索时会使得性能降低,故考虑禁用这些搜索以提升性能
166      * @see 2)扩展基于数字和日期的搜索---有时需要进行一个数字的范围搜索,故需扩展原有的QueryParser才能实现此搜索
167      * @see --------------------------------------------------------------------------------------------------
168      * @see 限制性能低的QueryParser
169      * @see 继承QueryParser类并重载相应方法,比如getFuzzyQuery和getWildcardQuery
170      * @see 这样造成的结果就是,当输入普通的搜索表达式时,如'I AND Haerbin'可以正常搜索
171      * @see 但输入'name:Jadk~'或者'name:Ja??er'时,就会执行到重载方法中,这时就可以自行处理了,比如本例中禁止该功能
172      * @see --------------------------------------------------------------------------------------------------
173      * @see 扩展基于数字和日期的查询
174      * @see 思路就是继承QueryParser类后重载getRangeQuery()方法
175      * @see 再针对数字和日期的'域',做特殊处理(使用NumericRangeQuery.newIntRange()方法来搜索)
176      * @see --------------------------------------------------------------------------------------------------
177      * @create Aug 6, 2013 4:13:42 PM
178      * @author 玄玉<http://blog.csdn.net/jadyer>
179      */
180     public class MyQueryParser extends QueryParser {
181         public MyQueryParser(Version matchVersion, String f, Analyzer a) {
182             super(matchVersion, f, a);
183         }
184 
185         @Override
186         protected Query getWildcardQuery(String field, String termStr) throws ParseException {
187             throw new ParseException("由于性能原因,已禁用通配符搜索,请输入更精确的信息进行搜索 ^_^ ^_^");
188         }
189 
190         @Override
191         protected Query getFuzzyQuery(String field, String termStr, float minSimilarity) throws ParseException {
192             throw new ParseException("由于性能原因,已禁用模糊搜索,请输入更精确的信息进行搜索 ^_^ ^_^");
193         }
194 
195         @Override
196         protected Query getRangeQuery(String field, String part1, String part2, boolean inclusive) throws ParseException {
197             if (field.equals("size")) {
198                 // 默认的QueryParser.parse(String query)表达式中并不支持'size:[20 TO 80]'数字的域值
199                 // 这样一来,针对数字的域值进行特殊处理,那么QueryParser表达式就支持数字了
200                 return NumericRangeQuery.newIntRange(field, Integer.parseInt(part1), Integer.parseInt(part2), inclusive, inclusive);
201             }
202             else if (field.equals("date")) {
203                 String regex = "\\d{8}";
204                 String dateType = "yyyyMMdd";
205                 if (Pattern.matches(regex, part1) && Pattern.matches(regex, part2)) {
206                     SimpleDateFormat sdf = new SimpleDateFormat(dateType);
207                     try {
208                         long min = sdf.parse(part1).getTime();
209                         long max = sdf.parse(part2).getTime();
210                         // 使之支持日期的检索,应用时直接QueryParser.parse("date:[20130407 TO 20130701]")
211                         return NumericRangeQuery.newLongRange(field, min, max, inclusive, inclusive);
212                     }
213                     catch (java.text.ParseException e) {
214                         e.printStackTrace();
215                     }
216                 }
217                 else {
218                     throw new ParseException("Unknown date format, please use '" + dateType + "'");
219                 }
220             }
221             // 如没找到匹配的Field域,那么返回默认的TermRangeQuery
222             return super.getRangeQuery(field, part1, part2, inclusive);
223         }
224     }
225 }
View Code

  8.高亮

  1 import java.io.File;
  2 import java.io.IOException;
  3 
  4 import org.apache.lucene.analysis.Analyzer;
  5 import org.apache.lucene.document.Document;
  6 import org.apache.lucene.document.Field;
  7 import org.apache.lucene.index.IndexReader;
  8 import org.apache.lucene.index.IndexWriter;
  9 import org.apache.lucene.index.IndexWriterConfig;
 10 import org.apache.lucene.queryParser.MultiFieldQueryParser;
 11 import org.apache.lucene.queryParser.QueryParser;
 12 import org.apache.lucene.search.IndexSearcher;
 13 import org.apache.lucene.search.Query;
 14 import org.apache.lucene.search.ScoreDoc;
 15 import org.apache.lucene.search.TopDocs;
 16 import org.apache.lucene.search.highlight.Formatter;
 17 import org.apache.lucene.search.highlight.Fragmenter;
 18 import org.apache.lucene.search.highlight.Highlighter;
 19 import org.apache.lucene.search.highlight.QueryScorer;
 20 import org.apache.lucene.search.highlight.SimpleHTMLFormatter;
 21 import org.apache.lucene.search.highlight.SimpleSpanFragmenter;
 22 import org.apache.lucene.store.Directory;
 23 import org.apache.lucene.store.FSDirectory;
 24 import org.apache.lucene.util.Version;
 25 import org.apache.tika.Tika;
 26 
 27 import com.chenlb.mmseg4j.analysis.MMSegAnalyzer;
 28 
 29 /**
 30  * 【Lucene3.6.2入门系列】第11节_高亮
 31  * 
 32  * @see 高亮功能属于Lucene的扩展功能(或者叫做贡献功能)
 33  * @see 其所需jar位于Lucene-3.6.2.zip中的/contrib/highlighter/文件夹中
 34  * @see 本例中需要以下4个jar
 35  * @see lucene-core-3.6.2.jar
 36  * @see lucene-highlighter-3.6.2.jar
 37  * @see mmseg4j-all-1.8.5-with-dic.jar
 38  * @see tika-app-1.4.jar
 39  * @create Aug 7, 2013 11:37:10 AM
 40  * @author 玄玉<http://blog.csdn.net/jadyer>
 41  */
 42 public class Lucene_08_HelloHighLighter {
 43     private Directory directory;
 44     private IndexReader reader;
 45 
 46     public Lucene_08_HelloHighLighter() {
 47         Document doc = null;
 48         IndexWriter writer = null;
 49         try {
 50             directory = FSDirectory.open(new File("E:/lucene_test/01_index/"));
 51             writer = new IndexWriter(directory, new IndexWriterConfig(Version.LUCENE_36, new MMSegAnalyzer()));
 52             writer.deleteAll();
 53             for (File myFile : new File("E:/lucene_test/01_index/").listFiles()) {
 54                 doc = new Document();
 55                 doc.add(new Field("filecontent", new Tika().parse(myFile))); // Field.Store.NO,Field.Index.ANALYZED
 56                 doc.add(new Field("filepath", myFile.getAbsolutePath(), Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS));
 57                 writer.addDocument(doc);
 58             }
 59         }
 60         catch (Exception e) {
 61             e.printStackTrace();
 62         }
 63         finally {
 64             if (null != writer) {
 65                 try {
 66                     writer.close();
 67                 }
 68                 catch (IOException ce) {
 69                     ce.printStackTrace();
 70                 }
 71             }
 72         }
 73     }
 74 
 75     /**
 76      * 获取IndexSearcher实例
 77      */
 78     private IndexSearcher getIndexSearcher() {
 79         try {
 80             if (reader == null) {
 81                 reader = IndexReader.open(directory);
 82             }
 83             else {
 84                 // if the index was changed since the provided reader was opened, open and return a new reader; else,return null
 85                 // 如果当前reader在打开期间index发生改变,则打开并返回一个新的IndexReader,否则返回null
 86                 IndexReader ir = IndexReader.openIfChanged(reader);
 87                 if (ir != null) {
 88                     reader.close(); // 关闭原reader
 89                     reader = ir;    // 赋予新reader
 90                 }
 91             }
 92             return new IndexSearcher(reader);
 93         }
 94         catch (Exception e) {
 95             e.printStackTrace();
 96         }
 97         return null; // 发生异常则返回null
 98     }
 99 
100     /**
101      * 高亮搜索
102      * 
103      * @see 高亮搜索时,不建议把高亮信息存到索引里,而是搜索到内容之后再进行高亮处理
104      * @see 这里用的是MMSeg4j中文分词器,有关其介绍详见http://blog.csdn.net/jadyer/article/details/10049525
105      * @param expr
106      *            搜索表达式
107      */
108     public void searchByHignLighter(String expr) {
109         Analyzer analyzer = new MMSegAnalyzer();
110         IndexSearcher searcher = this.getIndexSearcher();
111         // 搜索多个Field
112         QueryParser parser = new MultiFieldQueryParser(Version.LUCENE_36, new String[] { "filepath", "filecontent" }, analyzer);
113         try {
114             Query query = parser.parse(expr);
115             TopDocs tds = searcher.search(query, 50);
116             for (ScoreDoc sd : tds.scoreDocs) {
117                 Document doc = searcher.doc(sd.doc);
118                 // 获取文档内容
119                 String filecontent = new Tika().parseToString(new File(doc.get("filepath")));
120                 System.out.println("搜索到的内容为[" + filecontent + "]");
121                 // 开始高亮处理
122                 QueryScorer queryScorer = new QueryScorer(query);
123                 Fragmenter fragmenter = new SimpleSpanFragmenter(queryScorer, filecontent.length());
124                 Formatter formatter = new SimpleHTMLFormatter("<span style='color:red'>", "</span>");
125                 Highlighter hl = new Highlighter(formatter, queryScorer);
126                 hl.setTextFragmenter(fragmenter);
127                 System.out.println("高亮后的内容为[" + hl.getBestFragment(analyzer, "filecontent", filecontent) + "]");
128             }
129         }
130         catch (Exception e) {
131             e.printStackTrace();
132         }
133         finally {
134             if (null != searcher) {
135                 try {
136                     searcher.close(); // 记得关闭IndexSearcher
137                 }
138                 catch (IOException e) {
139                     e.printStackTrace();
140                 }
141             }
142         }
143     }
144 
145     /**
146      * 高亮的使用方式
147      * 
148      * @see 这里用的是MMSeg4j中文分词器,有关其介绍详见http://blog.csdn.net/jadyer/article/details/10049525
149      */
150     private static void testHighLighter() {
151         String fieldName = "myinfo"; // 这个可以随便写,就是起个标识的作用
152         String text = "我来自中国黑龙江省哈尔滨市巴彦县兴隆镇长春乡民权村4队";
153         QueryParser parser = new QueryParser(Version.LUCENE_36, fieldName, new MMSegAnalyzer());
154         try {
155             // MMSeg4j的new MMSegAnalyzer()默认只会对'中国'和'兴隆'进行分词,所以这里就只高亮它们俩了
156             Query query = parser.parse("中国 兴隆");
157             // 针对查询出来的文本,查询其评分,以便于能够根据评分决定显示情况
158             QueryScorer queryScorer = new QueryScorer(query);
159             // 对字符串或文本进行分段,SimpleSpanFragmenter构造方法的第二个参数可以指定高亮的文本长度,默认为100
160             Fragmenter fragmenter = new SimpleSpanFragmenter(queryScorer);
161             // 高亮时的高亮格式,默认为<B></B>,这里指定为红色字体
162             Formatter formatter = new SimpleHTMLFormatter("<span style='color:red'>", "</span>");
163             // Highlighter专门用来做高亮显示
164             // 该构造方法还有一个参数为Encoder,它有两个实现类DefaultEncoder和SimpleHTMLEncoder
165             // SimpleHTMLEncoder可以忽略掉HTML标签,而DefaultEncoder则不会忽略HTML标签
166             Highlighter hl = new Highlighter(formatter, queryScorer);
167             hl.setTextFragmenter(fragmenter);
168             System.out.println(hl.getBestFragment(new MMSegAnalyzer(), fieldName, text));
169         }
170         catch (Exception e) {
171             e.printStackTrace();
172         }
173     }
174 
175     /**
176      * 小测试一下
177      */
178     public static void main(String[] args) {
179         // 测试高亮的基本使用效果
180         Lucene_08_HelloHighLighter.testHighLighter();
181         // 测试高亮搜索的效果(测试前记得在myExample/myFile/文件夹中准备一个或多个内容包含"依赖"的doc或pdf的等文件)
182         // new Lucene_08_HelloHighLighter().searchByHignLighter("依赖");
183     }
184 }
View Code

  9.近实时搜索

  1 import java.io.File;
  2 import java.io.IOException;
  3 
  4 import org.apache.lucene.analysis.standard.StandardAnalyzer;
  5 import org.apache.lucene.document.Document;
  6 import org.apache.lucene.document.Field;
  7 import org.apache.lucene.index.IndexReader;
  8 import org.apache.lucene.index.IndexWriter;
  9 import org.apache.lucene.index.IndexWriterConfig;
 10 import org.apache.lucene.index.Term;
 11 import org.apache.lucene.search.IndexSearcher;
 12 import org.apache.lucene.search.NRTManager;
 13 import org.apache.lucene.search.NRTManager.TrackingIndexWriter;
 14 import org.apache.lucene.search.NRTManagerReopenThread;
 15 import org.apache.lucene.search.Query;
 16 import org.apache.lucene.search.ScoreDoc;
 17 import org.apache.lucene.search.TermQuery;
 18 import org.apache.lucene.search.TopDocs;
 19 import org.apache.lucene.store.Directory;
 20 import org.apache.lucene.store.FSDirectory;
 21 import org.apache.lucene.util.Version;
 22 
 23 /**
 24  * 【Lucene3.6.2入门系列】第12节_近实时搜索
 25  * 
 26  * @see 实时搜索(near-real-time)---->只要数据发生变化,则马上更新索引(IndexWriter.commit())
 27  * @see 近实时搜索------------------>数据发生变化时,先将索引保存到内存中,然后在一个统一的时间再对内存中的所有索引执行commit提交动作
 28  * @see 为了实现近实时搜索,Lucene3.0提供的方式叫做reopen,后来的版本中提供了两个线程安全的类NRTManager和SearcherManager
 29  * @see 不过这俩线程安全的类在Lucene3.5和3.6版本中的用法有点不太一样,这点要注意
 30  * @create Aug 7, 2013 4:19:58 PM
 31  * @author 玄玉<http://blog.csdn.net/jadyer>
 32  */
 33 public class Lucene_09_HelloNRTSearch {
 34     private IndexWriter writer;
 35     private NRTManager nrtManager;
 36     private TrackingIndexWriter trackWriter;
 37 
 38     /**
 39      * 测试时,要在E:/lucene_test/01_file/文件夹中准备几个包含内容的文件(比如txt格式的)
 40      * 然后先执行createIndex()方法,再执行searchFile()方法,最后观看控制台输出即可
 41      */
 42     public static void main(String[] args) {
 43         Lucene_09_HelloNRTSearch instance = new Lucene_09_HelloNRTSearch();
 44         instance.createIndex();
 45         instance.testSearchFile();
 46         instance.getDocsCount();
 47     }
 48 
 49     public void testSearchFile() {
 50         Lucene_09_HelloNRTSearch hello = new Lucene_09_HelloNRTSearch();
 51         for (int i = 0; i < 5; i++) {
 52             hello.searchFile();
 53             System.out.println("-----------------------------------------------------------");
 54             hello.deleteIndex();
 55             if (i == 2) {
 56                 hello.updateIndex();
 57             }
 58             try {
 59                 System.out.println(".........开始休眠2s(模拟近实时搜索情景)");
 60                 Thread.sleep(2000);
 61                 System.out.println(".........休眠结束");
 62             }
 63             catch (InterruptedException e) {
 64                 e.printStackTrace();
 65             }
 66         }
 67         // 不能单独去new HelloNRTSearch,要保证它们是同一个对象,否则所做的delete和update不会被commit
 68         hello.commitIndex();
 69     }
 70 
 71     public Lucene_09_HelloNRTSearch() {
 72         try {
 73             Directory directory = FSDirectory.open(new File("E:/lucene_test/01_index/"));
 74             writer = new IndexWriter(directory, new IndexWriterConfig(Version.LUCENE_36, new StandardAnalyzer(Version.LUCENE_36)));
 75             trackWriter = new NRTManager.TrackingIndexWriter(writer);
 76             // /*
 77             // * Lucene3.5中的NRTManager是通过下面的方式创建的
 78             // * 并且Lucene3.5中可以直接使用NRTManager.getSearcherManager(true)获取到org.apache.lucene.search.SearcherManager
 79             // */
 80             // nrtManager = new NRTManager(writer,new org.apache.lucene.search.SearcherWarmer() {
 81             // @Override
 82             // public void warm(IndexSearcher s) throws IOException {
 83             // System.out.println("IndexSearcher.reopen时会自动调用此方法");
 84             // }
 85             // });
 86             nrtManager = new NRTManager(trackWriter, null);
 87             // 启动一个Lucene提供的后台线程来自动定时的执行NRTManager.maybeRefresh()方法
 88             // 这里的后俩参数,是根据这篇分析的文章写的http://blog.mikemccandless.com/2011/11/near-real-time-readers-with-lucenes.html
 89             NRTManagerReopenThread reopenThread = new NRTManagerReopenThread(nrtManager, 5.0, 0.025);
 90             reopenThread.setName("NRT Reopen Thread");
 91             reopenThread.setDaemon(true);
 92             reopenThread.start();
 93         }
 94         catch (Exception e) {
 95             e.printStackTrace();
 96         }
 97     }
 98 
 99     /**
100      * 创建索引
101      */
102     public void createIndex() {
103         String[] ids = { "1", "2", "3", "4", "5", "6" };
104         String[] names = { "Michael", "Scofield", "Tbag", "Jack", "Jade", "Jadyer" };
105         String[] contents = { "my blog", "my website", "my name", "my job is JavaDeveloper", "I am from Haerbin", "I like Lucene" };
106         IndexWriter writer = null;
107         Document doc = null;
108         try {
109             Directory directory = FSDirectory.open(new File("E:/lucene_test/01_index/"));
110             writer = new IndexWriter(directory, new IndexWriterConfig(Version.LUCENE_36, new StandardAnalyzer(Version.LUCENE_36)));
111             writer.deleteAll();
112             for (int i = 0; i < names.length; i++) {
113                 doc = new Document();
114                 doc.add(new Field("id", ids[i], Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS));
115                 doc.add(new Field("name", names[i], Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS));
116                 doc.add(new Field("content", contents[i], Field.Store.YES, Field.Index.ANALYZED));
117                 writer.addDocument(doc);
118             }
119         }
120         catch (Exception e) {
121             e.printStackTrace();
122         }
123         finally {
124             if (null != writer) {
125                 try {
126                     writer.close();
127                 }
128                 catch (IOException ce) {
129                     ce.printStackTrace();
130                 }
131             }
132         }
133     }
134 
135     /**
136      * 通过IndexReader获取文档数量
137      */
138     public void getDocsCount() {
139         IndexReader reader = null;
140         try {
141             reader = IndexReader.open(FSDirectory.open(new File("E:/lucene_test/01_index/")));
142             System.out.println("maxDocs:" + reader.maxDoc());
143             System.out.println("numDocs:" + reader.numDocs());
144             System.out.println("deletedDocs:" + reader.numDeletedDocs());
145         }
146         catch (Exception e) {
147             e.printStackTrace();
148         }
149         finally {
150             if (reader != null) {
151                 try {
152                     reader.close();
153                 }
154                 catch (IOException e) {
155                     e.printStackTrace();
156                 }
157             }
158         }
159     }
160 
161     /**
162      * 搜索文件
163      */
164     public void searchFile() {
165         // Lucene3.5里面可以直接使用NRTManager.getSearcherManager(true).acquire()
166         IndexSearcher searcher = nrtManager.acquire();
167         Query query = new TermQuery(new Term("content", "my"));
168         try {
169             TopDocs tds = searcher.search(query, 10);
170             for (ScoreDoc sd : tds.scoreDocs) {
171                 Document doc = searcher.doc(sd.doc);
172                 System.out.print("文档编号=" + sd.doc + "  文档权值=" + doc.getBoost() + "  文档评分=" + sd.score + "    ");
173                 System.out.println("id=" + doc.get("id") + "  name=" + doc.get("name") + "  content=" + doc.get("content"));
174             }
175         }
176         catch (Exception e) {
177             e.printStackTrace();
178         }
179         finally {
180             try {
181                 // 这里就不要IndexSearcher.close()啦,而是交由NRTManager来释放
182                 nrtManager.release(searcher);
183                 // Lucene-3.6.2文档中ReferenceManager.acquire()方法描述里建议再手工设置searcher为null,以防止在其它地方被意外的使用
184                 searcher = null;
185             }
186             catch (IOException e) {
187                 e.printStackTrace();
188             }
189         }
190     }
191 
192     /**
193      * 更新索引
194      */
195     public void updateIndex() {
196         Document doc = new Document();
197         doc.add(new Field("id", "11", Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS));
198         doc.add(new Field("name", "xuanyu", Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS));
199         doc.add(new Field("content", "my name is xuanyu", Field.Store.YES, Field.Index.ANALYZED));
200         try {
201             // Lucene3.5中可以直接使用org.apache.lucene.search.NRTManager.updateDocument(new Term("id", "1"), doc)
202             trackWriter.updateDocument(new Term("id", "1"), doc);
203         }
204         catch (IOException e) {
205             e.printStackTrace();
206         }
207     }
208 
209     /**
210      * 删除索引
211      */
212     public void deleteIndex() {
213         try {
214             // Lucene3.5中可以直接使用org.apache.lucene.search.NRTManager.deleteDocuments(new Term("id", "2"))
215             trackWriter.deleteDocuments(new Term("id", "2"));
216         }
217         catch (IOException e) {
218             e.printStackTrace();
219         }
220     }
221 
222     /**
223      * 提交索引内容的变更情况
224      */
225     public void commitIndex() {
226         try {
227             writer.commit();
228         }
229         catch (IOException e) {
230             e.printStackTrace();
231         }
232     }
233 }
View Code

参考文章

  http://www.chedong.com/tech/lucene.html

  http://blog.csdn.net/column/details/jadyerlucene.html

 

posted @ 2014-10-24 15:02  Leo.cheng  阅读(4594)  评论(1编辑  收藏  举报