lucene学习

更新时间:2023-03-08 05:27:11 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

1. 基本概念

信息检索(IR)是指文档搜索、 文档内信息搜索或者文档相关的元数据搜索等操作。 文档:用于搜索的内容部件。

文档过滤器:将文本格式信息从原始内容中提取出来,便于后期建立搜索引擎文档。如Tika,与数据库的内容连接,DBSight、Hibernate Search、LuSQL、Compass和Oracle/Lucene集成项目。

词汇单元:即分词。词干提取器,如Snowball。

搜索质量主要由查准率(Precision)和查全率(Recall)来衡量。[1]P13 语法检查器: Lucene的 contrib目录提供了两个模块完成此功能。

查询对象: Lucene提供了一个称之为查询解析器(QueryParser),用它可以根据通用查询语法将用户输入的文本处理成查询对象。

查询搜索:査询检索索引并返回与査询语句匹配的文档,结果返回时 按照査询请求来排序。搜索查询组件涵盖了搜索引擎内部复杂的工作机制,Lucene正是 如此,它为你完成这一切。 倒排索引:inverted index

常见的搜索理论模型有如下3种。

■ 纯布尔模型(Pure Boolean model) 文档不管是否匹配查询请求,都不会被评分.在该模型下,匹配文档与评分不相关,也是无序的;一条查询仅获取所有匹配文档集合的一个子集。

■ 向量空间模型(Vector space model)

查询语句和文档都是高维空间的向

量模型,这里每一个独立的项都是一个维度。查询语句和文档之间的相关性或相似性由各自向量之间的距离计算得到.

■概率模型(Probabilistic model)在该模型中,采用全概率方法来计算文档和查询语句的匹配概率。

Lucene在实现上采用向量空间模型和纯布尔模型,并能针对具体搜索让你决定采用哪种模型。最后,Lucene返回的文档结果必须用比较经济的方式展现给用户。

搜索范围:涉及分布式搜索,Apache Lucene项目下的Solr和Nutch项目提供了对索引拆分和复制的支持,另Katta和Elastic search。

1.1 Lucene核心类概貌

执行简单的索引过程需要用到以下几个类: ■ IndexWriter ■ Directory ■ Analyzer ■ Document ■ Field

? IndexWriter (写索引)是索引过程的核心组件。这个类负责创建新索引或者打

开 已有索引,以及向索引中添加、删除或更新被索引文档的信息。可以把IndexWriter 看做这样一个对象:它为你提供针对索引文件的写人操作,但不能用于读取或搜索索引。 IndexWriter需要开辟一定空间来存储索引,该功能可以由Directory完成。

? Directory类描述了 Lucene索引的存放位置。它是一个抽象类,它的子类负

责具体指定索引的存储路径。在前面的Indexer例子中,我们用FSDirectory.open方法来获取真实文件在文件系统的存储路径,然后将它们依次传递给IndexWriter类构造方法。

? 文本文件在被索引之前,需要经过Analyzer (分析器)处理。Analyzer是由

IndexWriter 的构造方法来指定的,它负责从被索引文本文件中提取语汇单元,并提出剩下的无用信 息。如果被索引内容不是纯文本文件,那就就需要先

将其转换为文本文档。

? Document (文档)对象代表一些域(Field)的集合。你可以将Document对象

理解 为虚拟文档——比如Web页面、E-mail信息或者文本文件——然后你可以从中取回大量 数据。文档的域代表文档或者和文档相关的一些元数据。文档的数据源(比如数据库记 录、Word文档、书中的某章节等)对于Lucene来说是无关紧要的。Lucene只处理从二 进制文档中提取的以Field实例形式出现的文本。

? 索引中的每个文档都包含一个或多个不同命名的域,这些域包含在Field类

中。 每个域都有一个域名和对应的域值,以及一组选项来精确控制Lucene 索引操作各个域值。文档可能拥有不止一个同名的域。在这种情况下,域的值就按照索引操作顺序添加进去。在搜索时,所有域的文本就好像连接在一起,作为一个文本域来处理。

搜索操作: ■ IndexSearcher ■ Term ■ Query ■ TermQuery ■ TopDocs

? IndexSearcher类用于搜索由IndexWriter类创建的索引:这个类公开了几个

搜 索方法,它是连接索引的中心环节。可以将IndexSearcher类看做一个以只读方式打 开索引的类。它需要利用Directory实例来掌控前期创建的索引,然后才能提供大量 的搜索方法,其中一些方法在它的抽象父类Searcher中实现,最简单的搜索方法是将 单个Query对象和int topN计数作为该方法的参数,并返回一个TopDocs对象。

Directory dir = FSDirectory.open(new File(\; IndexSearcher searcher = new IndexSearcher(dir);

Query q = new TermQuery(new Term(\TopDocs hits - searcher.search(q,10); searcher.close();

? Term对象是搜索功能的基本单元。与Field对象类似,Term对象包含一对字

符串元 素:域名和单词(或域文本值)。注意Term对象还与索引操作有关。然而,由于Term 对象是由Lucene内部创建的,我们并不需要在索引阶段详细了解它们。在搜索过程中 可以创建Term对象,并和TermQuery对象一起使用:

Query q = new TermQuery(new Term(\; TopDocs hits = searcher.search(q, 10);

? Lucene含有许多具体的Query (查询)子类。到目前为止,我们谈到的只是

Lucene 基本的 Query 子类:TermQuery 类。其他的 Query 子类有:BooleanQuery、PhraseQuery、 PrefixQuery 、 PhrasePrefixQuery、 TermRangeQuery 、 NumericRangeQuery、 FilteredQuery和SpanQuery。 它包含了一些非常私用的方法,其中最有趣的当属 setBoost (float) 该方法能使你告知Lucene某个子查询相对其他子査询来说必须对最后的评分有更强贡献。 ? TopDocs类是一个简单的指针容器,指针一般指向前N个排名的搜索结果,

搜索结 果即匹配查询条件的文档。TopDocs会记录前N个结果中每个结果的int docID (可以 用它来恢复文档)和浮点型分数。

1.2 术语

规范化:将字母转换成小写 词干还原:将单词还原为词干形式

词形归并(lemmatization):将单词转换为基本形式 词汇单元(token):从文本流中提取的文本块 项(term):语汇单元与它的域名结合后的产物

2.构建索引

文档是Lucene索引和搜索的原子单位。文档为包含一个或多个域的容器,而域则 依次包含“真正的”被搜索内容。每个域都有一个标识名称,该名称为一个文本值或二 进制值。当你将文档加人到索引中时,可以通过一系列选项来控制Lucene的行为。在 对原始数据进行索引操作时,你得首先将数据转换成

Lucene所能识别的文档和域。在 随后的搜索过程中,被搜索对象则为域值,例如,用户在输人搜索内容“title:lucene” 时,搜索结果则为标题域值包含单词“lucene”的所有文档。

进一步地,Lucene可以针对域进行3种操作。

■ 域值可以被索引(或者不被索引)。如果需要搜索一个域,则必须首先对它进行 索引.被索引的域值必须是文本格式的(二进制格式的域值只能被存储而不能被 索引).在索引一个域时,需要首先使用分析过程将域值转换成语汇单元,然后 将语汇单元加入到索引中.有关索引域值的具体操作选项可以参考[1]2.4.1小节.

■域被索引后,还可以选择性地存储项向量,后者可以看做该域的一个小型反向 索引集合,通过该向量能够检索该域的所有语汇单元.这个机制有助于实现一些高级功能,比如搜索与当前文档相似的文档(更多的高级功能详见[1]5.7小节). 有关控制索引项向量的具体选项请参考[1]2.4.3小节.

■域值可以被单独存储,即是说被分析前的域值备份也可以写进索引中,以便后 续的检索.这个机制可以使你将原始域值展现给用户,比如文档的标题或摘要。 域值的存储选项请参考[1]2.4.2小节.

索引段的存储结构见[1]P35。如果使用混合文件格式,使用

IndexWriter.setUseCompoundFile修改,则存储结构会被压缩成一个单一文件: _X.cfs。

2.1 文档的构建

addDocument(Document,[Analyzer]);[1]P35

deleteDocuments();[1]P38 lucene并不是立即删除,而是放在缓存中,周期性刷新,删除完成后,存储空间也不会立即释放,而会将该文档标记为\删除\。 maxDoc()返回索引中被删除和未被删除的文档总数。 numDocs()但会索引中未被删除的文档总数。

updateDocument(Term,Document,[Analyzer])首先删除包含Term变量的所有文档,然后添加文档。

2.2 域选项

2.2.1域索引选项

? Index.ANALYZED 使用分析器将域值分解成独立的语汇单元流,并使每个语汇

单元能被政索.该选项适用于普通文本域(如正文.标题、摘要等). ? Index.NOT_ANALYZED

对域进行索引,但不对String值进行分析.该操作实

际上将域值作为单一语汇单元并使之能被搜索。该选项适用于索引那些不能被分解的域值,如URL、文件路径、日期、人名、社保号码和电话号码等.该选项尤其适用 于“精确匹配”搜索。 ? Index.ANALYZED_ NO_NORMS

这是 Index.ANALYZED选项的一个变体,它

不会在索引中存储norms信息。norms记录了索引中的index-time boost信息,但是当 你进行搜索时可能会比较耗费内存。有关norms的详细内容详见[1]2.5.3小节。

? Index.NOT_ANALYZED_NO_NORMS

与 Index.NOT_ANALYZED 选项类似,但

也是不存储norms.该选项常用于在搜索期间节省索引空间和减少内存耗费,因 为single-token域并不需要norms信息,除非它们已被进行加权操作。 ? index. NO

使对应的域值不被搜索.

当Lucene建立起倒排索引后,默认情况下它会保存所有必要信息以实施Vector Space Model。该Model需要计算文档中出现的term数,以及它们出现的位置(这是必要的,比 如通过词组搜索时用到)。但有时候这些域只是在布尔搜索时用到,

它们并不为相关评分 做贡献,一个常见的例子是,域只是被用作过滤,如权限过滤和日期过滤。在这种情况下, 可以通过调用 Field.setOmitTermFreqAndPositions (true)方法让Lucene跳过对该项 的出现频率和出现位置的索引。该方法可以节省一些索引在磁盘上的存储空间,还可以加 速搜索和过滤过程,但会悄悄地阻止需要位置信息的搜索,如阻止PhraseQuery和 SpanQuery类的运行。

2.2.2域存储选项

? Store. YES——指定存储域值。该情况下,原始的字符串值全部被保存在索引

中,并可以由IndexReader类恢复.该选项对于需要展示搜索结果的一些域很 有用(如URL、标題或数据库主键).如果索引的大小在搜索程序考虑之列的 话,不要存储太大的域值,因为存储这些域值会消耗掉索引的存储空间. ? Store.NO

指定不存储域值.该选项通常跟Index.ANALYZED选项共同用来索

引大的文本域值,通常这些域值不用恢复为初始格式,如Web页面的正文, 或其他类型的文本文档.

Lucene包含一个很实用的工具类,CompressionTools,该类提供静态方法压缩和 解压字节数组。该类运行时会在后台调用Java内置的java.util.zip类。你可以使用 CompressionTools在存储域值之前对它进行压缩。注意,尽管该方法可以为索引节省—些空间,但节省的幅度跟域值的可被压缩程度有关,而且该方法会降低索引和搜索速度。这样其实就是通过消耗更多CPU计算能力来换取更多的磁盘空间,对于很多程序 来说,需要仔细权衡一下。如果域值所占空间很小,建议少使用压缩。

2.2.3 其他

项向量

Reader、TokenStream和byte[]域值及Field对象初始化[1]P42

域排序选项[1]P44

多值域[1]P44,使用相同域名,而加入多个域值。要注意若要分开内容查询,则需要重载设置域值之间的间隔。

2.3 加权操作

文档加权操作doc.setBoost(float) 域加权操作field.setBoost(float)

当改变加权因子时,必须完全删除并创建对应的文档,或使用updateDocument方法达到同样的效果。

2.3.1加权基准(Norms)

详见[1]P47,其面临的问题之一就是搜索期间的高内存用量。关闭norms相关操作,NO_NORMS选项或Field.setOmitNorms(true)

如果在索引进行一半时关闭norms选项,那么你必须对整个索引进 行重建,因为即使只有一个文档域在索引时包含了 norms选项,那么在随后的段合并操 作中,这个情况会“扩散”,从而使得所有文档都会占用一个字节的norms空间,即使 它们在此前的索引操作中关闭了 norms选项也是如此。发生这种情况主要是因为Lucene 并不针对norms进行松散存储。

2.4索引数字、时间与日期

详见[1]2.6节

2.4 索引截取

建立IndexWriter时 ,MaxFieldLength.UNLIMITED 不采取截取策略 MaxFieldLength.LIMITED只截取域中前1000个项。

建立IndexWriter后,使用setMaxFieldLength方法,getMaxFieldLength方法获得当前截取限制。该调用不具有追溯效果。

2.5近实时搜索、优化索引

IndexReader getReader()能实时刷新缓冲区中新增或删除的文档。该方法的调用会降低索引效率。

优化索引就是讲索引多个段合并成一个或少量段,从而减少对多段索引搜索后合并的操作。详见[1]P52,索引优化的doWait选项可以立即返回并在后台线程执行优化。

2.6 Directory

静态的 FSDirectory.open方法。该方法会根据当前的操作系统和平台来尝试选择最合适的默 认FSDirectory子类。

可以按照类似如下的方式将另一个Directory中的内容拷贝到 RAMDirectory 中: Directory ramDir = new RAMDirectory(otherDir);

该方法通常用于针对现有的存在于磁盘上的索引进行搜索提速,它要求索引尺寸足够小。但当代操作系统能对当前使用的数据保存在i/o缓存,所以该方法可能不会对产 生太大的性能提升。

2.7 并发、线程安全及锁机制

并发操作

? 任意数量的只读属性的IndexReader类都可以同时打开一个索引.无论这些

Reader是否属于同一个JVM,以及是否属于同一台计算机都无关紧要.但需要 记住:在单个JVM内,利用资源和发挥效率的最好办法是用多线程共享单个的 IndexReader实例.例如,多个线程或进程并行搜索同一个索引.

? 对于一个索引来说,一次只能打开一个Writer. Lucene采用文件锁来提供保

障.一旦建立起IndexWriter对象,系统即会分配一个锁给它. 该锁只有当IndexWriter对象被关闭时才会释放。注意如果你使用IndexReader对象来改变索引的话 比如修改norms或者删除文档。这时IndexReader对象会作为Writer使用:它必须在修改上述内容之前成功地获取Write锁,并在被关闭时释放该锁.

? IndexReader对象甚至可以在IndexWriter对象正在修改索引时打开。每个

IndexReader对象将向索引展示自己被打开的时间点.该对象只有在 IndexWriter对象提交修改或自己被重新打开后才能获知索引的修改情况.所 以一个更好的选择是,在已经有IndexReader对象被打开的情况下,打开新 IndexReader时采用参数create=true:这样,新的IndexReader会持续检查索引的情况.

? 任意多个线程都可以共享同一个IndexReader 类或IndexWriter类。这些类不

仅是线程安全 的,而且是线程友好的,即是说它们能够很好地 扩展到新增线程(假定你的硬件支持并发访问, 因为这些类中标识为同步(synchronized)的代码 数并不多,仅为最小值).

2.7.1远程文件系统访问索引

Solr能比较好的支持索引复制策略。

对于不连贯的客户端缓存出现的间断性问题。程序将在open或reopen方法中抛出FileNotFoundException异常。该问题的解决方法:稍后重新操作即可,因为客户端缓存会在出现问题一段时间后自动修复。

NFS系统中客户端删除操作可能引起IOException异常。处理方式详见[1]P57

2.7.2 锁机制

详见[1]P57页起。

2.8 其他

IndexReader删除文档[1]P61

回收被删除文档所使用过的磁盘空间[1]P62 expungeDeletes 缓

[1]P62

setRAMBufferSizeMB

setMaxBufferedDocs

setMaxBufferedDeleteTerm。刷新操作是用来释放被缓存的更改的;提交操作是用来让所有的更改在索引中保持可视。 索引提交与事务[1]P63 两阶段提交 prepareCommit() 合并段[1]P66

3. 实现搜索

QueryParser可将输入的搜索语句转化为查询条件Query,而Query也可通过简单查询对象来构建。

IndexSearcher

大多数情况下我们搜索的索引都是存在于文件系统的: Directory dir = FSDirectory.open(new File(\然后建立一个 IndexReader:

IndexReader reader = IndexReader.open(dir); 最后,创建IndexSearcher:

IndexSearcher searcher = new IndexSearcher(reader);

由于打开一个IndexReader需要较大的系统开销,因此最好是在所有搜索期间都重复使用同一个IndexReader实例,只在必要时才打开新的IndexReader。 如需获取索引中的变更信息,必须打开一个新的reader。IndexReader.reopen可有效获取新的IndexReader,并耗费较少。可使用writer.getReader()获取处于相同jvm中IndexWriter的近实时reader,此速度更快。 IndexReader newReader = reader.reopen(); if (reader != newReader) {

reader.close(); reader = newReader;

searcher = new IndexSearcher(reader); }

如果索引有所变更,reopen方法值返回一个新的reader,这时程序必须关闭旧的reader并创建新的IndexSearcher。在实际的应用程序中,可能有多个线程还在使用旧的reader进行捜索,因此必须保证这段代码是线程安全的。

QueryParser

QueryParser parser = new QueryParser(Version matchVersion, String field,

Analyzer analyzer)

域名参数指所有被搜索项所定义的默认域,除了以下情况:搜索文本明确要求与 之匹配的域名必须用“field:text”的语法形式(详见[1]3.5.11小节)。然后调用parse()方法。

解析不成功会抛出ParseException异常。

TopDocs

TopDocs.totalHits属性会返回匹配文档数量。默认情况下,匹配文档是根据评分按照降 序排列的。TopDocs.scoreDocs属性是一个数组,它包含程序所要求的顶部匹配文档数量。 每个ScoreDoc实例都有一个浮点类型的评分,该评分是相关性评分,该实例还包括一个整 型的文档号,它用于标识文档ID,能够用于检索文档中保存的域,具体可以通过调用 IndexSearcher .document (doc) 方法进行。最后,TopDocs .getMaxScore () 方法会返回 所有匹配文档的最大评分,当使用相关性评分进行排序时(在默认情况下),返回的最大评分通常就是第一个搜索结果对应的评分。

分页功能

? 将首次搜索获得的多页搜索结果收集起来并保存在ScoreDocs和

IndexSearcher实 例中,并在用户换页浏览时展现这几页结果; ? 每次用户换页浏览时都重新进行查询操作。

重新查询通常是更好的解决方案。这个方案可以不用存储每个用户的当前浏览状态, 而这个操作对于Web应用程序来说开销巨大,特别是对于拥有巨大用户群的应用程序来 说。咋一看,重新查询似乎比较浪费,但Lucene的快速处理

能力正好可以弥补缺陷。 另外,得益于当今操作系统的I/O缓存机制,重新查询操作通常会很快完成,因为该操作所 需要处理的磁盘数据已经被缓存至RAM 了。

3.1 lucene评分机制

相似度评分公式,用来衡量查询语句和对应匹配文档之间的相似程度。分值计算方式为查询语句(q)中每个项(t)与文档(d)的匹配分值之和。

得到原始评分,然后对评分进行归一化处理,即用该条查询对应的评分除以最大评分。

IndexSearcher类包含一个explain方法,调用该方法需要传入一个Query对象和一个文档ID,然后该方法会返回一个Explanation对象。该对象包含评分计算的各种信息细节。

短语査询是根据短语匹配所需要的编辑距离来进行评分的。项之间的距离越小,具 有的权重也就越大。评分与距离成反比关系,距离越大的匹配其评分越低。

1

distance?13.2 预置查询类

查询最终是调用IndexSearcher.search方法,该方法需要Query对象作为参数,该对象可以由QueryParser分析产生,也可以使用预置的Query对象。

3.2.1 TermQuery 通过项搜索

对索引中特定项进行搜索是最基本的搜索方式。Term是最小的索引片段,每个Term 包含了一个域名和一个文本值。下面这段代码 初始化了一个Term实例: Term t = new Term(\

TermQuery构造方法允许一个单独的Term对象作为其参数: Query query = new TermQuery(t);

使用这个TermQuery对象进行搜索,可以返回在content域包含单词“java”的所有文档。值得注意的是,该査询值是区分大小写的,因此搜索前一定要对索引后的项大小写进行匹配。

3.2.2在指定的项范围内捜索:TermRangeQuery类

索引中的各个Term对象会按照字典编排顺序(通过String.compareTo方法)进行排序,并允许在Lucene的TermRangeQuery对象提供的范围内进行文本项的直接搜索。 搜索时包含或不包含起始项和终止项。如果这个项为空,那么它对应的端就是无边界的。 举例来说,一个空的lowerTerm意味着没有下边界,这样所有比上边界项小的项都会 被计算在内。该査询只适用于文本范围,比如搜索从N到Q范围内的域名称。下一节 提到的NumericRangeQuery可以用于数值域的范围查询。

下面的代码为TermRangeQuery的使用方法,其功能是搜索起始字母范围从d到j 的书籍标题。这里需要注意,书籍索引中的title2域是小写字母形式的,并使用Field. NOT_ANALYZED_NO_NORMS将该域索引成单个语汇单元。 public void testTermRangeQuery{) throws Exception { Directory dir = TestUtil.genBooklndexDirectory(); IndexSearcher searcher = new IndexSearcher(dir);

TermRangeQuery query = new TermRangeQuery ( \true, true);

TopDocs matches = searcher.search(query, 100); assertEquals(3, matches.totalHits);

searcher.close(); dir.close(); }

其中TermRangeQuery初始化方法中的的两个Boolean对象参数表示是(用true 表示)否(用false表示)包含捜索范围的起点或终点。该对象还可以处理自定义的Collator对象,并用于搜索范围的检查,但对于大的索引来说,这个处理异常缓慢,因为他在检查边界条件时需要列举出索引的每个项。CollationKeyAnalyzer可以提高程序性能。

3.2.3在指定的数字范围内捜索:NumericRangeQuery类

如果使用NumericField对象来索引域,那么你就能有效地使用NumericRange Query类在某个特定范围内搜索该域。Lucene会在后台将提交的搜索范围转换成与前面索引操作生成的树结构(trie structure)等效的括号集。这里每个括号都是索引中各不相同的项,它们对应的文档都会在搜索时进行或运算。使用NumericRangeQuery类搜索时所需要的括号数量相对较小,这使得该类运行时与TermRangeQuery类相比,性能好很多。

下面的程序示例,该程序基于书籍索引中的pub month域运行。程序将该域作为整数进行索引,并且索引精度为月,也就是说,2010年3月会被索引成域值为 201003的NumericField。程序接下来进行了一次范围内搜寻:

public void testlnclusive() throws Exception { Directory dir = TestUtil.getBooklndexDirectory(); IndexSearcher searcher = new IndexSearcher(dir); // pub date of TTC was September 2006

NainericRangeQuery query = NumericRangeQuery.newIntRange ( \200605,

200609, true, true);

TopDocs matches = searcher.search(query, 10); assertEquals(1, matches.totalHits); searcher.close(); dir.close{); }

程序最后newIntRange方法中的两个Boolean参数 表示搜索范围是(用true表示)否(用false表示)包含起点和终点。

该类可传入与NumericField相同的precisionStep参数。该参数的匹配详见Java文档。

3.2.4通过字符串捜索:PrefixQuery类

搜索程序使用PrefixQuery来搜索包含以指定字符串开头的项的文档。这个操作看起来是很容易的。下面的代码展示了如何通过简单的PrefixQuery对象对某个层次结构进行递归査询。包含category域的文档会呈现一个层次结构,该结构可以完美地用于匹配一个PrefixQuery对象

public class PrefixQueryTest extends TestCase { public void testPrefix() throws Exception {

Directory dir = TestUtil.getBooklndexDirectory(); IndexSearcher searcher = new IndexSearcher(dir); Term term = new Term(\

\;

PrefixQuery query = new PrefixQuery(term); //搜索编程方面的书籍,包括它们的子类书籍 TopDocs matches = searcher.search(query, 10); int progratnmingAndBelow = matches. totalHits; // 搜素编程方面的书籍不包括它们的子类

matches = searcher. search (new TermQuery (term) , 10) ; int justProgramming = matches.totalHits

assertTrue(programmingAndBelow > justPrograiraming); searcher.close(); dir.close(); } }

PrefixQueryTest 类展示了 PrefixQuery 和 TermQuery 之间的差异。methodology 分类存在于/technology/computers/programming分类目录下。在这个methodology子类中的书籍程序通过PrefixQuery,而不是通过TermQuery找到的。

3.2.5组合查询:BooleanQuery 类

通过使用BooleanQuery类可以将本章讨论的各种查询类型组合成复杂的查询方式, 而BooleanQuery本身是一个Boolean子句(clauses)的容器。这个子句可以是表示逻辑 “与”、逻辑“或”或者逻辑“非”的一个子査询。这些属性允许

进行逻辑AND、OR和NOT 组合。你可以调用如下API的方法将査询子句加入到BooleanQuery对象中:

public void add(Query query, BooleanClause.Occur occur)

这里 Occur 对象可以设置为 BooleanClause .Occur .MUST、BooleanClause.Occur. SHOULD 或者 BooleanClause. Occur.MUST_NOT。BooleanQuery对象还可以作为另一个BooleanQuery对象的字句,这样就允许它们任意地嵌套了。 public void testAnd() throws Exception { TermQuery searchingBooks =

new TermQuery (new Term( \; Query books2010 =

NumericRangeQuery.newIntRange(\ 201012, true, true);

BooleanQuery searchingBooks2010 = new BooleanQuery();

searchingBooks2010.add(searchingBooks, BooleanClause.Occur.MUST); ? searchingBooks2010.add(books2010, BooleanClause.Occur.MUST); Directory dir = TestUtil.getBooklndexDirectory(); IndexSearcher searcher = new IndexSearcher(dir);

TopDocs matches = searcher.search{searchingBooks2010, 10); assertTrue(TestUtil.hitslncludeTitle(searcher, matches, .\searcher.close(); dir.close(); )

BooleanClause.Occur.MUST的精确含义是:只有匹配该查询子句的文档才在考虑之列。BooleanClause. Occur .SHOULD意味着该项只是可选项,可用于或选项。BooleanClause. Occur .MUST_NOT意味着搜索结果不会包含任何匹配该查询子句的文档。

BooleanQuery对其中包含的查询子句是有数量限制的,默认情况下允许包含1024 个査询子句。该限制主要是为了防止时对应用程序的性能造成影响。当子句数量超过最大 值时,程序会抛出TooManyClauses异常。对于Lucene早期版本来说,这个限制是必要 的,因为某些查询语句可能会在后台被改写成等效的BooleanQuery类。但从版本2.9开始, Lucene会以一种更有效地方式处理这些查询语句。如果你在一些特殊情况下需要增大查 询子句的数量限制,可以使用BooleanQuery类提供的ClauseCount (int)方法进行设置,但设置前需要意识到这个做法对程序性能产生的影响。

3.2.6通过短语搜索:PhraseQuery类

索引时如果不用omitTermFreqAndPositions选项(详见[1]2.4.1小节)建立纯Boolean 域的话,索引会根据默认设置包含各个项的位置信息。PhraseQuery类会根据这些位置信息定位某个距离范围内的项所对应的文档。例如,假设某个域中包含短语“the quick brown fox jumped over the lazy\即时我们不知道这个短语的完整写法,也一样可以通过 查找域中quick和fox相关并且相距很近的文档。当然,一个简单的TermQuery类也能够通 过对这两个项的单独査询而找到同样的文档,但在该例中,我们仅仅希望査到域中quick紧 邻fox (quick fox)的或者两者之间只有一个单词(quick [其他单词]fox)的文档。

在匹配的情况下,两个项的位置之间所允许的最大间隔距离称为slop。这里的距离是指项若要按顺序组成给定的短语所需要移动位置的次数。 public class PhraseQueryTest extends TestCase { private Directory dir;

private IndexSearcher searcher;

protected void setup() throws IOException { dir = new RAMDirectory();

IndexWriter writer = new IndexWriter(dir, new WhitespaceAnalyzer{),

IndexWriter.MaxFieldLength.UNLIMITED); Document doc = new Document(); doc.add(new Field(\

\Field .Store. YES , Field.Index.ANALYZED)); writer.addDocument(doc); writer.close();

searcher = new IndexSearcher(dir); )

protected void tearDown() throws IOException {

searcher.close(); dir.close(); }

private boolean matched(String[] phrase, int slop) throws IOException { PhraseQuery query = new PhraseQuery(); query.setSlop(slop);

//初始化PhraseQuery对象

for (String word : phrase) {

query.add (new Term(\}

TopDocs matches = searcher.search(query, 10) ; return matches.totalHits > 0; } }

如果进入query的短语顺序不对,则slop需要按移动次数进行调整,后面的移到前面位置,同时前面的也要移到后面位置,所以slop数更多。

PhraseQuery支持复合项短语,slop因子规定了按顺序移动项位置的总次数的最大值。

在QueryParser的分析表达式中,双引号里面的若干个项被转换成一个 PhraseQuery对象.Slop因子的默认值是0,但是你可以在QueryParser的查询表 达式中加上~n声明,以此来调整slop因子的值.例如,表达式“quick fox\的意思为:为fox和quick项生成一个slop因子为3的PhraseQuery对象. 与之相对应 SpanNearQuery能确保按次序的匹配。

3.2.7通配符查询:WildcardQuery 类

通配符查询可以让我们使用不完整的、缺少某些字母的项进行查询,但是仍然可以查到相关匹配结果。Lucene使用两个标准的通配符:*代表0个或者多个字 母,?代表0个或者1个字母。

3.2.8 搜索类似项:FuzzyQuery类

Lucene的模糊查询FuzzyQuery类用于匹配与指定项相似的项。Levenshtein距离算法用来决定索引文件中的项与指定目标项的相似程度(有关Levenshtein距离算法的更多信息请参考http://eitwikipedia. org/wiki/ Levenshtein_Distance)。这种算法又称为编辑距离算法,它是两个字符串之间相似度的一个度量方法,编辑距离就是用来计算从一个字符串转换到另一个字符串所需的最少插入、删除和替换的字母个数。例如,“three”和“tree”两个字符串的编辑距离为1,因为前者只需要删除一个字符它们就一样了。

Levenshtein距离计算不同于PhraseQuery和PhrasePrefixQuery中所使用的距离 计算方法。短语查询距离是为了匹配目标文档短语所需移动项的次数,而Levenshtein 距离则是一个项内部字母移动的次数。

FuzzyQuery通过一个阈值来控制 匹配,而不是单纯依靠编辑距离。这个阈值是通过字符串长度除以编辑距离得到的一个因子。编辑距离能影响匹配结果的评分,编辑距离越小的项所获得的评分就越高。

1?distance

min(textlen,targetlen)该类会尽可能枚举出一个索引中所有项,对程序性能会有所影响。

3.2.9匹配所有文档:MatchAIIDocsQuery 类

MatchAilDocsQuery类,顾名思义,就是匹配索引中所有文档。默认情况下, 该类对匹配的文档分配了一个固定的评分,该文档具体的査询加权默认值为1.0。

如果你将该查询作为顶层查询,那么除了使用默认的相关性排序之外,最好通过域来排序。

对于一些特殊域来说,还可以用MatchAHDocsQuery来为索引中的文档进行评分 加权,操作如下:

Query query = new MatchAHDocsQuery (field);

如果执行这样的操作,文档就会根据该特定域的加权情况而被评分(详见[1]2.5 小节)。

3.3 解析查询表达式:QueryParser

QueryParser使用反斜杠(\\)表示转义字符。需转义的字符有: \\ + - ! ( ) : ^ ] { } ~ * ?

Query 的 toString 方法可以查看构建的复杂查询,也可看到QueryParser对查询的解析结果。

3.3.1 单词查询

单个词的查询相当于TermQuery,被解析为 field:key

3.3.2 范围搜索

范围搜索:中括号表示包含在内;大括号表示排除在外。起止点要么都包含在内,要么都排除在外。文本范围查询会将查询边界转换为小写字母形式,除非程序调用了QueryParser.setLowercaseExtendedTerms(false)。如果起止点内包含空格,则必须用双引号括起来。

3.3.3 前缀查询和通配符查询

当查询包含了星号或问号,就被当做通配符查询对象WildcardQuery。当查询只在末尾有一个星号时,QueryParser将优化为前缀查询对象PrefixQuery。默认查

询对象都会被转化为小写字母形式,解除这一默认值,调用QueryParser.setLowercaseExtendedTerms(false)。要在项开端包含通配符,需要调用setAllowLeadingWildcard方法,同时牺牲一部分性能。

3.3.4 布尔查询

AND OR NOT 必须大写。如果查询项之间没有指定布尔操作符,则默认为OR。程序可改变对隐含操作符的设置。

parser.setDefaultOperator(QueryParser.AND_OPERATOR); NOT操作单独使用将不起作用。

3.3.5 短语查询

查询语句中用双引号扩起来的项可以用来创建一个PhraseQuery。引号之间的文本将被进行分析,作为分析结果,PhraseQuery可能不会跟原始短语一样精确。双引号内的文本会促使分析器将之转换为PhraseQuery,停用词被问号代替,默认Slop因子为0,但可以调用QueryParser.setPhraseSlop()方法来改变这个默认因子,也可以用波浪号(~)和预期的整型slop值来修改。单项短语(single-term phrase)将被优化成TermQuery对象。

3.3.6 模糊查询

波浪符(~)会针对正在处理的项来创建模糊查询。需要注意的是,波浪符还可以用于指定松散短语查询,但具体环境是各不相同的。双引号表示短语查询,它并不能用于模糊査询。你可以选择性指定一个浮点数,用来表示所需的最小相似程度。下面是使用案例:

public void testFuzzyQuery() throws Exception {

QueryParser parser = new QueryParser{Version.LUCENE_30, \;

Query query = parser.parse(\

System.out.printIn(\query = parser.parse(\System.out.printIn(\)

该程序会产生如下输出:. fuzzy: subject:kountry~0.5 fuzzy 2: subject:kcountry~0.7

同样的性能可以揭示:使用通配符查询(WildcardQuery)和使用模糊査询是一致 的,它们还可以通过自定义方式禁止运行,具体可以参考[1]6.3.2小节。

3.3.7 MatchAIIDocsQuery

当输人*:*后,QueryParser 会生成 MatchAIIDocsQuery。

这个操作会将QueryParser所产生的所有Lucene核心査询类型进行包装。但这并 不是QueryParser的一切:它还支持一些非常实用的Query子句分组语法、加权子句以及将子句限制在特定的域上。

3.3.8 分组查询、域选择与加权

使用小括号建立子查询。创建查询时,默认的域名是创建QueryParser时提供的。使用 field:key可将搜索范围限定在某一个具体域,也可以使用field(a b c)的形式将三个项的查询以逻辑OR的形式连接起来。

在浮点数前面附上一个^符号可以对查询处理进行加权因子的设置。举例来说,查询表达式junit^2.0 testing会将junit TermQuery的加权系数设置为2.0,并维持 testing TermQuery的默认加权系数1.0。你可以对任何类型的查询进行加权,这其中包括插入的组。

ANTLR和JFlex项目可提供高于QueryParser能力的语法和功能。

4. 分析器与分析过程

对于Lucene核心来说,分析操作会出现在两个时间点:建立索引期间和使用QueryParser对象进行搜索时。在搜索结果中高亮显示被搜索内容时,也可能要用到分析操作。

几个内置分析器的解析示例如下:

4.1内置分析器说明

? WhitespaceAnalyzer:顾名思义,该分析器通过空格来分割文本信息,而并不

对生成的语汇单元进行其他的规范化处理.

? SimpIeAnalyzer:该分析器会首先通过非字母字符来分割文本信息,然后将语

汇单元统一为小写形式。需要注意的是,该分析器会去掉数字类型的字符, 但会保留其他字符。

? StopAnalyzer:该分析器功能与SimpIeAnalyzer类似.区别在于,前者会去除常

用单词.在默认情况下,它会去除英文中的常用单词(如the、a等),但你也可以根据需要自己设置常用单词.

? StandardAnalyzer:这是Lucene最复杂的核心分析器。基于JFlex-based语法

操作。它包含大量的逻辑操作来识别某些种类的语汇单元,比如公司名称、E-mail地址以及主机名称等. 它还会将语汇单元转换成小写形式,并去除停用词和标点符号.

详见[1] 第4.3节

在真正应用中,一般创建自己的分析器链。这需要自己扩展TokenFilter和Analyzer,一个例子见[1]4.4同音词,4.5同义词处理,4.6词干分析

4.2 分析过程简述

更多描述见[1]第四章

创建IndexWriter后,即为文档选择了默认的分析器。若某个文档需要使用特殊的分析器时,addDocument方法和updateDocument方法都可以选择对应的分析器。为了确保文本信息被分析器处理,可以在创建域时指定Field.Index.ANALYZED

或者Field. Index.ANLYZED_NO_NORMS参数。若需要将整个域值作为一个语汇单元处 理,如图 4.1 中 Field 3 所示,则可以将 Field. Index.NOT_ANALYZED 或 Field. Index. NOT_ANALYZED_NO_NORMS作为第4个参数传人该域。 Analyzer通过TokenStream类将文本逐字转换为语汇单元流。

语汇单元是分析过程产生的基本段元,携带文本值和其他一些元数据。[1]P111 项(term),位置增量(position-Increment)、 偏移量(offset)、类型(type)、标志位(flags)和有效负载。位置增量为0通常用于插入同义词或其他类似信息。

如:

public TokenStream tokenStream{String fieldName, Reader reader) { return new StopFilter{true,

new LowerCaseTokenizer(reader), stopWords); )

过滤顺序对分析过程的影响很重要。[1]P121

AnalyzerUtils的displayTokens(analyzer,text)方法可对文本分析过程进行显示。 AnalyzerDemo可以在命令行显示这一过程。详见[1]P117

4.3 域分析与处理,多值域处理

一个文档可能有多个同名域,这些域建立索引时会附加在一起,如需分离,可继承Analyzer类,并重载getPositionIncrementGap方法,将返回值增加到足够大,以表示两个域相隔较远。

针对不同的特定域使用不同的分析器,需要自己创建分析器,另外也可使用内置工具类 PerFieldAnalyzerWapper。其addAnalyzer方法可针对不同域添加新的

分析器。

只有在索引期间才知道一个域是否被分析过,该域的项一旦被编入索引,就与其他项完全一样。该问题非常重要,参见[1]P137。组合使用KeywordAnalyzer。索引与分析时,均可以对特定域使用特定分析器,这可以减少NOT_ANALYZED_NO_NORMS的使用。

4.4语言分析

对于不同语言的规范化处理,通常需要用到字符过滤类,CharReader、CharStream、CharFilter等。

适用于亚洲语言的分析器,contrib目录中:CJK、 Chinese、 Smart Chinese。

4.5 一些项目中使用的分析技术

Nutch 分析技术: 二元语法技术[1] 4.9节

5.高级搜索技术 5.1 域缓存

使用域缓存可以根据域值对搜索结果进行排序,在后台使用域缓存等。使用域缓存的一个重要限制是所有文档都必须拥有一个与指定域对应的单一域值,因此可能不能处理多值域,需要看版本支持。

域缓存只能用于包含单个项的域.这通常意味着该域在被索引时需要使用参

数Index .NOT_ANALYZED 或 Index . N0T_ANALYZED_N0_N0RMS ,如果使用诸如 Keyword- Analyzer等分析器的话,这些域还是可以被分析的,该分析过程只产生一个语汇单元. 如下使用方式:

如果每个文档都有一个称之为“weight”的域,那么你就可以获得每个文档中该域的域值,操作如下:

float [] weights = FieldCache.DEFAULT.getFloats(reader, \

然后,在需要知道产品文档重量的时候加人引用weights [dOCID]即可。域缓存支 持很多本地数据类型:byte、short、int、long、float, double、string 以及 Stringlndex 类,最后一项包括string值的排序顺序。

域缓存的初次建立耗费较大。只有reader关闭并删除其对应引用时,该缓存才会释放。

5.2 搜索结果排序、按不同语言字符集排序

能对结果进行排序的方法可调用search (Query,Filter,int, Sort)。如果不需要对结果进 行过滤的话,需要将Filter对象置为null。

使用IndexSearcher类的setDefaultFieldSortScoring () 方法,该方法有两个布尔类型参数:doTrackScores和doMaxScore。如果 doTrackScores参数为true,那么每个搜索命中结果都会被进行评分操作。如果 doMaxScore为true,那么程序将对最大分值的搜索命中结果进行评分操作。需要注意 的是,后者通常会耗费更多的系统资源,因为前者只对最有效的搜索结果进行评分操作。 按照相关性排序,默认排序方式。Sort.RELEVANCE 按索引顺序排序,Sort.INDEXORDER

通过域排序,当域整个被索引成单个语汇单元时可用,即不分析。当域内容存储时,可使用如下例的方式:

new Sort(new SortField(\

默认情况下,Lucene排序方向使用的是自然排序方式。对相关度采用降序排列,对其他域采用升序排列。需进行倒序处理,如下: new Sort(new SortField(\

需要对多个域进行排序时,创建Sort对象时,加入多个SortField对象。SortField 对象都包含域名、域类型和反向排序标志位。另外,SortField对象还包含集中域类型的常 量,它们是 SCORE、DOC、STRING, BYTE, SHORT、INT、LONG、FLOAT 和 DOUBLE。 其中SCORE和DOC常量是用于针对相关性和文档ID进行排序的特殊类型。

按照SortField.STRING类型排序时,程序内部会默认地调用String.compareTo() 方法来确定排列次序。不过,如果需要另一种不同的排列顺序,SortField类允许你通过制定一个本地化的方法来达到这个目的。Collator对象是通过Collator.getInstance(Locale)方法来初始化的,而Collator.compare()方法则用于决定排序方式。在指定Locale对象时,有两个可重载的SortField初始化方法可用: public SortField (String field, Locale locale)

public SortField (String field, Locale locale, boolean reverse)

这两个构造方法所创建的对象都是按照SortField.STRING类型排序的SortField对 象,因为Locale对象只适用于字符类型的排序,而不能用于数值类型。

5.3 MultiPhraseQuery

可用来将不同位置的相似匹配进行组合。如:

MultiPhraseQuery query = new MultiPhraseQuery(); query.add(new Term[]{ new Term(\允许其中一个项被首 new Term(\ });

query.add(new Term(\

先匹配

5.4 针对多个域的查询

第一种实现方式就是创建多值的全包含域来对所有域的文本进行索引。一定要在添加域值时对域值之间的空格进行位置增量处理,这样能避免程序错误地将两个域之间的域值进行査询匹配操作。然后程序就可以对这个全包含域进行搜索了。该方案有一些缺陷:你不能直接对每个域的加权进行控制,并且假如你还分开使用这些域的话,它会浪费磁盘空间。

第二种实现方式就是使用MultiFieldQueryParser,它是QueryParser的子类。它在会在后台程序中实例化一个QueryParser对象,用来针对每个域进行查询表达式的解析,然后使用BooleanQuery将查询结果合并起来。当程序向BooleanQuery添加查询子句时,默认操作符OR被用于最简单的解析方法中。为了实现更好的控制,布尔操作符可以使用BooleanClause的常量指定给每个域,如果需要指定的话可以使用 BooleanClause.Occur.MUST ,如果禁止指定可以使用 BooleanClause. Occur.MUST_NOT,或者普通情况为 BooleanClause.Occur. SHOULD。该方法用于复杂查询时,情况会更加复杂,需要分析以确定查询模式正确。

用于自动査询多值域的第三种实现方式就是使用髙级DisjunctionMaxQuery类,它会封装一个或多个任意的查询,将匹配的文档进行OR操作。你可以使用 BooleanQuery完成这个功能,正如MultiFieldQueryParser所完成的那样,但 DisjunctionMaxQuery的有趣之处在于它的评分方式:当某个文档匹配到多于一条查询时,该类会将这个文档的评分记为最髙分,而与BooleanQuery相比,后者会将所有匹配的评分相加。这样能产生更好的终端用户相关性。

DisjunctionMaxQuery还包含一个可选的仲裁器,因此所有处理都是平等的,一个 匹配更多查询的文档能够获得更高的评分。为了用DisjunctionMaxQuery进行多值域 査询,你需要创建一个新的基于域的Query,该Query得包含所有需要用到的域,然后 使用DisjunctionMaxQuery的add方法来包含这个Query。

5.5 跨度查询

即域中的启示语汇单元与终止语汇单元的位置。在搜索期间,跨度查询所跟踪的文档要比匹配的文档要多:每个单独的跨度(每个域可包含多个跨度)都会被跟踪。与TermQuery做个比较,TermQuery只是对文档进行简单的匹配操作,而SpanTermQuery除了完成这个功能外,还会保留每个匹配文档 对应的项位置信息。总的来说,跨度查询是一种计算密集型操作。

详见[1]5.5节,在短语查询及短语嵌套查询中,以上类能实现QueryPhrase不能实现的功能。

5.6 搜索过滤

过滤是Lucene中用于缩小搜索空间的一种机制,它把可能的搜索匹配结果仅限制在 所有文档的一个子集中。它们可以用来对已经得到的搜索匹配结果进行进一步搜索,以实现在搜索结果中的继续搜索(search-within-search)特性。此外,它们还可以用来限制文 档的搜索空间。安全过滤器允许用户只能看见属于“自己的”文档搜索结果,即使这些査 询实际上还匹配了其他的文档。

通过重载一个带有Filter对象参数的search()方法,你可以对任何一个Lucene搜索进行过滤。下面介绍一些Lucene内置的过滤器子类。

? TermRangeFilter只对包含特定项范围的文档进行匹配操作.功能与

TermRangeQuery—致,但前者没有评分操作.

? NumericRangeFilter只对特定域的特定數值范围进行文档匹配操作.功能与

NumericRangeQuery 一致,但前者没有评分搮作。

? FieldCacheRangeFilter针对某个项或者某个数值范围进行文档匹配操作,使用

时可以结合FieldCache (详见[1]5.1小节)获得更好的性能表现.

? FieldCacheTermsFilter针对特定项进行文档匹配操作,使用时可以结合

FieldCache获得更好的性能表现。

? QueryWrapperFilter可以将任意Query实例转换为Filter实例,转换时仅将

Query实例对应的匹配文档作为过滤空间,该操作忽略文档评分.

? SpanQueryFilter将SpanQuery实例转换成SpanFilter实例,这个操作会将Filter

基类派生为对应的子类并且会在子类中新增一个方法,用于为每个文档匹配操作提供位置跨度访问.该类与QueryWrapperFilter类似,只不过前者的转换结果为SpanQuery类。

? PrefixFilter只匹配包含特定域和特定前缀的项的文档.功能与PrefixQuery—致,

但前者没有评分操作。

? CachingWrapperFilter是其他过滤器的封装器(Decorator),它将结果缓存起来

以便再次使用,从而提高系统性能。

? CachingSpanFilter 与 CachingWrapperFilter 功能一致,但前者的缓冲目标是

SpanFilter.

? FilteredDocIdSet允许对过滤器进行过滤,一次处理一个文档。使用它时, 必

须首先派生它的子类,然后在该子类中定义匹配方法。 详见[1]5.6节

QueryWrapperFilter使用查询中匹配的文档来对随后搜索中可以访问的文档进行 限制。它允许你将带有评分功能的查询转换为不带评分功能的过滤器。使用 QueryWrapperFilter可以将被搜索文档限制在特定的类别范围内。 public void testQueryWrapperFiIter() throws Exception { TermQuery categoryQuery =

new TermQuery(new Term(\; Filter categoryFilter = new QueryWrapperFilter(categoryQuery); assertEquals(\1,

TestUtil.hitCount(searcher, allBooks, categoryFilter}); )

SpanQueryFilter与之类似。

对于一些安全权限应用,在索引期间将文档和用户或角色关联起来,然后使用上述类,就可以限定在一定范围内。

还可以进行反向操作,使用 ConstantScoreQuery将过滤器转换成查询以用于随后

的搜索。生成的查询只对过滤器 所包含的文档进行匹配,然后赋予它们与查询加权相等的评分。

FilteredDocldSet类是一个抽象类,它能够接受一个filter参数,在随后的匹配操作期间,只要碰到一个文档,它就会调用对应的match方法(即我们所实现的其子类的同名方法)来检查该文档是否匹配。这使得你能够自定义实现match方法 中的逻辑,并能够对其他过滤器进行动态过滤。该方法效率很高,因为 FilteredDocldSet从不为过滤器完全分配字节空间。而实际上,每个匹配操作都是 根据程序要求而运行的。

该方法可以用来加强权限操作,特别是在大量权限为静态(当前在索引中)但又需要动态检查一定数量权限的时候非常有用。对于这样一个使用案例,你需要创建一个基于索引内容的标准权限过滤器,然后实现FilteredDocIdSet子类,并覆盖其match 方法以实现自己的动态权限逻辑。

lucene contrib模块有一个ChainedFilter过滤器,可组成复杂的过滤器链。

5.7 使用功能查询实现自定义评分

功能查询带给你一定的自由度来通过编程的形式使用自己的逻辑来对匹配文档进 行评分。所有用到的类都来自于org .apache .lucene. search. function程序包。

所有功能査询类的基类是ValueSourceQuery。该查询会对所有文档进行匹配操作, 但对每个匹配文档的评分是通过该类初始化时传人的ValueSource而设置的。该程序包提供了一个FieldCacheSource子类,它负责从域缓存中导出域值。你还可以创建自 定义的ValueSource——举例来说,通过这个自定义的类从外部数据库中导出评分。但很可能最简单的方案是使用FieldScoreQuery,它是ValueSourceQuery的子类,并能从指定的索引域中静态导出每个文档的评分。该域必须是数值型的,并且不能使用 norms索引,以及每个文档中该域只能由一个语汇单元。通常你可以使用 Field. Index.NOT_ANALYZED_NO_NORMS参数进行语汇单元化处理。一个样例如下:

首先,文档中需要包含“score”域,如下所示:

doc.add(new Field(\\

Field.Store.NO,

Field.Index.NOT_ANALYZED_NO_NORMS)); 然后,创建功能查询:

Query g = new FieldScoreQuery(\

该查询会对所有文档进行匹配操作,并根据文档中的“score”域而对每个文档賦予 评分。你还可以使用SHORT、INT或FLOAT类型的常量。在程序后台,该功能査询会使 用域缓存,因此这里也需要对此进行[1]5.1小节中的所提到的性能权衡。

当你使用第二种功能查询CustomScoreQuery时,就能实 现一些有趣的功能。该查询使你能够将通常的Lucene査询和一个或多个其他功能查询 联合起来使用。 现在我们可以使用先前创建的FieldScoreQuery类以及一个CustomScoreQuery类来 计算评分:

Query q - new QueryParser(Version.LUCENE_30, \

new StandardAnalyzer( Version.LUCENE_3 0)) .parse{\;

FieldScoreQuery qf = new FieldScoreQuery(\FieldScoreQuery.Type.BYTE);

CustomScoreQuery customQ = new CustomScoreQuery(q, qf) {

public CustomScoreProvider getCustomScoreProvider(IndexReader r) { return new CustomScoreProvider(r) { public float customScore(int doc, float subQueryScore, float valSrcScore) {

return (float) (Math.sqrt(subQueryScore) * valSrcScore);

在本示例中,我们通过解析用户的搜索文本而创建了一个通常的查询q。接下来

我们将创建先前使用过的同样的FieldScoreQuery,并根据score域来对文档进行评最后,我们创建了 一个 CustomScoreQuery 类,并覆盖了其中的 getCustomScoreProvider 方法, 让其返回一个包含针对每个匹配文档所采用的自定义评分算法的类。在这个人为的案例中,我们将查询评分开平方,并随后用它与FieldScoreQuerry所提供的静态评分相乘。你可以使用任意的程序逻辑来创建自己的评分系统。

需要注意的是,传给getCustomScoreProvider的IndexReader参数是针对段的,意思是如果索引包含的段不止一个,那么搜索期间会多次调用这个方法。强调这点是重要的, 因为它使你的评分逻辑能够有效使用段reader来对域缓存中的值进行检索。 其他示例见[1]5.7节

5.8 针对多索引的搜索

某些应用程序需要保持多个分离的Lucene索引,但又需要在捜索过程中能够使针 对这几个索引的所有搜索结果合并输出。有时候,导致这类分离索引出现可能是为了方便程序运行或者管理上的原因——例如,如果不同的用户或者组织为不同的文档集合创建了不同的索引,就会导致多个分离索引的出现。有时这种情况的出现是为了增大文档容量。例如,一个新闻网站可能每月新创建一个索引,然后在搜索时指定该月份对应的索引即可。出于这些原因,Lucene提供了两个很实用的类来针对多索引进行搜索。

使用Multisearcher类可以搜索到所有索引,并以一种指定的顺序(或者是以评分递 减的顺序)将搜索结果合并起来。多索引搜索(MultiSearcher)类的使用是相对于单索引搜索(IndexSearcher)而言的,除非你通过一组IndexSearcher对象去搜索一个以上 的目录(因而这是一^髙效的封装模式,它将大部分工作都委托给了子搜索器完成)。

使用方法很简单,new MultiSearcher(IndexSearcher[] searchers),就可以直接使用了。

ParallelMultiSearcher是MultiSearcher的多线程版本,它会为每个

本文来源:https://www.bwwdw.com/article/rco.html

Top