《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();
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,009评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,808评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,891评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,283评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,285评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,409评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,809评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,487评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,680评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,499评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,548评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,268评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,815评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,872评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,102评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,683评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,253评论 2 341

推荐阅读更多精彩内容

  • 1. 案例分析:什么时全文检索,如何实现全文检索   1.1 案例   实现一个文件的搜索功能,通过关键字搜索文件...
    东方舵手阅读 1,172评论 0 1
  • 目录结构:1.全文检索 2.Lucene入门3.Lucene进阶 全文检索 一, 生活中的搜索:1.Win...
    CoderZS阅读 1,640评论 0 12
  • Solr&ElasticSearch原理及应用 一、综述 搜索 http://baike.baidu.com/it...
    楼外楼V阅读 7,241评论 1 17
  • 论文:pix2pix代码:GitHub 本文最大的贡献在于提出了一个统一的框架解决了图像翻译问题。所谓图像翻译,指...
    Mordekaiser阅读 6,544评论 0 4
  • 没有玫瑰的娇艳 没有牡丹的华贵 静静地呆在窗台那个小小的角落 只要一点点水 你就可以静默地生长 很不起眼 很不养眼...
    梦殇古城阅读 368评论 2 4