《lucene in action》笔记:为应用程序添加搜索功能

1.实现简单的搜索功能

1.1 对特定项的搜索

IndexSearcher类是用于对索引中文档进行搜索的核心类。它有几个重载的搜索方法。可以使用最常用的搜索方法对特定的项进行搜索。一个项由一个字符串类型的域值和对应的域名构成。下面的例子展示了使用TermQuery进行搜索:

public class BasicSearchingTest extends TestCase {
    public void testTerm() throws Exception {
        Directory dir = TestUtil.getBookIndexDirectory();
        IndexSearcher searcher = new IndexSearcher(dir);
        Term t = new Term("subject", "ant");
        Query query = new TermQuery(t);
        TopDocs docs = searcher.search(query, 10);
        assertEquals("Ant in Action", 1, docs.totalHits);
        t = new Term("subject", "junit");
        docs = searcher.search(new TermQuery(t), 10);
        assertEquals("Ant in Action, JUinit in Action, Second Edition", 2, docs.totalHits);
        searcher.close();
        dir.close();
    }
}

1.2 解析用户输入的查询表达式:QueryParser

从上面的例子中看出,Lucene的搜索方法需要接受一个query对象作为参数。而QueryParser的作用就是将用户的输入转换成query对象。下面的例子展示了如何将用户输入的"+JUINIT +ANT -MOCK"转换成query对象并执行查询操作

public void testQueryParser() throws Exception {
    Directory dir = TestUtil.getBookIndexDirectory();
    IndexSearcher searcher = new IndexSearcher(dir);
    QueryParser parser = new QueryParser(Version.LUCENE_30, "contents", new SimpleAnalyzer());
    Query query = parser.parse("+JUINIT +ANT -MOCK");
    TopDocs docs = searcher.search(query, 10);
    assertEquals("Ant in Action, JUinit in Action, Second Edition", 2, docs.totalHits);
    searcher.close();
    dir.close();
}

通过QueryParser,应用可以解析非常复杂的查询表达式,最后生成的query实例可能会非常庞大而复杂。

2.使用IndexSearch类

从上述的例子中可以看出lucene的搜索操作非常简单。首先创建一个IndexSearcher实例,它负责打开所用,然后使用该实例的search方法即可进行搜索。

2.1 创建IndexSearcher类

IndexSearcher实例的创建非常简单。

Directory dir = FSDirectory.open("/path/to/index");
IndexReader reader = IndexReader.open(dir);
IndexSearcher searcher = new IndexSearcher(reader);

上述的例子第一步创建了一个目录的实例,该实例指向了索引存放的位置,第二步创建一个索引只读的实例,最终创建一个IndexSearcher实例。
打开IndexReader需要较大的系统开销,所以原则上复用IndexReader就尽量复用。上述例子中的IndexReader是手动创建的,当IndexSearcher关闭时,IndexReader并不会自动关闭。另外还可以从索引目录中直接创建IndexSearcher,这种情况下系统会自动创建一个只属于该searcher的IndexReader实例,当searcher被关闭时,reader也会同时被关闭。
需要理解的一点是,IndexReader实例是索引的一个快照,所以如果reader被创建之后,索引继续更新,该reader是无法看到被更新的索引内容的。此时如果想看到最新的更新,必须新打开一个Reader。记住,索引的更新不能立即可见,必须通过重新打开IndexReader。

2.2 实现搜索功能

IndexSearcher实现了很多的search方法。在程序后台,search方法会快速完成大量的工作。它会访问所有候选的搜索匹配文档,并返回符合每个查询约束条件的结构。最后,它会收集最靠前的几个搜索结果并返回给调用程序。

2.3 使用TopDocs类

我们已经调用了search方法,并获取其返回的TopDocs对象,我们可以利用该对象来访问搜索结果。TopDocs有以下几个重要的属性或方法:
totalHits: 匹配搜索条件的文档数量
scoreDocs: 包含搜索结果的ScoreDoc对象数组
getMaxScore(): 如果已完成排序则返回最大评分

2.4 搜索结果分页

如果要将搜索结果呈现给终端用户,通常是只将前10~20个最相关的文档展现出来。通过ScoreDocs进行分页处理是一个常见的需求。

2.5 近实时搜索

Lucene2.9版本发布的新功能之一就是近实时搜索,它使你能够使用一个打开的IndexWriter快速搜索索引的变更内容,而不必首先关闭writer或向该writer提交。上面的例子中的IndexReader实例的创建依赖于Directory,近实时搜索的IndexReader创建如下:

IndexWriter writer = new IndexWriter(dir);
IndexReader reader = writer.getReader();

当有新的索引写入时,使用下面的方法重新打开一个IndexReader

IndexReader newReader = reader.reopen()
reader.close()
searcher = new IndexSearcher(reader)

上述的reopen方法打开的reader可以获取到writer写入但未提交的文档。注意一点IndexReader始终是索引某一时刻的只读快照,如果想获取到更新的索引,必须重新打开一个IndexReader。

3.理解Lucene的评分机制

每当搜索到匹配文档时,该文档会被赋予一定的分值,用以反映匹配程度。评分公式这里就不具体展开的,有兴趣的可以自行了解

4. Lucene的多样化查询

除了上述例子中的单项查询,Lucene还支持其它一些场景的查询,如范围查询,组合查询等等。这里简单介绍几个例子

4.1 在指定的项范围内搜索:TermRangeQuery类

索引中的各个Term对象会按照字典编排顺序进行排序,并允许在Lucene的TermRangeQuery对象提供的范围内进行文本项的直接搜索。

public void testTermRangeQuery() throws Exception {
    Directory dir = TestUtil.getBookIndexDirectory();
    IndexSearcher searcher = new IndexSearcher(dir);
    TermRangeQuery query = new TermRangeQuery("title2", "d", "j", true, true);
    TopDocs matches = searcher.search(query, 100);
    assertEquals(3, matches.totalHits);
    searcher.close();
    dir.close();
}

上述代码的功能是搜索起始字母范围从d到j的书籍标题。其中TermRangeQuery初始化方法中的两个Boolean对象参数表示是否包含搜索范围的起点或终点。

4.2在指定的数字范围内搜索:NumericRangeQuery

如果使用NumericField对象来索引域,那么你就能有效地使用NumericRangeQuery类在某个特定范围内搜索该域。下面的例子展示了搜索2006年5月到2006年9月出版的书籍

public void testInclusive() throws Exception {
    Directory dir = TestUtil.getBookIndexDirectory();
    IndexSearcher searcher = new IndexSearcher(dir);
    NumericRangeQuery query = new NumericRangeQuery("pubmonth", 201605, 201609, true, true);
    TopDocs matches = searcher.search(query, 10);
    assertEqual(1, matches.totalHits);
    searcher.close();
    dir.close();
}
4.3组合查询:BooleanQuery类

通过使用BooleanQuery类可以将各种查询类型组合成复杂的查询方式,而BooleanQuery本身是一个Boolean子句的容器。
下面的例子展示了使用AND查找我们所关注的主题为search的最新书籍

pulic void testAnd() throws Exception {
    TermQuery searchingBooks = new TermQuery(new Term("subject", "search"));
    Query books2010 = NumericRangeQuery.newIntRange("pubmonth", 201001, 201012, true, true);
    BooleanQuery searchingBooks2010 = new BooleanQuery();
    searchingBooks2010.add(searchingBooks, BooleanClause.Occur.MUST);
    searchingBooks2010.add(books2010, BooleanClause.Occur.MUST);
    Directory dir = TestUtil.getBookIndexDirectory();
    IndexSearcher searcher = new IndexSearcher(dir);
    TopDocs matches = searcher.search(searchingBooks2010, 10);
    assetTrue(TestUtil.hitsIncludeTitle(searcher, matches, "Lucene in Action, Second Edition"));
    searcher.close();
    dir.close();
}

推荐阅读更多精彩内容

  • 1. 案例分析:什么时全文检索,如何实现全文检索   1.1 案例   实现一个文件的搜索功能,通过关键字搜索文件...
    东方舵手阅读 144评论 0 1
  • 目录结构:1.全文检索 2.Lucene入门3.Lucene进阶 全文检索 一, 生活中的搜索:1.Win...
    CoderZS阅读 430评论 0 12
  • 概要: 全文检索的原理和基本概念(铺垫) Lucene简介,索引文档和检索文档的过程(主要) Lucene 相似度...
    GhostStories阅读 681评论 0 3
  • Solr&ElasticSearch原理及应用 一、综述 搜索 http://baike.baidu.com/it...
    楼外楼V阅读 3,017评论 1 15
  • 论文:pix2pix代码:GitHub 本文最大的贡献在于提出了一个统一的框架解决了图像翻译问题。所谓图像翻译,指...
    Mordekaiser阅读 2,069评论 0 1