Lucene实战(第2版)

978-7-115-25177-0
作者: 【美】Michael McCandless Erik Hatcher Otis Gospodnetic
译者: 牛长流肖宇
编辑: 杜洁

图书目录:

详情

全书分为两部分。第1部分着重于Lucene的核心API介绍,并按照把Lucene集成到程序中的顺序组织;第2部分通过对Lucene内置工具的介绍,展示了Lucene技术的高级应用和在各种程序语言上的移植。

图书摘要

版权信息

书名:Lucene实战(第2版)

ISBN:978-7-115-25177-0

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。

• 著    [美] Michael McCandless Erik Hatcher Otis Gospodnetic

  译    牛长流  肖 宇

  责任编辑 杨海玲

• 人民邮电出版社出版发行  北京市丰台区成寿寺路11号

  邮编 100164  电子邮件 315@ptpress.com.cn

  网址 http://www.ptpress.com.cn

• 读者服务热线:(010)81055410

  反盗版热线:(010)81055315


Original English language edition, entitled Lucene in Action (Second Edition) by Michael McCandless, Erik Hatcher, Otis Gospodnetic, published by Manning Publications Co., 209 Bruce Park Avenue, Greenwich, CT 06830. Copyright © 2010 by Manning Publications Co.

Simplified Chinese-language edition copyright ©2011 by Posts & Telecom Press. All rights reserved.

本书中文简体字版由Manning Publications Co.授权人民邮电出版社独家出版。未经出版者书面许可,不得以任何方式复制或抄袭本书内容。

版权所有,侵权必究。


本书基于Apache的Lucene 3.0,从Lucene核心、Lucene应用、案例分析3个方面详细系统地介绍了Lucene,包括认识Lucene、建立索引、为应用程序添加搜索功能、高级搜索技术、扩展搜索、使用Tika提取文本、Lucene的高级扩展、使用其他编程语言访问Lucene、Lucene管理和性能调优等内容,最后还提供了三大经典成功案例,为读者展示了一个奇妙的搜索世界。

本书适合于已具有一定Java编程基本的读者,以及希望能够把强大的搜索功能添加到自己的应用程序中的开发人员。本书对于从事搜索引擎工作的工程技术人员,以及在Java平台上进行各类软件开发的人员和编程爱好者,也具有很好的学习参考价值。


Michael McCandless已从事了10年以上搭建搜索引擎相关工作。在1999年,他和其他三人创立了iPhrase Technologies公司,开始推出基于用户为中心的商业搜索软件,该软件是用Python和C++编写的。在2005年IBM公司接收iPhrase项目后,Michael便投入了Lucene项目并开始贡献相应补丁,2006年他成为该项目的提交者之一并在2008年成为PMC成员。Michael曾在MIT获得过本科、硕士和博士学位,现在与妻子Jane和4个可爱的孩子Mia、Kyra、Joel和Kyle居住在马萨诸塞州的Lexington。Michael的博客地址为http://chbits.blogspot.com

Erik Hatcher在自己感兴趣且颇具挑战性的技术领域进行了大量编码、写作和演讲。他曾经使用不同的技术和计算机语言编写过多种不同行业的软件。Erik和Steve Loughran曾合著了《使用Ant进行Java开发》(Java Development with Ant,Manning出版社2002年出版),该书曾得到业内人士的广泛赞誉。从Erik的第一本书出版以来,他已经在大量的行业会议上发表了演讲,这些会议包括:No Fluff、Just Stuff巡回研讨会、JavaOne、O’Reilly’s Open Source Convention、JavaZone、devoxx、用户组以及有时还有网上研讨会。 作为Apache软件基金(Apache Software Foundation)成员之一,他在包括Lucene和Solr等项目中是一个活跃的贡献者和提交者。Erik热情地呈现了自己喜爱的技术,最近值得一提的是Solr、Solritas、Flare、Blacklight和solr-ruby——他喜欢研究用户体验和Solr之间的交集。Erik还加入了Lucid Imagination,在那里努力地投入开源搜索产品的开发中。Erik已逐步适应了弗吉尼亚州中部的宁静生活。

Otis Gospodnetic在Lucene成为Apache Lucene项目前就已经是Lucene开发人员了。他是Sematext公司的共同创始人,该公司专注于有关搜索(侧重于Lucene、Solr和Nutch)和分析(请参考BigData、Hadoop等)方面的个人服务及产品。Otis已从事Lucne和Solr项目多年,一些他以前的包括Lucene等技术著作已由O’Reilly Network和IBM developerWorks发表。多年前,Otis还撰写了《To Choose and Be Chosen: Pursuing Education in America》一书,该书为想在美国念书的外国人提供了参考手册;其内容是基于作者自己的经历而撰写的。Otis目前居住于纽约,负责NY Search & Discovery Meetup。


《Lucene实战》(第2版)将为读者提供关于最优秀的开源搜索引擎——Lucene的具体使用细节、使用体验、应用范围以及使用技巧。

本书假定读者已经具备了基本的Java编程技能。Lucene本身仅仅是一个JAR文件包,文件尺寸小于1MB,它不需要其他依赖包并能集成到最简单的Java控制台程序中,也可以在最复杂的企业级应用中对它进行集成。

本书第1部分内容涵盖了Lucene核心应用程序接口(API),我们将按照将Lucene集成到应用程序的顺序来组织这部分内容。

第2部分不再介绍Lucene的内置功能,而是介绍了如何使用Lucene来构造具体的应用。

第3部分(第12、13、14章)通过贡献者提供的案例学习来回顾Lucene各个技术细节,这些贡献者以Lucene为核心构建了很多有价值、快速和宽泛的搜索应用程序。

自从本书的第1版出版5年以来,Lucene已经发生了较大的变化。这是因为对于一个成功的并具有强大技术架构的开源项目来说,社区内的使用者和开发者已随时间的推移逐步成长起来,由此为我们带来了大量的令人惊异的改进。下面是这些改进的样例:

我们在第12、13、14章中加入了几乎全新的案例学习内容。另外加入了一个新的章节(第11章)用来介绍Lucene的管理特性。第7章内容在本书的第1版中主要介绍了用于解析各种不同文档类型的自定义框架,本书的第2版则基于Tika对该内容进行了全部重写。另外,所有代码示例都已更新至Lucene 3.0.1版本的API中。当然,我们还收录了大量读者反馈信息。

那些希望能够把强大的搜索功能加入到自己应用程序中的开发者应该阅读本书。《Lucene实战》第2版还适合那些对Lucene或索引和搜索技术感兴趣、但暂时还不需要使用Lucene的开发者。将Lucene的专有技术加入到你的知识库中,会使你在以后进行的项目开发中获益匪浅——因为搜索将会是今后一个非常热门的话题。

本书主要按照Java实现的Lucene版本进行介绍,并且大部分的示例代码都是用Java语言编写的。熟悉Java的读者很快就可以轻松进行了,具备Java方面的专业知识无疑对阅读本书有较大帮助;不过现在已经出现了C++、C#、Python和Perl版本的Lucene。Java版的Lucene和其他语言版本的Lucene之间在基本概念、技术甚至自身的API之间都是相通的。

读者可以从Manning出版社的主页http://www.manning.com/LuceneinActionSecondEdition或http://www.manning.com/hatcher3上下载本书源代码。这些代码的使用说明可以从源代码包中的README文件中找到。

本书的大部分代码都是由我们编写的,读者可以在源代码包中找到这些代码,它们是由Apache Software Licence(http://www.apache.org/licenses/License-2.0)颁发许可的。而其中一些代码(特别是案例分析中的代码,以及其他程序移植的Lucene代码)并不由我们的源代码包提供;本书展示的代码片段是由Lucene社区捐赠者贡献的。在几个案例中使用到的部分代码片段是从Lucene代码库中获取的,并都得到了Apache Software License 2.0的许可。

代码示例并不包括导入的代码包及对应的导入声明,这主要是为了节省篇幅;读者如果需要了解这部分细节,可以去研究对应的实战代码。同样是为了节省篇幅的缘故,代码片段的很多地方省略了throws Exception声明,而读者在编写代码时需要声明和获取特定的异常,如果程序在运行期间抛出这个异常则需要进行处理。在一些代码示例中有的代码片段附带了文本内容,而这些示例是不能直接使用的;这些示例代码包含在名为Fragments.java源文件中,每个子目录下面都有这样一个文件。

我们相信本书中的代码示例应该有一流的质量和很强的可用性。而在计算机书籍中常用的“hello world”程序则经常会侮辱我们的智商,而且这对于读者了解如何进入实际的应用几乎毫无帮助。

在《Lucene实战》第2版中我们通过独特的方法列举了一些源代码例子。这些例子中很多都是实际的JUnit测试用例(http://www.junit.org),对应的版本号位4.1。JUnit实际上是Java单元测试框架,它为我们提供了一种可重复运行的方式来判断指定的代码是否在按照预期运行。它能将被测代码进行明确的隔离,方法是在这段代码前面加入这个测试用例,并通过将被测代码放置在测试用例所调用的API后面,来指示JUnit如何完成这项测试。使用IDE或者Ant的自动化JUnit测试用例时,可以只通过一步操作(或者通过持续集成方式)就能够建立测试。我们之所以在本书中使用JUnit,是因为在日常的项目中我们一直都在使用它。因此也想让读者了解我们在现实当中是如何调试代码的。测试驱动开发(TDD,Test Driven Development)是我们极力推崇的一种程序开发方式。

如果读者对JUnit还不熟悉,那么我们推荐几本关于JUnit的入门书籍:《Pragmatic Unit Testing in Java with JUnit》,作者为Dave Thomas和Andy Hunt;另外一本是由Manning出版社出版的《JUnit in Action》,作者为Vincent Massol和Ted Husted,该书第2版的作者为Petar Tahchiev、Felipe Leme、Vincent Massol和Gary Gregory。

出现在清单或文本文件中的源代码,以固定宽度的字体表示,以便从普通文本中将这些源代码分离出来。文本中的Java方法名称一般不包括完整的方法声明。

为了调整可用的页面空白,代码都按照限定的宽度进行排版,并在合适的地方加入续行符。

我们没有讨论引入语法,也很少涉及类名全称——因为这样做会占据书中有价值的篇幅。详情请参考Lucene的Javadocs。所有优秀的IDE都对自动添加引入语法有很好的支持。虽然不知道类名全称,Erik仍然利用IDEA IntelliJ兴奋地编写出代码;Otis用Xemacs做了相同的工作。只需要把Lucene JAR的路径加入到自己项目的classpath里就可以完成所有的设置。关于classpath(这是一件很烦杂的工作),我们假定Lucene JAR和其他必要的JAR都是可用的,不再显式表示。Lib目录包含源代码及其用到的JAR包。当读者运行ant目标命令时,这些JAR包会被放置在classpath目录下。

我们为本书创建了很多示例,这些示例都可以免费获取。读者可以从Manning出版社网站的Lucene实战页面http://www.manning.com/LuceneinActionSecondEdition页面下载该ZIP包,它包含了本书中提到的所有示例代码(http://www.manning.com/hatcher3)。有关运行这些示例代码的详细说明,可以从扩展文档的主目录里查到,对应的文件名为README。

本书的大部分内容都围绕着一套通用的示例数据来保持一致性,以避免在每节中都使用一组全新的数据。下表中的示例数据都由相关书籍的详细信息组成。表1提供了贯穿全书的数据供读者参考,并有助于读者理解我们的示例。

这些数据除了表中所展示的域以外,还包括ISBN、URL和出版月份等几个域。当读者从www.manning.com/hatcher3中下载并解压源代码后,本书中用到的源代码即以*.properties文件格式保存在data子目录中,而src/lia/common/CreateTestIndex.java中的命令行工具则用于创建本书所用到的测试索引。类别域和主题域是根据我们的主观判断并根据它们所属的范围给出的,而其他的信息则是关于这些书的客观内容。

表1 全书用到的示例数据

标题/作者

类 别

主 题

A Modern Art of Education Rudolf Steiner

/教育/教学

education philosophy psychology practice Waldorf

Lipitor, Thief of Memory Duane Graveline, Kilmer S. McCully, Jay S. Cohen

/健康

cholesterol,statin,lipitor

Nudge: Improving Decisions About Health, Wealth, and Happiness Richard H. Thaler, Cass R. Sunstein

/健康

information architecture,decisions, choices

Imperial Secrets of Health and Longevity Bob Flaws

/健康/可选/中文

diet chinese medicine qi gong health herbs

Tao Te Ching 道德经 Stephen Mitchell

/哲学/东方

taoism

Gödel, Escher, Bach: an Eternal Golden Braid Douglas Hofstadter

/科技/计算机/人工智能

artificial intelligence number theory mathematics music

Mindstorms: Children, Computers, And Powerful Ideas Seymour Papert

/科技/计算机/编程/教育

children computers powerful ideas LOGO education

Ant in Action Steve Loughran, Erik Hatcher

/科技/计算机/编程

apache ant build tool junit java development

JUnit in Action, Second Edition Petar Tahchiev, Felipe Leme, Vincent Massol, Gary Gregory

/科技/计算机/编程

junit unit testing mock objects

Lucene in Action, Second Edition Michael McCandless, Erik Hatcher, Otis Gospodnetić

/科技/计算机/编程

lucene search java

Extreme Programming Explained Kent Beck

/科技/计算机/编程/方法论

extreme programming agile test driven development methodology

Tapestry in Action Howard Lewis-Ship

/科技/计算机/编程

tapestry web user interface components

The Pragmatic Programmer Dave Thomas, Andy Hunt

/科技/计算机/编程

pragmatic agile methodology developer tools

如果你购买了《Lucene实战》第2版,你就可以免费访问由Manning出版社管理的一个私人网络论坛,在那里你可以和作者以及其他读者就这本书的内容进行讨论。要访问并订阅该论坛的内容,请在浏览器地址栏中输入网址:http://www.manning.com/LuceneinActionSecondEdition。这个页面提供了有关注册后如何登录,在网站上能获取哪些帮助以及论坛的管理规则等信息。

通过将介绍、概括与示例引导融合在一起,In Action系列图书被尽力设计得有助于学习和记忆。根据对认知科学的研究,人们最容易记忆的事情就是那些他们通过自主探索所发现的内容。

虽然Manning出版社没有认知科学家,但我们仍然相信为了巩固所学到的知识,必须经过一系列探索、实践,并复述学习内容的阶段。只有在积极地对它们进行探索之后,人们才能理解并铭记新的事物;从另一角度来说就是掌握它们。人们通过实际操作学习新知识。In Action系列图书的最核心部分就是通过实例驱动。它鼓励读者通过实践、运用有创新意识的代码并探索新的想法。

本书适用这个标题还有另一个很直接的原因:我们的读者都很忙。他们使用一本书可能只是为了工作甚至是为了解决一个棘手的问题。读者需要的是一本可以方便查阅的书,并且他们只需掌握自己想要的内容。他们需要的是一本能够有助于实践的书。而这个系列的书就是为了满足这类读者而设计的。

《Lucene实战》第2版的封面插图是“一个叙利亚海岸居民”。这幅图取自于描绘奥斯曼帝国(Ottoman Empire)时期服饰的一本画册,这本画册是由位于伦敦Old Bond街的William Miller出版社于1802年1月1日出版的。该画册的扉页已经丢失了,到目前为止我们还没有找到。在该画册的目录中,作者用英语和法语对这些图进行了标注。每幅插图都带有两位绘制者的名字,毫无疑问,如果他们知道自己的作品出现在200年后的计算机编程书籍的封面上,他们一定会惊喜万分。

Manning出版社的一个编辑在西曼哈顿大街26号的Garage古玩市场买到了这本画册。出售这本画册的人是一个住在土耳其首都安卡拉的美国人,他在动身去安卡拉的当天卖掉了这本画册。由于这位编辑身上所带的现金不够支付这本画册,他希望能用信用卡和支票代替,不过被婉言谢绝了。

在当天晚上那个卖画人就飞回了安卡拉,买画的事情似乎也毫无希望了。那么你会问,我们是怎样买到画的呢?其实他们只是使用了一种古老的口头协议——握手。卖画人非常爽快地提议:把自己的银行账号留给编辑,让编辑先把画册拿回去,然后再将买画的钱电汇给它。不必说,我们在第二天就把钱转到了他的账上,这件事给我们留下了美好的回忆,我们非常感激这位素昧平生的朋友对我们的信任。这使我想起了很早之前发生的一些事情。

这些来自土耳其民族的收藏画和Manning出版社其他书籍的封面插图一样,使我们清晰地看到了两个世纪以前土耳其人民丰富多样的民族服装。它们唤醒了我们与那个时代的孤立感和距离感:除了我们这个精神高度紧张的时代以外的每一个其他历史时期的孤立感和距离感。

从那时起着装方式已经发生了很大的改变,当时不同的地区有着不同的着装方式,因此当时服装样式也异常丰富,但这种多样性已随着时间的推移而逐渐消失了。现在已经很难再从服装上区别不同地区的人了。或许我们可以用一种很乐观的态度去看待它:我们是将文化、视觉的多样性和更为丰富多彩的私人生活进行了交换,或者是和更多样化且更有意义的理性和专业生活进行了交换。作为Manning出版社的成员,我们对这个出版社所拥有的独创性、主动性,当然还包括本书基于200年前多样性的区域生活的封面,以及从这幅藏品里挖掘出来的画中所体现的生活,感到十分庆幸。


如果你正准备在自己的应用程序中使用Lucene,或者是对Lucene的功能感兴趣的话,本书无疑是最好的选择。

——JavaLobby

搜索功能是对信息时代的增强。而本书正是将我们引向这个宝藏的入口。它通过大量的代码示例和令人信服的解释,巧妙地阐明了应用程序编程接口(API),为这个优秀工具打开了方便之门。

——Computing Reviews

对于任何想要学习Lucene或者甚至是准备在自己的应用程序中嵌入搜索功能,或者想要总体了解信息检索功能的人来说,本书是必读的。强烈推荐本书!

——TheServerSide.com

本书内容的组织是经过深思熟虑的,编排也很合理,明显优于其他同类书籍。我很喜欢阅读本书。如果你有任何的文本搜索方面的需求,本书将能指导你成功达到这个目标。甚至,如果你正在寻找和下载一个预先写好的搜索引擎,那么本书将能为你提供一个有关信息检索、文本索引和搜索的背景知识。

——Slashdot.org

本书并不只是纸上的墨水印,而更像一个水晶球——我在阅读过程中找到了对于一些紧迫问题的解决方法。

——Arman Anwar,Arman@Web

本书为使用和定制Lucene提供了详细的规划图。它详细介绍了这款最为流行的开源搜索引擎的内部工作机制。它加入了代码示例,并强调了动手的学习方式。

——SearchEngineWatch.com

Harcher和Gospodnetic作为Lucene的两个核心提交人员,将自己的经验完美地写入本书。本书能帮助任何对Lucene不够熟悉或者对开发搜索引擎不太熟悉的开发人员快速进入该领域。我向那些Lucene初学者、需要在自己应用程序中添加强大的索引和搜索功能的人,或者需要大量Lucene参考资料的人强烈推荐本书。

——Fort Worth Java Users Group

本书是最杰出、最全面和最新的。建议拿起这本书来了解如何释放Lucene的潜力。

——来自于Val的博客

代码示例非常有用,并且可以重用。

——Scott Ganyo,Lucene Java提交人员

本书充满示例和有关如何有效使用这个令人难以执行的强大工具的建议。

——Brian Goetz,Quiotix公司

本书让我激发出了有关Lucene的神奇力量。

——Reece Wilton,迪士尼互联网集团

以JUnit测试用例形式出现的代码示例能带给你大量帮助。

——Norman Richards,《XDoclet in Action》合著者

一个快速而简单地使Lucene工作的手册。

——Books-On-Line

这是一本综合指南。本书的作者是该领域的专家。他们将Lucene的力量释放了出来。本书是目前有关Lucene最好的参考手册。

——JavaReference.com


首先我们要真诚感谢Doug Cutting。如果没有他的无私付出,这本书就不可能出版。如果没有那些关心Lucene项目的参与者支持,Lucene也不会有如此丰富的功能,可能还会有更多的缺陷,更不会在这么短的时间内发展得如此迅猛。感谢长期以来对该项目给予支持的所有人员。另外,我们还要感谢那些为本书第12、13、14章提供研究案例的贡献者,他们是:Michele Catasta、Renaud Delbru、Mikkel Kamstrup Erlandsen、Toke Eskildsen、Robert Fuller、Grant Glouser、Ken Krugler、Jake Mannix、Nickolai Toupikov、Giovanni Tummarello、Mads Villadsen和John Wang。我们还要感谢Doug Cutting为本书的第2版撰写前言。

感谢Manning出版社员工对本书所做的贡献,他们是:Marjan Bace、Jeff Bleiel、Sebstian Stirling、Karen Tegtmeyer、Liz Welch、Elizabeth Martin、Dottie Marsico、Mary Piergies和Marija Tudor。感谢Manning出版社的多位专家对本书提出的很多改进意见,由此你才能读到这样一本优秀的著作,他们是Chad Davis、Dave Pawson、Rob Allen、Rick Wagner、Michele Galli、Robi Sen、Stuart Caborn、Jeremy Flowers、Robert Hanson、Rodney Woodruff、Anton Mazkovoi、Ramarao Kanneganti、Matt Payne、Curtis Miller、Nathan Levesque、Cos DiFazio和Andy Dingley。另外,还要特别感谢Shai Erera为本书所做的技术编辑工作。同时感谢在Manning论坛MEAP上发布反馈的读者。

写一本书是不容易的,尤其是要写一本像Lucene这样带有大量技术内容的书籍更是极具挑战性。要写一本有关一个成功、活跃、快速发展的开源项目的书几乎是不可能的!要启动和完成本书的撰写,必须要有一定的条件才行。

如果没有Doug的早期开拓、技术实力以及将自己的想法慷慨进行开源,如果没有开源社区坚定地推进Lucene项目,如果没有IBM前期对我加入Lucene项目和本书的支持,如果没有Erik和Otis撰写本书的第1版,那么我将无法成为本书的组成部分。

我的4个小孩——Mia、Kyra、Joel、Kyle——他们在这个过程中想尽一切办法来鼓励我。他们无限的精力、自由的思考、提出一系列有见地的问题、令人惊异的幸福感、永不满足的好奇心、温顺的坚持、幽默感、激情、发脾气以及敏锐的头脑,使得我能够保持年轻心态并激励着我完成诸如撰写本书等任务。我应当一直保持孩子般的奋斗激情。

感谢我的妻子Jane,当Manning出版社为本书的事找到我时,她说服我接受这个任务,同时她在有效运营我们这个繁忙的家庭时还展示出了无与伦比的技巧。值得一提的是,她为我的工作、撰写付出了大量时间,并能分享我的疯狂爱好。可以看出,这种能力是非常少见的。

我的父母以及岳父岳母为我增添了解决问题的勇气,同时还让我坚持完成了本书的撰写。他们教会了我正直:如果你承诺做某件事情,那么就一定要做好它。我们一定要信守承诺并努力完成它。他们还以身作则,通过努力工作告诉我,一个人是可以做大事情的。更重要的是,他们教导我在一生中做自己热爱的事情,生命是如此短暂,已没时间做其他自己不喜欢的事情了。

首先向Mike McCandless表示衷心的感谢。他几乎独立完成了对本书从1.0版本到当前更为出色的3.0版本的校对工作。Mike以极大的热情参与到本书撰写过程中,并急切地处理手头上的各个任务。本书第1版的致谢同样适用于这里,因为它对本书的影响是永恒的。

我要以个人名义感谢Otis对本书所做的努力。虽然我们尚未谋面,Otis仍然是我非常乐于合作的伙伴。我们自始至终对本书的结构和内容的意见都保持着高度统一。感谢弗吉尼亚(Virginia)州夏洛茨维尔(Charlotterville)镇的Java Java咖啡店,它为我提供了有线和无线网络帮助;还要感谢Greenberry咖啡店为我们提供帮助,为了我们,他们要比Java Java咖啡店更晚关门,让我们免遭无法访问网络的困扰(补充一下:他们现在已经使用Wi-Fi无线网络了,情况比我们那时要好的多)。我周围的家人和朋友让我的生活极大丰富起来。David Smith是我一生的良师益友,他的才智给了我很多启发,他为我提出了很多关于Lucene的思考(其中很多内容直到现在我都没有完全领会,很抱歉没有把这些内容加入到本书的手稿中)。Jay Zimmerman和“No Fluff,Just Stuff”研讨会对我产生了深远的影响。NFJS的常务发言人——Dave Thomas、Stuart Halloway、James Duncan Davidson、Jason Hunter、Ted Newward、Ben Galbraith、Glenn Vanderburg、Venkat Subramaniam、Craig Walls和Bruce Tate,他们都非常友善且乐于助人。特别要提到的是Rick Hightower和Nick Lesiecki,他们在技术和交流方面为我提供了有力的支持。Mike Clark在我编写《Lucene实战》的过程中给予了我非常热情的鼓励,这让我无以言谢。在技术方面,Mike为我提供了JUnitPerf性能测试案例,但是最令我感动的却是他所表现出来的活力、雄心和友善。另外,还要特别感谢Darden Solutions公司,他们在我枯燥的编辑过程和旅程安排上始终尽可能配合我,从而使我能够轻松地投入工作。Darden的一位协作者——Dave Engler为我们提供了CellPhone Skeleton Swing应用程序,我已经在NFJS回忆和JavaOne上演示过这个程序,十分感谢Dave!Darden的另外两位协作者——Andrew Shannon和Nick Skriloff使我们对Verity项目有了深入了解,作为搜索解决方案,Verity是Lucene的一个有力竞争对手。感谢Amy Moore为本书提供了插图。我的好朋友Davie Murray针对他所创作的插图耐心接受了我们提出的修改意见。Daniel Steinberg是我个人的良师益友,他让我把有关Lucene的想法放到他的网站Java.net上进行宣传。我的另一位好友Simon Galbraith现在已经是搜索引擎方面的大师级人物了,我经常通过E-mail和他讨论一些搜索引擎方面的想法。

我不喜欢那些无聊的致谢,但是我对Margaret所给予我的支持和耐心却无言以表,我对她的亏欠实在是太多了。我的父母Sanja和Vito是我最初的启蒙老师,他们给了我一个不同却精彩的世界;是他们鼓励我完成了自己的第一本书,从而消除了我早先对于写书的恐惧感。当然,我得感谢Doug Cutting,他的有关将Lucene进行开源的决定对我的人生产生了重要影响,我还要感谢Michael McCandless,他对《Lucene实战》第2版和Lucene本身的开发都付出了惊人的努力。我想Mike甚至有些时候在他的地下室进行7天24小时的工作。难怪到现在我还没见着他!


本节内容将对JUnit进行简要的介绍,但要知道我们的介绍还很不完整。我们将提供理解代码示例所需的最基本的JUnit内容。首先,我们的JUnit测试用例继承自junit.framework.TestCase基类。而具体的测试类遵循一个命名规范:为类名加上后缀Test。比如我们的QueryParser测试类就会被取名为QueryParserTest.java

JUnit自动执行所有声明为public void testXXX()形式的方法,其中XXX是一个比较随意但有意义的名字。JUnit测试方法应当简洁明了,一定要养成良好的软件设计习惯(比如不要总是重复工作,而要提高方法的可用性等)。

JUnit是围绕着一系列assert声明进行构建的,这就可以让你专注于编写测试程序,而让JUnit框架负责处理测试失败情况并报告错误细节。其中最常用的assert方法声明是assertEquals方法;该方法声明中有很多可重载参数,而且这些参数的类型多种多样。一个简单的示例测试方法如下:

如果预期值(在这个例子中预期值为10)不等于实际值(在这个例子中实际值为调用obj.someMethod()返回的结果),assert方法就会抛出一个运行异常。除了assertEquals方法之外,还有其他几个可用的assert方法。我们还可以使用assertTrue(expression)assertFalse(expression)assertNull (expression)声明。它们分别用于测试表达式是否为真、是否为假或者是否为空。

Assert具有一些可重载方法,这些方法声明把String变量作为第一个参数。该String参数完全是为了反馈一些测试信息而设置的,当一个测试返回失败时,这个参数可以为测试人员提供更多信息。所以我们在使用这个String参数时会让它更具有描述性(有时会让它读起来更有趣)。

通过使用上述方式,在JUnit测试用例中对我们的假设和期望进行编码,可以让我们从构建大而复杂的系统中剥离出来,从而把精力集中到比较小的细节中去。我们会在何时的位置编写大量的测试用例,并且可以根据这些用例进行快速开发。我们可以完全没有后顾之忧地修改系统中某一部分代码,比如说可以对算法进行优化,这份信息来自于我们知道即使代码有所改变也不会破坏系统的其他部分,如果这样做确实影响到系统的某一部分,自动测试套件会在进入最终产品之前提示错误。这种敏捷性源自于通过重构来保证代码库的清洁。重构是一门改变代码内部结构的艺术(或者说是一门科学),是在不影响系统外部接口的前提下修改程序(或代码)的内部结构,以满足不断演进的需求。

让我们把本书中所提到的有关JUnit内容加入到代码环境中。其中一个测试用例(来自于第3章)展示如下:

当然了,稍后我们将向读者解释这个测试用例中所使用的Lucene API。TestUtil类来自于lia/common/TestUtil.java,它包括几个在本书中多次用到的方法。每次我们使用该方法时都会展示它的源代码。下面是getBookIndexDirectory方法的源代码:

该方法会返回一个路径信息,我们的示例数据索引就保存在文件系统该路径下。当我们在该测试中不使用该方法时,JUnit还提供了一个初始化方法,该方法会在调用所有测试方法之前被调用,方法名称为public void setup()

如果对assert intestTerm的测试失败,那么我们可以看到类似如下的异常信息:

该信息表示我们的测试数据与预期的不同。

本书中大部分测试用例都对Lucene本身进行了测试。而事实上这可行吗?难道不是应该设计测试用例来测试自己编写的代码,而不是测试程序库吗?针对用来学习API的测试驱动开发有一个很有意思的方法:测试驱动学习(Test Driven Learning)。这对于设计一个用于直接测试新API的测试程序来说至关重要,而设计这个测试程序可以在很大程度上帮助读者理解新的API是如何运行的,以及它有什么功能。这也正是我们在大部分代码示例中所做的最有意义的事情,所以本书的测试用例都是针对Lucene自身的。因此不要把这些学习性质的测试束之高阁,一定要随时使用这些测试方法,以保证在API升级时这些测试仍然能适用于新版本的API,并且能够在API不可避免地发生改变时重构这些测试方法。

出于测试目的,我们为几个测试用例用到了Mock Objects。为了确定业务逻辑是否正常运行,Mock Objects被用作实际业务逻辑的探针(Probe)。比如在第4章中有一个SynonymEngine接口(详见4.6小节)。这个接口的实际业务逻辑是作为一个分析器使用的。当我们想测试一个分析器本身时,SynonymEngine被用作何种类型并不重要,不过我们希望使用一个定义良好且行为可预见的类型。我们创建了一个MockSynonymEngine对象,可以利用它对分析器进行可靠且可预期的测试。Mock Objects有助于简化测试用例,利用它们,我们可以每次只对系统的一部分进行测试,而不至于纠结于系统各个部分之间的依赖关系。这种做法可以保证在出现一个测试失败时不影响其他部分。当我们进行设计变更时,Mock Objects所带来的优势就可以体现出来了,比如,我们可以通过使用接口而非直接具体的实现来区分关注点和设计。


Lucene刚开始是一个私有项目。在1997年年末,因为工作不稳定,我便产生了把自己的一些软件商业化的想法。当时,Java已经是一种热门的编程语言了,而我也正需要一个学习它的理由。鉴于我有编写搜索软件的经验,因此我想可以使用Java写一个搜索软件以维持生计。基于以上原因,我写了Lucene。

到了2000年,我开始意识到自己并不适合商业运作工作。我对许可证和合同谈判丝毫没有兴趣,而且也不想成立自己的公司招聘员工。我真正的兴趣在于编写软件,而不是出售它。所以后来我把Lucene放到了SourceForge上,看看用开源的方式会不会让我一直保持程序创作激情。

Lucene在SourceForge上公开后,马上就有一些人开始试用。到了一年后的2001年,Apache表示希望接收Lucene项目。从那时起,Lucene的邮件列表上的消息数目与日俱增;同时开始涌现出一批志愿者加入到Lucene项目中。大部分的参与者都只是对Lucene进行一些外围开发,而我仍然是唯一的核心开发者。尽管如此,Lucene已逐渐成为一个真正的合作项目。

到了2010年,由于已经有很多对Lucene核心有着深入了解的开发者参与了项目,我基本上已经不再参与Lucene的日常开发和维护了,而Lucene程序实质性的增加和改进通常都是由这个强大的开发团队完成的。

通过近几年的发展,Lucene已经有了如C++、C#、Perl和Python等其他程序语言的版本。不管是最初的Java版本还是其他版本,Lucene广泛的应用范围已经远远超出了我的预期。它在多种不同的应用场景中提供了强大的搜索功能,例如财富百强企业的讨论组、商业性程序缺陷跟踪、微软提供的邮件搜索,以及覆盖面达到10亿网页数量的搜索引擎等。在业内,我被人作为“Lucene guy”到处介绍,而我多半也会听说有人在某个项目中使用了Lucene。但仍然要指出的是,我所了解的还只是所有Lucene应用中很小的一部分。

如果当初我把Lucene商业化了,它绝不会得到像现在这样广泛的应用。应用程序开发者可能更喜欢使用那些开源软件,因为这样一来,当他们遇到问题时,只需要自己分析源代码就可以找到错误原因,从而不必联系商业软件技术支持来分析了(随后还需要等待分析结果,并期望分析人员已经准确理解了该问题)。如果开发者自己分析源代码仍然不能解决问题,还可以求助于邮件列表中的同行以获取帮助,这通常比商业软件的技术支持要方便快捷很多。像Lucene这样开源形式能够使开发者的工作效率提高很多。

通过开放源代码,Lucene取得的巨大成功简直让人难以想象。虽然当初创立Lucene项目的人是我,但现在Lucene的蓬勃发展却是开发团队共同努力的结果。

那么Lucene的未来会怎样呢?这个我没法预测。根据我所了解到的情况,Lucene在经过10年的发展后,目前仍然势头强劲,并且它的用户群体和开发社区也比以前更大、更忙了。这部分归功于本书第1版的出版,它使得更多人能更容易上手Lucene。随着各个版本的不断发布,Lucene目前已经变得更好、更成熟、功能更多,其运行也更快了。

从2004年本书第1版出版到目前为止,Lucene内核和API已经经历了很大变化,从而需要对该书内容进行较大更新。在本书的第2版中,作者将为你介绍Lucene最近的发展以及最新的API。

从拥有《Lucene实战》第2版开始,你已经成为Lucene团队的一员了,Lucene的未来就属于你了,祝一路顺风!

Doug Cutting

Lucene、Nutch以及Hadoop的创始人


在Internet刚刚兴起的时候,我就对搜索和索引方面的技术产生了兴趣。还记得大约在1991年,一个邮件列表管理的项目给我留下了一段美好的回忆。当时我使用了majordomo和MUSH(Mail User’s Shell),还利用到一些Perl、awk及shell脚本语言来进行项目开发。那时我实现了一个公共网关接口(CGI),还允许用户利用grep对列表文件和其他用户文件进行搜索。后来就出现了Yahoo!、AltaVista和Excite等提供搜索功能的网站,而这些都是我经常访问的网站。

自从我的第一个孩子Jakob出生后,我的数码照片就突然变得多了起来。因此我打算开发一个能够管理这些照片的系统,利用它应该可以很方便地为每张照片添加一些元数据并进行说明,比如照片的关键字和拍摄日期;当然应该也可以根据任一方面的信息来查找图片。在20世纪90年代末,我利用微软公司的一些技术建立了一个基于文件系统访问(filesystem-based approach)原型,这些技术包括了Microsoft Index Server、Active Server Pages和一个用于图像处理的第三方COM组件。我的时间都浪费在反复使用这些技术上,那时我抽出几天空闲时间就能用这些东西拼凑出一个应用程序。

此后我的职业生涯开始转向Java技术,而在我的计算机生活中微软Windows操作系统技术所占的分量就越来越少了。在试图使用可以跨平台的Java技术重新实现个人照片库和搜索引擎的过程中,我偶然发现了Lucene。Lucene的简单易用完全超出了我的预料,因为我曾经使用过其他很多开源的软件库和工具,它们在概念上比Lucene简单很多,但使用起来却很复杂。

在2001年,我和Steve Loughran开始撰写《使用Ant进行Java开发》(Java Development with Ant)(由Manning出版社出版)一书。我们决定写一款图像搜索软件,并且把它扩展成一个文档搜索引擎。这个程序在关于Ant的书中贯穿始终,并且它也可以通过简单的配置和定义之后,作为图像搜索引擎来使用。它和Ant的联系不仅来源于简单“编译打包”的编译过程,而且还来源于一个自定义的Ant任务<index>,我们在编译过程中使用了Lucene来创建这些索引文件。这个Ant任务被放进了Lucene的Sandbox工具包,具体内容将在8.4小节中阐述。

该Ant任务还已经被应用于我建立的博客(Blog)系统中,我把这个博客系统称为BlogScene(http://www.blogscene.org/erik)。在创建了一个新的博客记录之后,系统会运行一个Ant编译连接进程,它索引了新加入的记录并将该记录上传到我的服务器上。我的Blog服务器由一个Servlet、一些Velocity模板和一个Lucene索引构成。在这个Blog中,你可以进行各种查询,甚至是复合查询。和其他的Blog系统相比,BlogScene在功能设计技巧上可能显得有点逊色,但其强大的全文搜索能力是一大亮点。

目前我正效力于弗吉尼亚大学的Patacriticism应用研究小组(http://www.patacriticism.org)。在那里我提出了我自己的文本分析、检索和研究各种针对测试的专家意见,并且通过讨论量子物理学和文学的相关性来拓展我的思路。因为我相信“诗人是世界上没有得到承认的工程师”。

我对信息检索和处理方面的兴趣和热情始于在Middlebury大学的学生时代。正是那时,我认识了这个包含着浩如烟海的被称为Web的事物。虽然那时Web才刚刚起步,不过人们对信息收集、分析、索引和搜索的长期需求已经初现端倪了。我开始沉迷于把从网络中抓取的信息建立成信息库,并开始编写网络爬虫程序,同时也开始考虑如何对收集到的数据进行搜索。我将搜索看成一个未知领域的杀手应用。有了这种想法作为支撑,我开始着手创建第一个自己的项目:收集和搜索信息,它也是一系列后续项目的共同基础。

1995年,我和同学Marshall Levin共同创建了WebPh,这是一个用来收集和检索个人联系信息的开源项目。从本质上说,它是一个带有公共网关接口(CGI)的简单的数字电话簿,这是当时同类型产品中最先出现的产品之一(事实上,在20世纪90年代末它还作为先进技术的例子被一个法庭案例所引用)。世界各地的大学和政府部门成为它的主要使用者,直到现在还有很多学校和部门仍然在使用它。在1997年,由于有了开发WebPh的经验,我开始着手开发Populus。那时Populus是一个很流行的、用于保护用户信息的数据库。虽然在技术方面(类似于WebPh)并未取得实质性突破,但Populus仍然对该领域产生了重要影响,它还被广泛应用于诸如WhoWhere、Bigfoot和Infospace等大型网站中。

在开发了两个用于处理个人联系方式信息的软件后,我想转到新的领域继续开发。于是我开始了新的尝试——Infojump,这是一个能够从在线新闻、报纸、期刊杂志中挖掘出高质量信息的软件。它除了包含我自己的软件(由大量的Perl模块和脚本程序组成)外,还利用了一个叫Webinator的网络爬虫软件和一个叫Texis的全文检索软件。Infojump在1998年提供的服务和现在的FindArticles.com有异曲同工之处。

虽然WebPh、Populus和Infojump基本上符合各自的设计初衷,而且也实现了全部的设计功能,不过它们仍然存在一些技术上的局限。它们所缺少的是一个高性能的信息检索库,这个信息检索库要能提供在倒排索引的支持下进行全文检索的功能。为了解决这个问题,我开始寻找一种新的解决方法而并不是重复原来那些毫无意义的工作。2000年年初,我终于认识到Lucene正是我所寻找的可以弥补这些缺陷的软件,于是很快便对它产生了浓厚的兴趣。

当Lucene还是SourceForge上的一个开源项目时,我就已经加入了该项目组。后来在2002年,Lucene转移到了Apache软件基金组织中。我之所以这么热爱Lucene项目,是因为它正是我近几年脑海中始终萦绕的核心想法。这些想法之一就是Simpy,它是我新近开发的一个宠物项目(Pet Project),是一个颇具特色的个人网络服务,用户可以用它标记、索引、搜索和共享从网络中搜到的信息。在构建这个系统时,我大量使用了Lucene的索引功能,而且还使用了Doug Cutting的另一个软件——Nutch(详见第1版的第10章)。我对Lucene项目的积极参与给我带来了一个意外的惊喜,那就是和Erik Hatcher合著了《Lucene实战》(第1版)一书。

《Lucene实战》对Lucene进行了最细致入微的描述。书中涵盖了有关创建基于Lucene的复杂应用程序的所有信息。如同Lucene社区的工作一样,这本书是几位作者通力合作的结晶。当人们拥有共同的兴趣、灵活的意愿以及对人类知识做出贡献的理想时,即使他们面对很多的困难和阻碍也可以取得巨大的成功,Lucene和《Lucene实战》就是一个很好的例证。


当我首次接触Lucene时,已经是《Lucene实战》第1版出版一年后了,当时我已有一些搭建搜索引擎方面的经验,但并不知道Lucene的细节信息。因此,我找到一本由Erik和Otis撰写的《Lucene实战》,开始从头到尾进行阅读,最后我简直被它吸引住了!

当使用Lucene后,我发现它在很多地方都有改进,因此我开始贡献一些小的补丁、更新Java文档,在Lucene邮件列表中讨论一些相关话题,等等。最后我终于成为一名活跃的Lucene核心提交人员和PMC成员,这些年已提交了许多修改。

现在距离《Lucene实战》第1版的出版已经5年半了,这对于开源世界来说已经是太长的时间了。Lucene在此间已发布过两个主版本,目前它已具有各种新功能,如数值域、可重用分析API、有效载荷、近实时搜索、用于索引和搜索的互通API等。

当Manning首次找到我时,很明显该书第2版已到了急需出版的时候了。此外,我作为Lucene开源社区的核心提交人员之一,主要负责提交这些变更内容,我有义务为本书第2版的撰写出力。所以我答应了Manning,并疯狂地投入《Lucene实战》第2版的撰写工作中,我对最后的结果也是非常满意的。我希望《Lucene实战》第2版能满足读者的需要,有助于大家建立自己的搜索程序,并且我期待着能在用户和开发人员列表中看到你们,以及你们提出的富有价值的问题,并继续推动Lucene的快速成长!

Michael McCandless


本书前半部分涵盖了Lucene的Java程序包相关内容。第1章“初识Lucene”是对Lucene的总体概述,你可据此开发一个完整的索引和搜索程序。每个后续章节都深入阐述了一个具体部分。第2章“构建索引”和第3章“为应用程序添加搜索功能”均为使用Lucene的第一步。第4章“Lucene的分析过程”将深入到索引过程,帮助我们理解Lucene是如何对文本进行索引的。

通过对前4章的学习,你会较好地理解Lucene的一些基本功能。但Lucene真正的亮点在于搜索,所以我们将在本书前半部分结束前用两章的篇幅来介绍它,分别是:第5章“高级搜索技术”,这些高级搜索技术都基于Lucene的内置特性;第6章“扩展搜索”,展示了Lucene针对定制目的而提供的可扩展性。


本章要点

Lucene其实是一类强大的Java搜索库,它能让你很轻易地将搜索功能加入到任何程序中。近年来Lucene变得非常流行,同时它也是使用最为广泛的信息搜索库:它能够增强很多Web站点和桌面应用程序的搜索能力。尽管当初它是用Java编写的,由于使用太广泛,以及热心开发人员的努力,目前你已经可以自由获取大量的针对其他编程语言的Lucene移植版本(其中包含C/C++、C#、Ruby、Perl、Python以及PHP版本)。

简单易用是Lucene广受欢迎的关键因素之一,但是不要被这点所迷惑:后台复杂、设计先进的信息检索技术其实一直在不为人知地运行着。Lucene是一款设计非常优秀的软件,它向用户提供了简单易用的索引和搜索API,并屏蔽了复杂的内部实现过程。当开始使用Lucene时,你不必深入了解它的信息索引及检索的工作原理。同时由于Lucene API简单直接,你只需要学会如何使用它提供的类就可以了。再者,对于早已厌倦了臃肿软件的你而言,会惊奇地发现Lucene的核心JAR包是如此短小精悍——仅有1MB大小——并且不需要其他任何依赖的JAR包!

在本章中,我们将分析一款典型搜索程序的总体架构,以及Lucene在其中的使用场合。需要指出的是,Lucene仅仅是一个提供搜索功能的类库,所以你还需要根据实际情况自行完成搜索程序的其他模块(例如网页抓取、文档处理、服务器运行、用户界面和管理等)。在具体分析过程中,我们将首先通过一些现成的代码示例,为你展示如何使用Lucene进行基本的索引和搜索实现,然后简要地介绍在索引和搜索过程中需要了解的全部核心知识点。我们处在一个信息爆炸的时代,需要解决的首要问题就是具备强大的搜索能力。

NOTE

 

Lucene是一个快速发展的开源项目。当你读到此处时,很可能Lucene的很多API和特性都已经发生改变。本书内容基于该项目的3.0.1版本,由于Lucene版本是向后兼容的,本书所有代码示例都可以在后续3.x版本中编译和运行。如果在这过程中你遇到任何问题,请发送邮件至java-user@lucene.apache.org。Lucene社区规模巨大,他们热情并且反馈迅速,一定能够为你提供帮助。

为了认识我们这个复杂的世界,人们发明了各种各样的方案来对信息进行分类和组织。在图书馆使用的杜威十进制分类法(Dewey Decimal System)就是层次分类方法的一个经典案例。

随着互联网的普及以及数字信息的爆炸式增长,人们已经可以足不出户地接触到海量信息。随着数据量的日益剧增,我们迫切需要采用全新的、更为动态化的方法来查找所需要的信息(如图1.1所示)。尽管我们可以对数据进行分门别类,但从成千上万的类别或者子类别中查找信息已经不再是一种行之有效的方法了。

如今人们需要在浩如烟海的数据中快速查找所需信息,这不仅仅体现在互联网领域中——因为台式计算机的数据存储量也随着硬盘存储能力的提高而激增。通过改变目录,展开或收起文件夹层次结构,已经不再是一种访问存储文档的有效方法。此外,人们不仅要使用计算机的原始计算功能,而且要用它进行通信交流、多媒体播放和存储等。这类应用需要计算机能够快速查找某个特定的数据片段;同时,我们还需要能够方便地查到诸如图片、视频和音频文件等各式各样的多媒体文件(Rich Media)。

我们一边要面对如此大量的数据信息,一边又在吝惜自己宝贵的时间资源,为了解决这个矛盾,我们必须找到一种灵活、自由和及时的数据查询方法,以便能够在花费最小精力代价的情况下,用这种方法快速穿越各种严格的分类界限,准确找到我们需要的信息。

为了说明在互联网和台式计算机中使用广泛的搜索应用,如图1.1所示是在Google中搜索Lucene的情形。图1.2展示了Apple Mac OS X Finder(类似于Microsoft Windows资源管理器)以及它右上方显示的内嵌搜索功能。Mac OS X音乐播放器iTunes,同样具有内嵌搜索功能,如图1.3所示。

因此,搜索功能可以说是无处不在!所有主流操作系统都内嵌了搜索功能。对于Mac OS X系统来说,它的突出特性在于,它集成的索引和搜索功能涵盖了所有文件类型,包括具有大量元数据的文件类型,比如电子邮件、通讯录等[1]

图1.1 在互联网上利用Google进行搜索

图1.2 Mac OS X Finder及其内嵌的搜索器

图1.3 Apple’s iTunes嵌入直观的搜索功能

不同的人都在为解决同一个问题而奋斗着——信息量太过庞大——从而采取不同的方法来处理它。一些人致力于不断推出新的用户界面,一些人则是推出智能助理,还有一些人致力于开发复杂的搜索工具或者类似于Lucene的搜索库来解决这个问题。在进入示例代码之前,我们先从总体上介绍一下Lucene是什么,Lucene能够做什么,以及它的发展历程。

Lucene是一款高性能的、可扩展的信息检索(IR)工具库。信息检索是指文档搜索、文档内信息搜索或者文档相关的元数据搜索等操作。Lucene能够融入到你的应用程序中,以增加搜索功能。它是一款以JAVA实现的成熟、自由、开源的软件项目,也是Apache软件基金(Apache Software Foundation)中的一个项目,并且基于Apache软件许可协议授权。因此,Lucene在近年来已经成为最受欢迎的开源信息检索工具库。

NOTE

 

本书中,我们将一直使用信息检索(或它的英文缩写IR)这个术语来描述Lucene这一类型的搜索工具。人们通常认为信息检索库就是搜索引擎,其实两者是有区别的,我们不能混淆信息检索库与Web搜索引擎这两个概念。

使用Lucene后你会很快发现,它为你提供了一套简单而强大的核心API,并且在使用它们时你不必深入理解全文索引和搜索机制。你只需要掌握Lucene中少数几个类就可以将它集成到你的应用程序中了。由于Lucene只是一个Java类库,对于不同的索引和搜索内容它是通用的,相对于其他大量的搜索程序来说,这是个很大的优势。Lucene的设计紧凑而简单,能够很容易地嵌入桌面应用程序当中。

在Lucene的核心JAR包之外,有大量的扩展模块,它们提供一些附加功能。其中一些功能对于几乎所有搜索程序来说都是至关重要的,譬如spellchecker和highlighter模块。这些模块位于一个我们称之为contrib的单独区域,本书会多次提及这类contrib模块。这些模块的数量太大,所以我们将在第8章和第9章分两章来详细讲解它们!

Lucene的站点链接为http://lucene.apache.org/java,在这里你可以更详细地了解Lucene的最新状况。主要有:新手教程、Lucene最近所有版本API的Java帮助文档、问题跟踪系统、版本下载链接以及Lucene的维基链接http://wiki.apache.org/lucene-java,该维基链接包含了大量的由Lucene社区更新和维护的网页。

很可能你在使用Lucene的时候自己却并不知道!因为Lucene所提供的搜索功能目前正以令人难以置信的速度大量应用于各种场合:网飞公司(NetFlix)、掘客(Digg)、MySpace社交网站、LinkedIn专业网络社区、联邦快递(Fedex)、苹果公司(Apple)、特码捷票务公司(Ticketmaster)、SalesForce.com网站、大不列颠百科全书光盘、日蚀集成开发环境(Eclipse IDE)、梅奥医学中心(the Mayo Clinic)、New Scientist杂志社、Atlassian软件公司(JIRA)、Epiphany浏览器、麻省理工学院在线课件(OpenCourseWare)和数字空间系统(DSpace)、Hathi Trust数字图书馆、Akamai公司的前端运算(Edge Computing)平台等。或许你的名字也即将出现在这个列表中!Lucene维基页面的技术支持分页有更多的用户列表。

人们初次接触到Lucene时,很容易将它和一些即用型程序搞混淆,比如文件搜索程序、网页搜索器以及网站搜索引擎等。其实这并不是Lucene的真面目:Lucene只是一个软件类库,或者一个工具箱,而并不是一个完整的搜索程序。Lucene专注于文本索引和搜索功能,并且运行效果非常不错。Lucene能够让你的应用程序在不用了解复杂的索引和搜索实现的情况下,通过调用它的一个简单易用的API,就能够按照固定规则来进行事务处理。这时你的整个程序将围绕Lucene这个核心来运行。

很多完整的搜索程序其实都是建立在Lucene核心之上的。如果你正在寻找一些成型的网页搜索程序、文档处理程序以及搜索引擎,可以选择Lucene维基页面在其技术支持分页所列出的一些现成的应用程序。

Lucene允许你向自己的应用程序中添加搜索功能。Lucene能够把你从文本中解析出来的数据进行索引和搜索。Lucene并不关心数据来源、格式,甚至不关心数据的语种,只要能把它转换为文本格式即可。也就是说你可以索引和搜索存储在文件中的如下数据:远程Web服务器上的网页、本地文件系统中的文档、简单的文本文件、Word文档、XML文档、HTML文档或者PDF文档,或者其他能够从中提取文本信息的数据格式。

同样,你也可以利用Lucene来索引存储在数据库中的数据,以给你的用户提供一些其他数据库所不具备的诸如全文搜索等功能。一旦你的应用程序集成了Lucene,用户就可以进行诸如+George +Rice –eat-pudding、Apple-pie+Tiger、animal:monkey AND food:banana等有着复杂查询条件的搜索。有了Lucene,你可以为电子邮件信息、归档邮件列表、即时聊天信息以及维基(Wiki)页面等信息进行索引和搜索。下面让我们来回顾一下Lucene的历史。

Lucene最初是由Doug Cutting编写的[2]。当时其工具包可以从SourceForge的Lucene主页下载。在2001年9月,Lucene加入Apache软件基金会的Jakarta家族并开始提供高质量Java开源软件产品;2005年该项目一跃成为顶级的Apache项目。目前,Lucene包含大量的子项目,具体可以通过http://lucene.apache.org了解。本书主要包含Lucene项目的Java子项目,详见http://lucene.apache.org/java,但大多数人一般将这个Java子项目称为Lucene项目。

自那以后,每个Lucene版本的发布都使得这个项目备受关注,吸引着更多的用户和开发人员的眼球。截止2010年,最新的Lucene版本号是3.0.1。表1.1列出了Lucene的版本发布历史。

表1.1  Lucene的版本发布历史

版  本

发 布 日 期

里 程 碑

0.01

2000年3月

在SourceForge网站第一次发布开源版本

1.0

2000年10月

1.01b

2001年7月

在SourceForge网站最后一次发布

1.2

2002年6月

在Apache Jakarta第一次发布

1.3

2003年12月

加入复合索引文件格式,增强了QueryParser、远程搜索、Token定位、扩展站的评分API等

1.4

2004年7月

增加排序、跨度查询、项向量等功能

1.4.1

2004年8月

排序性能缺陷的修正

1.4.2

2004年10月

优化了IndexSearcher并修正一些程序缺陷

1.4.3

2004年11月

修正各种程序缺陷

1.9.0

2006年2月

增加二进制存储域、DateTools、NumberTools、RangeFilterRegexQuery等;要求Java1.4版本

1.9.1

2006年3月

BufferedIndexOutput缺陷修正

2.0

2006年5月

删除过时的方法

2.1

2007年2月

新增在IndexWriter中删除/更新文档、简化锁机制、优化QueryParser、benchmark contrib模块

2.2

2007年6月

性能改进、程序查询、有效载荷、预分析域、自定义删除策略

2.3.0

2008年1月

性能改进、自定义段合并策略和段合并计划任务、默认后台进行段合并、索引损坏检测工具、新增IndexReader.reopen方法

2.3.1

2008年2月

修正2.3.0中的程序缺陷

2.3.2

2008年5月

修正2.3.0中的程序缺陷

2.4.0

2008年10月

性能提升、事务语义(回滚、提交)、新增expungeDeletes方法、在IndexWriter中根据查询删除

2.4.1

2009年3月

修正2.4.0中的程序缺陷

2.9

2009年9月

新增段Collector API、提升搜索性能、近实时搜索、基于属性的分析

2.9.1

2009年11月

修正2.9中的程序缺陷

2.9.2

2010年2月

修正2.9.1中的程序缺陷

3.0.0

2009年11月

删除过时方法、修正部分程序缺陷

3.0.1

2010年2月

修正3.0.0中的程序缺陷

NOTE

 

Lucene的创始人Doug Cutting在信息检索(IR)领域有着极其丰富的理论和实践经验。他曾经发表过大量的信息检索方向的主题论文,也曾经任职于Excite、Apple、Grand Central以及Yahoo!等多家公司。在2004年,由于担心日益减少的网络搜索引擎可能对该行业带来新的商业垄断,他又创建了Nutch,即第一个开源的Web搜索引擎(http://lucene.apache.org/nutch);这个引擎主要被设计用来进行抓取、索引和搜索数十亿计的频繁更新的Web页面。毋庸置疑,Lucene是Nutch的核心。Doug同时致力于Hadoop项目(http://hadoop.apache.org),该项目作为Nutch项目的拓展内容,通过映射/化简(map/reduce)架构向用户提供分布式存储和计算工具。

Doug Cutting依然是Lucene背后的中流砥柱,同时又有更多的开发者不断加入到这个项目中。截止本书出稿之时,Lucene的核心团队已经拥有了大概6位主要开发人员,其中3位同时也是本书作者。除了这些项目开发者之外,Lucene用户技术社区还活跃着大量人员,他们一直在为Lucene提供补丁、Bug修正以及新特性。

一种衡量开源软件是否成功的方法就是看它被移植到其他编程语言的种类数。如果采用这个衡量标准,Lucene无疑是成功的!尽管Lucene是用纯Java编写的,但是在其他很多编程环境中也有它的移植版本,包括Perl、Python、Ruby、C/C++、PHP和C#(.NET)。这对于那些需要用不同编程语言来操作Lucene索引的开发者来说确实是一个好消息。移植相关内容我们将在第10章中详细介绍。

为了更好地理解Lucene是如何内嵌至搜索程序中的,以及Lucene能够做些什么,我们将在下一节以较大篇幅对当下“典型”的搜索程序架构进行回顾。

对于搜索程序来说,重要的是理解它的总体架构,这样就能清晰地理解程序中由Lucene完成的内容,以及其他需要你自行完成的内容。人们通常将Lucene误解为一个完整的搜索程序,而实际上它只是搜索程序的核心索引和搜索模块而已。

稍后我们将看到,搜索程序首先需要实现的功能是索引链,这需要按照几个独立的步骤依次来完成:检索原始内容;根据原始内容来创建对应的文档,比如从二进制文件中提取文本信息;对创建的文档进行索引。一旦建立起索引,用于搜索的组件也就出来了,这些搜索组件包括:用户接口、构建可编程查询语句的方法、执行查询语句(或者检索匹配文档)、展现查询结果等。

当前流行的搜索程序一般都具有一些不错的功能。有些搜索程序是在后台运行,就像一个小组件嵌入现有软件中,并搜索一些特定的内容集(如本机文件、邮件信息、日历条目等)。有些搜索程序运行在远程Web站点的专用服务器上,支持多个用户通过浏览器或移动设备与之交互,并且能够搜索诸如产品列表、已知的和确定范围的文档集等内容。另外,还有些搜索程序能够对大部分互联网内容进行索引,并且能够以令人难以想象的能力处理搜索内容和响应并发搜索请求。尽管种类繁多,这些搜索程序的搜索引擎通常都具有同样的总体架构,如图1.4所示。

图1.4 搜索程序的典型组件,其中阴影部分可由Lucene完成

当你在设计自己的搜索程序时,需要明确界定哪些功能是必须的,以及它们应该如何运行。事先提示一下:当前流行的Web搜索引擎(比如Google搜索引擎)所设定的基线要求(Baseline Requirements)几乎就是所有用户在跟它交互的第一时间内想要获取的。如果你的搜索引擎不能满足这个基线要求,那么你的用户会在一开始就对它失去信心。Google搜索引擎的语法纠正能力好得令人吃惊,它在每条搜索结果上以高亮形式动态标注的摘要也非常准确,另外它的响应时间也远小于一秒钟。如果你对以上内容存有疑问,可以亲自用用Google,从中获取一些灵感,以便能够在设计自己的搜索程序时,对于一些必须实现的基本功能提供指引。模仿是最真诚的赞美!

下面让我们来逐一分析搜索程序的各个组件。你可以在阅读的同时,思考一下图中哪些组件对于你自己的搜索程序来说是必须的,这样便于你理解如何在自己的搜索程序中使用Lucene并完成预定的搜索功能。同时,我们将会明确指出哪些组件是Lucene可以实现的(如图1.4所示的阴影部分),以及哪些模块是必须由你自己的搜索程序或其他开源软件实现的。然后,我们将针对Lucene在你的搜索程序中所扮演的角色进行小结。

从图1.4底部开始,展现了所有搜索引擎的首要部分,叫做索引操作(indexing)。这部分负责将原始数据引入可被高效查找的对照表中,以便能够对这些内容进行快速搜索。

我们假定你需要搜索大量的文件,然后找出其中包含某个词或短语的文件,那么你将怎样编写程序来做到这点呢?一个初级的方法就是顺序扫描每个文件,查找其中是否包含这个词或者短语。尽管这个办法能够达到目的,它也会带来大量问题,最明显的问题是:它不能对太大的文件集或者太大的文件进行处理。对于这样的问题,就需要引入索引了:为了快速搜索大量的文本,你必须首先建立针对文本索引,将文本内容转换成能够进行快速搜索的格式,从而消除慢速顺序扫描处理所带来的影响。这个过程就叫做索引操作(indexing),它的输出就叫做索引(index)。

你可以将索引想象成一种数据结构,它允许对存储在其中的单词进行快速随机访问。这个概念类似于书后的索引,后者能让你快速定位书中某个主题的页码。就Lucene来说,索引是一个精心设计的数据结构,通常作为一组索引文件存储在文件系统中。附录B中对索引文件结构有详细介绍,在此我们只需将Lucene索引简单理解为一种用来快速查找单词的工具。

进一步研究后你会发现,整个索引过程包含了一组逻辑上互不关联的步骤,我们接下来将详细分析。首先,你需要获取搜索的内容。

获取内容

如图1.4底部所示,第一步就是获取内容。这步操作包括使用网络爬虫或蜘蛛程序来搜集和界定需要索引的内容。这一步操作有时候并不重要,比如你在索引一组存在于文件系统特定目录下的XML文档时,又比如所有需要获取的内容都存在一个组织有序的数据库中时,这些内容是很容易获取的。但其他情况下,这步操作却可能变得异常复杂和繁琐,如果这些内容分散在各种地方的话(如文件系统、内容管理系统、Microsoft Exchange系统、Lotus Domino系统、各种Web站点、数据库、本地XML文档集合、运行在局域网服务器中的CGI脚本,等等)。

对于使用权限管理的系统来说(权限管理是指系统只允许用户访问其对应权限的文档),获取内容这一步将变得更复杂,因为这时需要用“超级用户”权限来获取内容。此外,在获取文档内容的时候必须同时获取访问权限或者访问控制列表(ACLs),并且后者必须以新增域的形式加入这些文档中,以便在搜索文档的过程中遵从该权限控制要求。我们将在5.6.7小节中介绍有关搜索过程中使用的安全措施。

内容获取模块在访问规模较大的内容集时,重要的是能够以增量方式运行,这样每次运行时就可以只访问针对上次运行后内容有改变的文档。或者说,内容获取模块还须是“活动的”,意思是说它是持续运行的后台服务,能实时获取新文档信息或者内容改变文档的信息,并在能够访问时获取这些文档的内容。

Lucene作为一款核心搜索库,并不提供任何功能来实现内容获取。内容获取的实现完全依赖于你自己的应用程序或者某一款其他软件。目前有大量的开源爬虫软件可以实现这个功能,下面列出其中一部分:

如果你的应用程序结构比较松散的话,那最好是使用已有的爬虫工具。这类工具采用专门设计,能够轻松获取存储在各种系统中的目标内容,有时这些工具还能提供一些针对诸如Web站点、数据库、通用内容管理系统以及文件系统等内容存储系统的预连接。如果你的应用程序没有为自己的爬虫工具提供这些已有的连接,那么也不困难,自己建立一下即可。

下一步将介绍如何根据获取的内容来建立小数据块,也称为文档(documents)。

建立文档

获取原始内容后,就需要对这些内容进行索引,你必须首先将这些内容转换成部件(通常称之为文档),以供搜索引擎使用。文档主要包括几个带值的域,比如标题(title)、正文(body)、摘要(abstract)、作者(author)和链接(url)。你还必须仔细设计好如何将原始内容分割成合适的文档和域,以及如何计算其中每个域的值。通常的做法是这样的:一个电子邮件信息作为一个文档,一个PDF文档或一个网页作为一个文档。但是这个做法有时候也存在问题:该如何处理电子邮件的附件?是将附件中提取的所有文本合并后统一写入某个文档,还是为各个附件分别创建文档并以某种方式将这些文档和附件关联起来?

设计完方案后,你就需要将原始内容中的文本提取出来写入各个文档了。如果原始内容本来就是文本格式的,并且是使用现成的文本编码格式的话,做法就简单了。但目前的文件格式大多是二进制格式的(PDF文件、Microsoft Office文件、Open Office文件、Adobe Flash文件、视频流和音频多媒体文件)或者包含一些在索引操作前必须去除的固定标记(RDF、XML、HTML)。这样,你需要使用文档过滤器从这些原始内容中提取文本格式信息,便于后期建立搜索引擎文档(document)。

在该步骤中,由于复杂的商业逻辑,我们可能需要创建额外的域。例如,如果你碰到的“正文文本”域比较大,你可以运行语义分析器从中提取出诸如名称、地点、日期、时间、位置等信息,再将它们分别作为单独的域写入文档。或者你也可以连接某个分离的数据存储区域(例如数据库)原始内容,并将这些内容合并成一个单独的文档提供给搜索引擎。

对于建立文档的操作来说,还有一部分常见的内容就是向单个的文档和域中插入加权值,如果这些文档和域比较重要的话。也许你希望释放因全面查阅所有文档而带来的压力,因为并不是所有文档都是同等重要的;也许新近修改过的文档会比此前的文档更为重要。加权操作可能在进行索引操作前就静态(针对每一文档或域)完成了,我们将在2.5小节进行详细讲解;也有可能要在搜索期间才动态完成,我们将在5.7小节中讲解。包括Lucene在内的几乎所有的搜索引擎都会自动地静态地对内容较短的域进行加权。这在直觉上是能说通的:如果要在一个很长的文档中匹配一两个单词,那么匹配结果一定不如在三四个单词长的文档中匹配同样的单词切题。

Lucene提供了一个API来建立域和文档,但不提供任何建立它们的程序逻辑,因这些逻辑完全由调用API的应用程序根据具体情况完成。Lucene也不提供任何文档过滤器,但它在Apache还有一个姊妹项目叫做Tika,后者能很好的实现文档过滤(见第7章)。如果你要获取的原始内容存储于数据库中,也有一些项目,通过无缝连接内容获取步骤和文档建立步骤就能轻易地对数据表进行索引操作和搜索操作,这些项目有:DBSight、Hibernate Search、LuSQL、Compass和Oracle/Lucene集成项目。

至此,文档中文本格式的域还不能用于搜索引擎的索引操作。为了进行索引操作,首先需要对文本进行分析。

文档分析

搜索引擎不能直接对文本进行索引:确切地说,我们必须将文本分割成一系列被称之为语汇单元的独立的原子元素。这就是在文档分析这一步要做的工作。每一个语汇单元能大致与语言中的“单词”对应起来,而这个步骤即决定文档中的文本域如何分割成语汇单元系列。所有有趣的问题都集中于此:如何处理连接一体的各个单词呢?是否需要进行语法修正呢(如果原始内容本身存在错别字)?是否需要向原始语汇单元中插入同义词,以至搜索“laptop”的时候能够返回“notebook”相关主题?是否需要将单数和复数格式的单词合并成同一个语汇单元?这块比较常用的是词干提取器,如Martin Porter博士的Snowball词干提取器(参见8.2.1小节)被用来从单词中提取词根(例如,runs、running和run都映射到基本词形run)。这样的话,我们是保留这类差别还是忽略它呢?对于非拉丁语言来说,我们甚至都不能界定“单词”是什么?分档分析组件是如此重要,我们将在第4章用一整章的内容来讲解。

Lucene提供了大量内嵌的分析器能让你轻松控制这步操作。你也很容易搭建自己的分析器,或者联合Lucene的语汇单元化工具和语汇单元过滤器来创建自定义的分析链,来定制语汇单元的创建方式。下面,最后一步是对文档进行索引。

文档索引

在索引步骤中,文档将被加入到索引列表。Lucene为本步骤提供了所有必要的支持,并且通过一个异常简单的API就魔术似的完成了索引操作。第2章将详细讲解索引操作的各个基本步骤。

上文回顾了搜索程序要完成的几个典型的索引步骤。重要的是记住,为了提供好的搜索体验,索引是必须要处理好的一环:你在设计和定制索引程序时必须围绕如何提高用户的搜索体验来进行。下节我们将介绍搜索过程中各个步骤。

搜索处理过程就是从索引中查找单词,从而找到包含该单词的文档。搜索质量主要由查准率(Precision)和查全率(Recall)来衡量。查全率用来衡量搜索系统查找相关文档的能力;而查准率用来衡量搜索系统过滤非相关文档的能力。附录C显示了如何用Lucene的benchmark /contrib模块来衡量你的搜索程序的查准率和查全率。

你在处理搜索过程时还必须考虑到其他很多问题。我们已经提到过搜索速度和快速搜索大容量文本的能力。对于单项查询、多项查询、短语查询、通配符查询、结果ranking和排序德支持也是重要的,以及友好查询输入法。Lucene提供了大量搜索功能,以及炫目的附属功能,由于数量众多,我们将用3章的篇幅来详细讲解(第3、5和6章)。

下面我们介绍搜索引擎的典型组件,这次从图1.4的顶部往下讲解,首先讲解用户搜索界面。

用户搜索界面

用户搜索界面是用户在与搜索程序交互时,在浏览器、桌面程序或移动设备中的可视界面。UI(User Interface,用户界面)其实是搜索程序中最重要的部分!因为,你可以开发世上最好的搜索引擎后台,并拥有极其先进的功能,但是一个低级UI设计错误就能抵消掉这些优势,从而导致挑剔多变的用户逐渐转向你的竞争对手一方。

搜索界面得保持简洁:不要在首页呈现大量的高级选项。搜索框需要放置在界面上显著位置,并且要随处可见(用户可以随时向搜索框输入文本进行搜索),而不要通过先点击搜索链接然后显示搜索框让用户输入文本这两步来完成(这是常见的错误)。

不要低估搜索结果展现的重要性。举几个简单例子,如未能在标题或摘要中高亮显示匹配内容,或字体显示太小以及在搜索结果中加入过多文本,这些都能很快毁掉用户的搜索体验效果。我们要确认程序会对搜索结果进行明确的排序,并且在显示搜索结果时默认的起点是符合用户要求的(通常按照相关性进行排序)。要充分明确:如果你的搜索程序正在完成一些“引人入胜的”功能时,比如将搜索扩展到同义词范畴、使用加权帮助排序或者自动修正语法错误时,要在搜索结果顶部对此进行说明,并使用户能够很轻易地关闭这些功能。

NOTE

 

最糟糕的事情莫过于毁掉用户对搜索结果的信心,这事很容易发生。一旦发生这种情况,用户可能放弃使用你的搜索引擎,而你可能再也没机会挽回用户对它的信心了。

最重要的是,要对自己开发的搜索程序进行广泛的试用。要体验它好的特性,但更要好好修正它的问题。几乎可以肯定地说,你的搜索界面会出现一些语法错误,对于这个问题你可以使用Lucene的conrib模块,即语法检查器,具体见8.5小节。同样地,在每一条搜索结果下面高亮显示动态摘录(有时叫做概要)也很重要,Lucene的contrib目录提供了两个模块完成该功能:高亮模块和高速向量高亮模块,使用方法见8.3小节和8.4小节。

Lucene不提供默认的用户搜索界面;该界面完全由你的程序构建。当用户用你的搜索界面进行搜索交互时,他们会提交一个搜索请求,该请求需首先转换成合适的查询query)对象格式,以便搜索引擎使用。

建立查询(BUILD QUERY)

当你试图吸引用户使用你的搜索程序时,他们会提交一个搜索请求,通常以HTML表单或者Ajax请求的形式由浏览器提交到你的搜索引擎服务器。然后须将这个请求转换成搜索引擎使用的查询Query)对象格式,这称为建立查询步骤。

查询对象可能很简单,也可能很复杂。Lucene提供了一个称之为查询解析器QueryParser)的强大开发包,用它可以根据通用查询语法将用户输入的文本处理成查询对象。查询对象及其语法将在第3章详述,完整的手册请参考http://lucene.apache.org/ java/3_0_0/queryparsersyntax.html。查询语句可以包含布尔运算、短语查询(包含两个引用)或通配符查询。如果你的应用程序对用户搜索界面有更好的控制,或者其他有趣的限制,那你必须实现一定的逻辑将它转换成对应的查询语句。例如,如果需要限制用户对文档集的搜索范围,那么你需要在查询语句中创建过滤器,该部分内容详见5.6小节。

如果在索引操作期间没能进行加权操作,很多程序都会修改查询语句,以便对重要信息进行加权或过滤。通常,一个电子商务网站会对利润更高的产品分类进行加权,或者滤出当前脱销的产品(这样顾客就不知道该产品已经脱销,从而不会到别处去购买它)。但一定要避免过多的加权和滤除搜索结果:因为用户会了解到这一切并失去对你的信任。

对于搜索程序来说,Lucene默认的查询解析器一般是够用的。有时你会想用查询解析器的输出结果,但后来又会加入自己的处理逻辑来提炼搜索对象。其他时候还想定制查询解析器的语法,或定制查询解析器创建的查询实例,由于Lucene的开源特性,这一切都很容易做到。我们将在6.3小节讨论定制查询解析器。现在,我们准备好执行搜索请求,并返回结果。

搜索查询(SEARCH QUERY)

搜索查询是这样一个过程:查询检索索引并返回与查询语句匹配的文档,结果返回时按照查询请求来排序。搜索查询组件涵盖了搜索引擎内部复杂的工作机制,Lucene正是如此,它为你完成这一切。并且,Lucene在该点有非常好的扩展机制,所以如果你想定制搜索结果的搜集、过滤、排序等功能,这些都很容易实现。详细内容请参考第6章。

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

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

展现结果

一旦获得匹配查询语句并排好序的文档结果集,接下来你就得用直观的、经济的方式为用户展现结果。UI也需要为后续的搜索或操作提供清晰的向导,如点击进入下一页面、完善搜索结果,或者寻找与匹配结果相似的文档,这样用户才不至于在使用中进入死胡同。

我们已经介绍完搜索程序中的索引和搜索组件,但搜索程序的内容不止于此,它还有其他模块。

对于典型的搜索引擎,特别是对于一个运行于Web站点的搜索引擎而言,还有很多模块未曾提到。搜索程序必须包括管理模块,这样才能跟踪程序的运行状况、配置程序的各种组件、以及启动和停止搜索服务。程序还必须包括分析模块,以便采取不同的视角观察用户是如何进行搜索的,从而对搜索程序各模块的运行给予必要的指示。最后,对于大的搜索程序来说,搜索范围(scaling)是一项非常重要的指标,唯有这样才能使搜索程序能够处理越来越大的搜索内容和更多的并发搜索请求。图1.4左侧展开后即管理界面。

管理界面(ADMINISTRATION INTERFACE)

当代搜索引擎是一种复杂的软件,并具有大量需要配置的控制功能。如果你使用爬虫软件来找寻搜索内容,那么就需要在管理界面上设置初始URL,以及创建规则来界定爬虫软件需要访问的站点或者搜索引擎需要加载的文档类型,还有设置搜索引擎读取文档的速度等。启动和停止搜索服务、控制备份(如果搜索范围较大或者需要高可用性故障恢复)、选择搜索日志、检查系统总体运行健康状况、从备份中建立和恢复系统等都属于管理界面的功能范畴。

Lucene管理界面向开发人员提供了大量配置选项。在进行索引操作时,你可能需要调节内存缓冲区的使用量、一次性合并的段数量、提交更改的频率,以及优化和清除某索引的时间点。我们将在第2章详细讲述该部分内容。在搜索过程中同样重要的管理选项,比如重新打开reader的频率。也许你还想公开一些有关索引的基础摘要信息,比如段信息和已挂起的删除操作数量。如果一些文档未能被正确索引,或者在搜索过程中一些查询请求出现异常,那么管理模块的API可以提供相关细节信息。

很多搜索程序,如桌面搜索,是不需要管理模块的,但一个完整的商业搜索程序都会包含一个复杂的管理界面。这个界面通常是基于Web页面的,但它可能同时包含一些附加的命令行工具。下面介绍搜索程序的分析界面,如图1.4右侧所示。

分析界面(ANALYTICS INTERFACE)

图1.4右侧展开即为分析界面,它通常是基于Web页面的UI,可以运行在独立的服务器上,并主管报表引擎。分析功能是重要的:通过查询搜索日志中的图表,你可以获取大量的用户相关信息,并能知晓用户为什么通过/不通过你的Web站点购买你的商品。一些人甚至认为这是部署高端搜索引擎的最重要的理由!如果你经营着一个电子商务Web站点,这些极其强大的工具能加强用户的购物体验,因为它们可以让你了解到用户如何使用搜索功能、哪些搜索未能获得令用户满意的结果、哪些搜索结果被用户点击过,以及最终交易依赖/不依赖搜索的频率。

基于Lucene的一些性能指标能够为分析接口提供参数,这些指标包含如下内容:

另外,你可能还想了解有关索引的性能指标,比如平均每秒被索引的文档数量或者文档字节大小。

Lucene由于仅仅是一个搜索程序类库,它不提供任何分析工具。如果你的搜索程序是基于Web的,可以使用Google分析接口来快速创建分析界面。如果这不能满足你的需求,你还可以基于Google的可视化API来创建自定义的分析图表。下面我们讨论最后一项内容:搜索范围。

搜索范围(SCALING)

搜索程序较为棘手一部分就是它的搜索范围。绝大多数搜索程序都不能在单台计算机上完成足够数量的数据搜索或并发搜索。Lucene的索引和搜索吞吐量使得我们可以在一台当代计算机上进行相当大数量的数据处理。另外,这些程序可能需要运行在两台计算机上,以避免由于硬件问题而出现单点故障(即无停工期)。这种解决方案还使你能够在不影响当前搜索程序运行的情况下临时推出一台计算机来进行维护和升级。

搜索范围有两种界定方式:净处理内容和净查询吞吐量。如果要处理的数据量较大,你必须将这些数据分割成各个小部分,以便让多台分离的计算机分别搜索对应部分。前端服务器会将新来的查询请求发送至所有部分,然后将各部分搜索结果合并成总的搜索结果集。如果你想在程序使用的高峰期获得较高的搜索吞吐量,那么你必须将同一索引复制到前述多个计算机上。前端加载平衡器会将新来的查询请求发送给加载最少的后台计算机中。如果你需要同时使用以上两种界定方式,正如Web搜索引擎所做的那样,那么可以将以上实践合并起来。

要建立这样一个搜索架构,需要对它加入大量的复杂算法。你将需要一个稳妥的方式来复制各个计算机中的索引。如果某台计算机宕机,不管该事件是否在计划内,你都需要让它能够继续工作。如果需要处理事务性请求,所有的Searcher都必须能够同时“启用”针对新索引的提交功能,这会增加程序的复杂度。对于分布式搜索系统的错误恢复也是复杂的。最后,诸如语法修正、高亮显示甚至是如何计算搜索项的评分等重要功能也会在分布式架构中受到影响。

Lucene并没有提供有关搜索范围的处理模块。但Apache Lucene项目下的Solr和Nutch项目都提供了对索引拆分和复制的支持。Katta开源项目(http://katta.sourceforge.net)是基于Lucene的,它也能提供这个功能。Elastic search(http://www.elasticsearch.com)提供了另一个选择,它也是基于Lucene的开源项目。在搭建自己的搜索程序之前,建议你最好仔细研究一下这些已有的解决方案。

我们已回顾了当代搜索应用程序的各个组件。下面我们将介绍Lucene是如何与应用程序进行整合的。

正如前述,当代搜索引擎需要很多组件来实现。但是,对于特定的搜索程序来说,它对以上组件的需求跟其他搜索程序有很大差别。Lucene很好地实现了大部分上述组件(如图1.4所示的灰色阴影部分),但其他未能实现的组件最好是由其他开源软件来补充,或者自己来定制开发这些程序逻辑。可能你的搜索程序功能比较专一,而并不需要其中某些组件,这时你得好好感受一下当初我们提到Lucene是一个搜索程序类库而不是完整的程序的意思。

如果Lucene不能直接整合到你的搜索程序中,有可能一些基于Lucene的开源项目能够满足你的要求。例如,Solr作为服务器程序运行并提供一个管理界面(包含两种搜索范围),提供索引数据库内容的能力,提供类似于分组导航的终端功能,这些功能都是基于Lucene构建的。Lucene是搜索类库而Solr提供了完整搜索程序的大部分组件。

另外,一些Web程序框架也提供了基于Lucene的搜索插件。例如,有一个适用于Grails(http://www.grails.org/Searchable+Plugin)开源项目的搜索插件,该插件基于Compass搜索引擎框架,而后者是采用Lucene作为后台的。

现在,我们来看看一个具体的搜索程序是如何用Lucene来进行索引和搜索的。

我们来看看Lucene的实际应用。为了说明这个问题,我们需要首先回顾一下1.3节提到的有关索引和搜索文件的内容。为了展现Lucene的索引和搜索能力,我们将采用一对基于命令行启动的程序:Indexer和Searcher。首先我们将某个目录中的文件进行索引,然后搜索这个索引。

这些示例程序将使你对Lucene的API、它的易用性和强大功能有更深入的了解。示例代码都是完整的、可直接运行的命令行程序。如果你需要解决文件索引和搜索的问题,可以复制这些代码并加以修改以满足自己的需要。下一章我们将对Lucene使用过程的方方面面进行更详细的讲解。

在使用Lucene进行搜索之前,我们需要创建索引文件,所以我们从Indexer程序开始介绍。

本节你会看到一个名为Indexer的简单类,它可以对某个目录下所有的以.txt扩展名结尾的文件进行索引。当Indexer运行结束时,会返回一个索引文件,供它的姊妹程序Searcher(详见1.4.2小节)使用。

你不需要很熟悉示例中用到的这几个Lucene相关类和方法,稍后我们将对此进行进一步解释。在看过这些带注释的代码后,我们将向你展示如何使用Indexer程序;如果该内容能助你在编写代码之前更好地理解Indexer的用法,可以直接研读代码后面的使用方法。

使用Indexer索引文本文件

程序1.1展示了Indexer命令行程序,这最早出现在Erik在java.net给出的有关Lucene的介绍性文章中。它带有两个参数:

程序1.1 Indexer:索引.txt文件

Indexer程序较为简单。程序入口的静态方法解析❶、❷两个输入参数,创建Indexer实例,在预设目录中定位❻*.txt文件并且输出被索引文档数和处理时间。包含Lucene API的代码同时也包括创建❸和关闭❹IndexWriter、创建❼❽❾文档对象、将❿文档对象加入索引,以及返回被索引的文档数量❺。

为了描述得更简洁,并展示Lucene的用法及其能力,示例代码仅仅专注于处理.txt扩展名结尾的纯文本文件。在第7章我们会介绍如何采用Tika框架对其他类型的普通文件进行索引,如Microsoft Word文件或Adobe PDF文件。在使用Indexer之前,我们先谈谈StandardAnalyzer的第一个参数:版本号参数。

版本号参数

从版本2.9起,大量的类在初始化时都接受Version类型参数(出自于org. apache.lucene.util包)。该类定义了枚举常量,如LUCENE_24LUCENE_29,它们用来标识Lucene的小版本。当输入其中一个版本值时,它会指示Lucene针对该值对应的版本进行环境和行为匹配。Lucene也会模拟该版本当前存在并在后续版本中修改过的bug,如果Lucene开发者认为修正bug会影响向后兼容现存索引的话。对于接受版本号参数的每个类来说,你必须查阅Javadoc手册来获知哪些设置或bug在版本之间变更了。本书所有示例程序都使用LUCENE_30版本。

尽管一些人认为版本号参数搞乱了Lucene的API,事实上它证实了两点:Lucene的成熟度和Lucene开发人员如何严肃地对待向后兼容。版本号参数使得Lucene有一定的自由来修改bug以及为新用户改善初始设置,久而久之,这已经变成实现向后兼容这一项重要内容了。该参数还让你面对向后兼容问题时能自行选择使用最新版本还是最兼容版本。

下面我们使用Indexer来构建第一个Lucene搜索索引!

运行Indexer

运行Indexer最简单的方法就是通过Apache Ant进行。首先你需要解压包含本书源代码的ZIP包,该包可以从http://www.manning.com/hatcher3站点下载,然后转到目录lia2e。如果在工作目录没有找到build.xml文件,说明目录没有设置正确。如果你是首次运行的话,Ant可以编译所有示例代码、构建测试索引、最后运行Indexer,首次运行Indexer时会提示你确定索引文件和被索引文档的目录,当然也可以使用默认目录。另外,还可以从命令行使用Java运行Indexer,只要确认调用Java的脚本包含对应JAR包的子目录以及build/classes目录即可。

在默认情况下,索引文件会被放置在目录indexes/MeetLucene下,放置在目录src/lia/meetlucene/data的示例文档才会被索引。该目录包含一个当代开源代码许可证样本。

下面我们继续。输入ant Indexer后,你会看到如下输出:

Indexer会打印出索引的文件名,这样就能看出它只对.txt格式的文件进行索引操作。当结束索引操作后,Indexer会打印被索引文件数和索引操作所耗时长。由于报告的时长包括文件路径列表和索引操作两部分,我们不能将此作为Indexer的官方性能指标。在本例中,每个被索引的文件都很小,但要花约0.8秒的时间来索引一组文本文件这个事实还是会令你印象深刻的。很明显,索引吞吐量很重要,我们将在第11章中详细讲述这块内容。总的来说,搜索操作要比索引操作重要得多,因为索引文件只被创建一次,却要被搜索多次。

在Lucene中进行搜索和索引一样快速简单。在第3、5、6章你会看到Lucene在搜索方面强大、令人惊叹的功能。现在,我们看看Searcher这个命令行程序,我们用它来搜索经由Indexer创建的索引。需要注意的是,这里用Searcher是为了展示Lucene的搜索API的用法。你的搜索程序也可以采用Web或桌面GUI程序的形式,或者Web应用程序的形式等。

在上一节中,我们索引了一个包含文本文件的目录。在本例中,索引文件跟Indexer存放在同一目录。我们在indexes/MeetLucene目录下用Indexer创建了一个Lucene索引。正如程序1.1中所示,该索引包含每个被索引文件及其绝对路径信息。现在,我们需要用Lucene来搜索那个索引,并找出包含指定文本片段的文件。比如,我们可能想查找所有包含patent或redistribute关键字的文件,或者希望找出包含短语modified version的文件。现在我们来试试搜索效果。

用Searcher来实现搜索

Searcher程序最早出现在Erik在java.net给出的有关Lucene的介绍性文章中,它提供命令行运行方式的搜索功能,与Indexer相辅相成。程序1.2给出了Searcher全貌。它包含两个命令行参数:

程序1.2 Searcher搜索Lucene索引

Searcher和它的姊妹Indexer一样简单并且仅有几行代码涉及Lucene:

❶❷ 解析命令行参数(索引路径、查询字符串)。

❸ 使用Lucene的DirectoryIndexSearcher类打开索引文件用于搜索。

❹ 使用QueryParser将人可读的查询解析为Lucene的Query类。

❺ 以TopDoc对象的形式返回搜索结果集。

❻ 打印搜索细节(搜索结果集数量和搜索时间)。

❼❽ 注意TopDocs对象只包括对应文档的引用。换句话说,匹配文档不是在搜索过程中立即被加载的,而是从索引中慢速加载的——即只有被IndexSearcher.doc(int)方法调用后才被加载。该调用返回一个Document对象,随即我们可以从中获取单个域值。

❾ 完成搜索后需要关闭IndexSearcher

运行Searcher

我们运行一下Searcher,用“patent”作为查询条件来搜索文档:

这个输出表明:在被索引的16个文件中,有8个文件包含单词“patent”,此外搜索过程花了不到11毫秒时间。由于Indexer存储了文件的绝对路径,使得Searcher能够将它们打印出来。值得注意的是,该例中我们把文件路径作为一个域存储在索引里,并认为这是合适的,但从Lucene的观点来看,它只是包含在索引文档中的任意元数据而已。

你也可以使用更为复杂的查询,如“patent AND freedom”或者“patent AND NOT apache”或者“+copyright +developers”等。第3、5、6章将谈到搜索的各个不同方面,包括Lucene的查询语法。

我们在索引和搜索程序示例中让你初步了解一下Lucene的功能。Lucene的API使用起来简单而普通。其主体代码(适用于所有与Lucene交互的程序)与实际业务并无直接关系——Indexer解析命令行参数、文件索引路径参数,以及Searcher将符合查询条件的文件名打印到标准输出设备上。但不要因为这个例子简单就感到满足:Lucene包含的内容还很多。

为了有效使用Lucene,你需要深入理解它是如何工作的,以及如何在需要的时候扩展它。本书后面的章节将专门为你讲述这些内容。

下面,我们将深入介绍Lucene为索引和搜索所公开的核心类。

正如你在Indexer类中所看到的,执行简单的索引过程需要用到以下几个类:

图1.5显示了这些类分别参与到索引过程中。接下来是每个类的简要概述,这能让你对这些类在Lucene中所扮演的角色有一个大体印象。我们将会在本书中使用这些类。

图1.5 使用Lucene索引文档时用到的类

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

Directory类描述了Lucene索引的存放位置。它是一个抽象类,它的子类负责具体指定索引的存储路径。在前面的Indexer例子中,我们用FSDirectory.open方法来获取真实文件在文件系统的存储路径,然后将它们依次传递给IndexWriter类构造方法。

Lucene包含大量有趣的Directory实现,具体请参考2.10小节。IndexWriter不能直接索引文本,这需要先由Analyzer将文本分割成独立的单词才行。

文本文件在被索引之前,需要经过Analyzer(分析器)处理。Analyzer是由IndexWriter的构造方法来指定的,它负责从被索引文本文件中提取语汇单元,并提出剩下的无用信息。如果被索引内容不是纯文本文件,那就就需要先将其转换为文本文档。第7章会介绍如何使用Tika从常用的多媒体格式文件中提取文本内容。Analyzer是一个抽象类,而Lucene提供了几个类实现它。这些类有的用于跳过停用词(stop words)(指一些常用的且不能帮助区分文档的词,如a、an、the、in和on等);有的用于把语汇单元转换成小写形式,以使搜索过程能忽略大小写差别;除此之外,还有一些其他类。Analyzer是Lucene很重要的一部分,它的用途远远不止过滤输入这一项。对于要将Lucene集成到应用程序的开发人员来说,选择什么样Analyzer的是程序设计中非常关键的一步。该部分内容将在第4章详细介绍。

分析器的分析对象为文档,该文档包含一些分离的能被索引的域。

Document(文档)对象代表一些域(Field)的集合。你可以将Document对象理解为虚拟文档——比如Web页面、E-mail信息或者文本文件——然后你可以从中取回大量数据。文档的域代表文档或者和文档相关的一些元数据。文档的数据源(比如数据库记录、Word文档、书中的某章节等)对于Lucene来说是无关紧要的。Lucene只处理从二进制文档中提取的以Field实例形式出现的文本。上述元数据(如作者、标题、主题和修改日期等)都作为文档的不同域单独存储并被索引。

NOTE

 

本书所涉及的Document(文档)概念是指某个文档格式,如Word、RTF、PDF或其他格式;这并不是指Lucene的Document类。注意两个单词在大小写和字体上的区别。

Lucene只处理文本和数字。Lucene的内核本身只处理java.lang.String、java.io.Reader对象和本地数字类型(如int和float类型)。索然各种类型的文档都能被索引和搜索,但处理非文本和非数字类型的文档过程并没有处理后两类文档简单直接。读者可以从第7章中了解更多处理非文本文档的内容。

在Indexer中,我们专注于针对文本文件的索引操作。因此,我们为每一个检索到的文件创建一个Document实例,并向实例中添加各个域(该部分内容将在后面详述),然后将Document对象添加到索引中,这样就完成了文档的索引操作。类似地,在你自己的搜索程序中,你得仔细设计如何构建Lucene文档和域,使之能满足数据源处理要求和程序设计要求。

Document对象的结构比较简单,为一个包含多个Field对象的容器;Field是指包含能被索引的文本内容的类。

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

在使用Lucene进行索引操作时,上述几个类是最常用的。为了实现最基本的搜索功能,你同样需要熟悉Lucene这套等量的小而简单的类。

Lucene提供的搜索接口跟索引接口一样简单易懂。仅需要少数一些类来执行基本的搜索操作:

下面几节提供了这些类的简要介绍。在进入更高级主题之前,我们将在余下几章进行详述。

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

有关IndexSearcher类的细节将在第3章介绍,并在第5、6章中给出更深层的内容。下面开始介绍搜索的基本单元:Term类。

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

以上代码命令Lucene寻找contents域中包含单词lucene的前10个文档,并按照降序排列这10个文档。由于TermQuery对象是从抽象父类Query派生而来,你可以在声明左侧使用Query类型。

Lucene含有许多具体的Query(查询)子类。到目前为止,我们谈到的只是Lucene基本的Query子类:TermQuery类。其他的Query子类有:BooleanQueryPhraseQueryPrefixQueryPhrasePrefixQueryTermRangeQueryNumericRangeQueryFilteredQuerySpanQuery。所有这些查询类内容都包含在第3章和第5章。Query是它们共同的抽象父类。它包含了一些非常私用的方法,其中最有趣的当属setBoost(float)方法,该方法能使你告知Lucene某个子查询相对其他子查询来说必须对最后的评分有更强贡献。setBoost方法将在3.5.12小节详述。下面我们介绍TermQuery类,它在Lucene中是大多数复杂查询的基础。

TermQuery是Lucene提供的最基本的查询类型,也是简单查询类型之一。它用来匹配指定域中包含特定项的文档,正如你在上面几段中看到的一样。我们最后以TopDocs类来结束搜索核心类简介,该类负责展示搜索结果。

TopDocs类是一个简单的指针容器,指针一般指向前N个排名的搜索结果,搜索结果即匹配查询条件的文档。TopDocs会记录前N个结果中每个结果的int docID(可以用它来恢复文档)和浮点型分数。第3章将给出TopDocs更多细节。

通过本章内容,你已获得一些有关搜索程序架构的背景知识,以及一些有关Lucene的基础知识。现在你已经知道Lucene是一个信息检索工具库,而不是一个软件成品,并且更应该清楚它并不是像刚接触Lucene的人想象的那些网络爬虫、文档过滤器及用户搜索界面等。然而,为了Lucene的普及,有众多的项目是与Lucene集成或是基于Lucene的,这些项目或许能够会成为构建自己搜索程序的有益帮助。另外,你可以通过众多的Java以外的编程环境来实现Lucene对应功能。同时,你应已经了解到一些关于Lucene是如何产生及其背后关键人物和组织的情况。

秉承Manning出版社的in Action系列书籍的一贯风格,我们迅速切入重点,向读者展示了两个独立的应用程序:Indexer和Searcher,它们可以用来索引和搜索存储在文件系统的文本文件。然后,我们简短描述了在这两个应用程序中Lucene所使用的类。

搜索无处不在,如果你碰巧正在读这本书,同时又对程序集成搜索功能感兴趣的话,那么本书对你是很有帮助的。根据你的需求,集成Lucene也许意义不大,或者需要再考虑软件架构等因素。

我们已经像本章一样组织好了下面的两章内容。首先我们要做的是索引文档;本书将在第2章中对此进行详细讨论。

[1] 本书作者Erik和Mike对Apple相关的技术都颇感兴趣。

[2] Lucene是Doug妻子的中名,同时这也是她外祖母的姓。


相关图书

算法详解 卷2 图算法和数据结构
算法详解 卷2 图算法和数据结构

相关文章

相关课程