Lucene in Action
中文版
第一部分
Lucene核心
1.
接触
Lucene
2.
索引
3.
为程序添加搜索
4.
分析
5.
高极搜索技术
6.
扩展搜索
第二部分
Lucene应用
7.
分析常用文档格式
8.
工具和扩充
9.
Lucene其它版本
10.
案例学习
序
Lucene开始是
做为私有项目。在1997年末,因为工作不稳定,我寻找自己的一些东西来卖。Java是比较热门的编程语言,我需要一个理由来学习它。我已经了解如何来编写搜索软件,所以我想我可以通过用Java写搜索软件来维持生计。所以我写了Lucene。
几年以后,在
2000年,我意识到我没有销售天赋。我对谈判许可和合同没有任何兴趣,并且我也不想雇人开一家公司。我喜欢做软件,而不是出售它。所以我把Lucene放在SourceForge上,看看是不是开源能让我继续我想做的。
有些人马上开始使用
Lucene。大约一年后,在2001年,Apache提出要采纳Lucene。Lucene邮件列表中的消息每天都稳定地增长。也有人开始贡献代码,大多是围绕Lucene的边缘补充:我依然是仅有的理解它的核心的开发者。尽管如些,Lucene开始成为真正的合作项目。
现在,
2004年,Lucene有一群积极的深刻理解其核心的开发者。我早已不再每天作开发,这个强有力的工作组在进行实质性的增加与改进。
这些年来,
Lucene已经翻译成很多其它的语言包括C++、C#、Perl和Python。在最开始的Java和其它这些语言中,Lucene的应用比我预想的要广泛地多。它为不同的应用(如财富100公司讨论组、商业Bug跟踪、Microsoft提供的邮件搜索和100页面范围的Web搜索引擎)提供搜索动力。在业内,我被介绍为“Lucene人”。很多人告诉我他们在项目中使用到Lucene。我依然认为我只听说了使用Lucene的程序的小部分。
如果我当初只是出售它,
Lucene应用得不会这么广泛。程序开发人员看来更喜欢开源。他们在有问题时不用联系技术支持而只需查看一下源代码。如果这还不够,邮件列表中的免费支持比大多商业支持要好得多。类似Lucene的开源项目使得程序开发人员更加有效率。
Lucene通过开源已经变得比我想象的伟大的多。我见证了它的发展,是
Lucene社区的努力才使得它如此兴旺。
Lucene的未来怎样?我无法回答。有了这本书,你现在也是
Lucene社区的一员,现在由您将Lucene带往新的高地。旅途顺利!
DOUG CUTTING
Lucene和
Nutch的作者
前言
来自
Erik Hatcher
在
Internet早期我就对搜索和索引感兴趣。我已经建立了用majordomo、MUSH(Mail User’s Shell)和少量Perl、awk及shell脚本来管理邮件列表的存储结构。我实现了一个CGI的web接口,允许用户搜索这个列表和其它用户的信息,其内部使用了grep。然后相继出现了Yahoo!、AltaVista和Excite,这些我都经常访问。
在我有了第一个儿子
Jakob之后,我开始了数字照片档案的设计。我想开发一套管理图片的系统,可以给图片附加元数据,如关键字、拍摄日期。当然用我选择的尺寸定位图片是很容易的。在19世纪90年代末,我构建了基于文件系统的原型,使用了Microsoft的技术,包括Microsoft Index Server、Action Server Pages及处理图片的第三方COM组件。从那时起,我的职业生涯都消耗在这些类似的技术上了。I was able to cobble together a compelling application in a couple of days of spare-time hacking.
我的职业转向
Java技术,并且我越来越少地利用Microsoft Windows。为了以系统无关的方式用Java技术重新实现我的个人照片档案系统及搜索引擎,我使用了Lucene。Lucene的简单易用远远超过了我的期望—我所期望的其它开源库或工具在概念上简单,但是却难以使用。
在
2001年,Steve Loughran和我开始编写Java Development with Ant(Manning)。我们采用图片搜索引擎的思想,并把它推广为一个文档搜索引擎。这个程序示例在那本Ant书中使用,而且可被定制为图片搜索引擎。Ant的责任不仅来自于简单的编译打包的构建过程,也来自于定制的任务,<index>,我们在构建过程中使用Lucene创建索引文件。Ant任务现在生存在Lucene的Sandbox(沙箱)中,将在本书8.4节描述。
Ant已经应用在我的
博客系统中,我称为BlogScene(http://www.blogscene.org/erik)。在建立一个博客实体之后,我运行一个Ant构建过程,索引新的实体并将它们上传到我的服务器上。我的博客服务器由一个Servlet、一些验证模板和一个Lucene索引组成,允许(rich)查询,甚至联合查询。与其它博客系统相比,BlogScene在特色和技巧上差很多,但是它的全文检索能力非常强大。
我现在效力于维吉尼亚大学对
Patacriticism的应用研究小组(http://www.patacriticism.org)。我用对文本分析、索引和搜索的经验通过讨论量子力学与艺术的关系来测试及拓展我的思路。“诗人是世界上不被认可的最伟大的工程师”。
来自Otis Gospodnetic
我对信息搜索与管理的兴趣和热情开始于在
Middlebury大学的学生时代。那时候,我发现了信息的广大资源,即Web。尽管Web仍然刚开始发展,但是对收集、分析、索引和搜索的长期需求是很明显的。我开始对建立来自Web的信息库感到困惑,开始编写Web爬行器梦想有种方法可以对这些收集的信息进行搜索。我认为在巨大的未知领域中搜索是杀手级软件。有了这种思想以后,我开始了一系列收集和搜索项目。
在
1995年,和同学Marshall Levin一起创建了WebPh,一个用来收集和找出个人联系信息的开源程序。基本上,这是一个简单的具有Web接口(CGI)的电话本,那时排在首位的类型。(实际上,它在19世纪90年代末的案例学习中被引用为一个示例。)大学和政府机构是这个程序的主要用户,现在还有很多在使用它。在1997年使用我的WebPh,我继续创建了Populus,一个当时很流行的白页。尽管技术(与WebPh类似)很普通,但是Populus有很重的负担,并且能够与WhoWhere、Bigfoot和Infospace等大角色相媲美。
在两个关于个人联系信息的项目之后,是该探索新的领域了。我开始了下一个冒险,
Infojump,用来在网上时事通讯、杂志、报纸中选择高质量的信息。我拥有的软件由大量的Perl模块和脚本组成,Infojump利用一个称作Webinator的Web爬行器和一个全文搜索的产品叫作Texis。在1998年Infojump提供的服务很像今天的FindArticles.com。
尽管
WebPh、Populus和Infojump达到了它们的目的并是功能很完善,但它们都有技术的局限性。它们缺少的是一个用反向索引来支持全文搜索强大的信息搜索库。为了不重复相同的工作,我开始搜寻一个我认为不可能存在的解决方案。在2000年早期,我发现了Lucene,我正在寻找的缺少的部分,并且我一下子就喜欢上了它。
我在
Lucene还在SourceForge的时候就加入了这个项目,后来2002年Lucene转移到Apache软件基金会。我对Lucene的热爱是因为这些年来它已经成为我很多思想的核心组件。这些思想中的一个是Simpy,我最近的一个项目。Simpy是个有许多特点的个性Web服务,可以让用户加标签、索引、搜索和共享在网上找到的信息。它主要使用了Lucene,上千条索引,由Doug Cutting的另一个项目Nutch(见第10章)提供动力支持。我对Lucene的积极参与导致我被邀请与Erik Hatcher共同编写Lucene in Action。
Lucene In Action有关于
Lucene最全面的信息。接下来的10章包含的信息围绕你使用Lucene创建优秀程序所需的所有主题。这是它平坦且轻快的协作过程的结果,就像Lucene社区一样。Lucene和Lucene in Action证明了有类似兴趣的人们可以完成什么,不管在人生中会碰到什么情况,都会积极地为全球知识的共享做出贡献。
致谢
首先并且是最重要的,我们感谢我们的妻子
Carole(Erik)和Margaret(Otis),一直支持这本书的写作。没有她们的支持,这本书就不可能出版。Erik感谢他的两个儿子,Ethan和Jakob,因为他们的忍耐和理解,Erik写这本书时没有时间陪他们玩耍。
我们真诚感谢
Doug Cutting。没有Doug的贡献,就不可能有Lucene。没有其他Lucene的贡献者,Lucene就会少很多特征、更多的Bug,Lucene的成长就会花更长的时间。感谢所有的贡献者,包括Peter Carlson、Tal Dayan、Scott Ganyo、Eugene Gluzberg、Brian Goetz、Christoph Goller、Mark Harwook、Tim Jones、Daniel Naber、Andrew C. Oliver、Dmitry Serebrennikov、Kelvin Tan和Matt Tucher。同时,我们感谢所有贡献在第10章的案例的人:Dion Almaer、Michael Cafarella、Bob Carpenter、Karsten Konrad、Terence Parr、Robert Selvaraj、Ralf Steinbach、Holger Stenzhorn和Craig Walls。
…
…
本书简介
Lucene in Action为使用最好的
Java开源搜索引擎的用户提供所有细节、最好的实践、警告、技巧。
本书假设读者熟悉基本的
Java编程。Lucene本身是个Java档案(JAR)文件并能集成到简单的命令行程序和大型企业级应用程序中。
Roadmap
我们在本书第
1部分覆盖Lucene核心编程接口(API)使你在将Lucene整合到你的程序中时愿意使用它:
n
第
1章,接触Lucene。我们介绍了一些基本的信息搜索术语和Lucene的主要竞争对手。我们很快地构建了一个你马上能用或修改以适应需要的简单索引和搜索程序。这个示例程序向你打开了探索Lucene其它能力的大门。
n
第
2章使你熟悉Lucene基本的索引操作。我们描述了索引数值和日期的不同字段类型和各种技术。包括调整索引过程、优化索引以及如何处理线程安全。
n
第
3章向你介绍基本的搜索,包括Lucene如何根据查询来排列文档的细节。我们讨论基础的查询类型及它们如何通过用户输入的查询表达式创建。
n
第
4章深入研究Lucene的索引核心,分析过程。分析器创建块及单词、单词流和单词过滤器。我们创建了一些定制的分析器,showcasing synonym injection and metaphone(like soundex) replacement.也分析了非英语语言,典型的分析汉字文本的示例。
n
第
5章讲述搜索章节剩余的。我们描述了一些高级的搜索特征,包括排序、过滤及使用词向量。高级的查询类型在此出现,包括SpanQuery家族。最后,我们讨论了Lucene对查询多索引的内建支持,并行的及远程的。
n
第
6章超越高级搜索,向你展示了如何扩展Lucene的搜索能力。你将学到如何定制搜索结果的排序、扩展查询表达式分析、实现Hit收集和调整查询性能。
第
2部分超越Lucene内建的工具并向你展示围绕Lucene可以做什么。
n
第
7章,我们创建了可重用、可扩展的用来分析Word、HTML、XML、PDF及其它格式文档的框架。
n
第
8章包括围绕Lucene的扩展和工具。我们描述了一些Lucene的索引查看和开发工具以及Lucene沙箱中的好东西。高亮搜索项就是这种你想要的沙箱扩展,还有在Ant构建过程中创建索引的其它工具。使用noncore分析器,并使用类似WordNet的索引。
n
第
9章描述Lucene翻译成其它各种语言的版本,如C++、C#、Perl和Python。
n
第
10章将Lucene的技术细节带到大量优秀的案例学习中。这些案例由那些创建了以Lucene为核心的有趣的、快速的、可升级的程序的开发者提供。
谁应该阅读本书?
在程序中需要强大搜索能力的开发人员需要阅读这本书。
Lucene in Action也适合于那些对Lucene或索引和搜索技术好奇的开发人员,他们可能不会马上就用到它。把Lucene添加到你的工具箱对以后的项目来说是值得的—搜索是个热门的话题并且将来也会是。
这本书主要使用
Java版的Lucene(来自Apache Jakarta),并且大多数示例使用Java。最适合熟悉Java的读者。Java经验是很有帮助的,然而Lucene已经翻译成很多其它的语言包括C++、C#、Python和Perl。概念、技术甚至API本身都和Java版Lucene差不多。
代码示例
本书的源代码可以从
Manning的网站http://www.manning.com/hatcher2上下载。代码的使用说明包含在代码包的README文件。
书中出现的大多数代码是由我们编写并包含在代码包中。某些代码
(尤其是案例代码)不在我们的代码包中提供。书中的代码片断归贡献者所有。同时,我们包含了Lucene代码库的部分代码,基于Apache软件许可协议(http://www.apache.org/licenses/LICENSE-2.0)。
代码示例不包括
package 和import 语句,以节省空间;具体请参照实际代码。
为什么是JUnit?
我们相信书中的代码示例应该都是高质量的。典型的“
hello world”例子经常帮助读者测试他们的环境。
我们使用独特的方法来使用书中的代码示例。大部分示例是实际的
JUnit测试用例(http://www.junit.org)。JUnit,是Java单元测试框架,可以断言一个特殊情况是否能以可重复的方式出现。通过IDE或Ant进行自动JUnit测试用例可以一步一步地构筑系统。我们在本书用使用JUnit是因为平时都在其它项目中使用,并想让你看看我们如何编码。测试驱动开发(Test Driven Development, TDD)是我们强烈推荐的开发模式。
如果你对
JUnit不熟,请阅读以下基础。我们也建议你阅读Dave Thomas和Andy Hunt编著的《Pragmatic Unit Testing in Java with JUnit》,还有Vincent Massol和Ted Husted编著的《JUnit in Action》。
JUnit基础
这部分是对
JUnit快速但当然不完整的介绍。我们将提供理解我们示例代码所需的基础知识。首先,我们的JUnit测试用例继承junit.framework.TestCase并且很多通过部LiaTestCase基类间接继承它。我们的具体测试类附合这个命名习惯:给类名加后缀Test。例如,我们的QueryParser的测试是QueryParserTest.java。
JUnit自动执行所有类似
public void testXXX()的方法,此处XXX是个任意有意义的名称。JUnit测试方法必须简洁,保持好的设计。(例如创建可重复的功能模块等等)
断言
JUnit建立在一组
assert语句上,使你自由编写简洁的测试代码并使JUnit框架处理失败状态及指出细节。最常用的assert语句是assertEquals;一些是为不同的数据类型而重载的assertEquals方法。一个示例测试方法如下:
public void testExample() {
SomeObject obj = new
SomeObject();
assertEqueals(10, obj.someMethod());
}
如果指定的值
(在本例中的10)不等于真实值(本例中是调用obj的someMethod的返回值),assert方法抛出运行时异常。除了assertEquals,为了方便还有一些其他assert方法。我们也使用assertTrue(expression)、assertFalse(expression)和assertNull(expression)语句。这些测试分别判断这个表达式是否是true、false和null。
assert语句有个接受一个附加的
String参数的重载表示。String参数都是用来汇报的,在测试失败时向开发人员指出更多信息。我们使用这个String消息参数以更好的描述。
通过以这种风格编写我们的测试用例,可以从我们构建大系统的复杂中解放出来,而且可以每次只关注更少的细节。利用合适的测试用例,我们能够增强信心和灵活性。信心来自于我们知道代码的变化如优化算法不会破坏系统的其它部分,因为出现这种情况的话,自动测试组件能让我们在它影响产品之前发现。重构是一种改变代码内部结构的艺术
(或者说科学),所以它能够适应变化的需求而又不影响系统的对外接口。
在上下文中的JUnit
让我们看一下到目前为止谈论的
JUnit并把它放到本书的上下文中。JUnit测试用例继承于junit.framework.TestCase,且测试方法都类似public void testXXX()形式。我们的测试用例之一(第3章)如下:
public class BasicSearchingTest extends LiaTestCase {
public void testTerm() throws Exception {
IndexSearcher searcher = new
IndexSearcher(directory);
Term t = new
Term(“subject”, “ant”);
Query query = new
TermQuery(t);
Hits hits =
searcher.search(query);
assertEquals(“JDwA”, 1, hits.length());
One hit expected for
search for “ant”
t = new
Term(“subject”, “junit”);
hits = searcher.search(new TermQuery(t));
assertEquals(2, hits.length());
Two hits expected for “junit”
searcher.close();
}
}
当然,我们将在之后解释这个测试用例中使用的
Lucene API。现在我们只关注JUnit的细节。testTerm方法中的directory变量没在此类中定义。JUnit提供一个在执行每个测试方法之前的初始化钩子;这个钩子是名为public void setUp()的方法。我们的LiaTestCase基类以这种方式实现setUp:
public abstract class LiaTestCase extends TestCase {
private String indexDir = System.getProperty(“index.dir”);
protected Directory directory;
protected void setUp() throws Exception {
directory = FSDirectory.getDirectory(indexDir, false);
}
}
如果
testTerm中的第一个断言失败,我们会得到一个异常:
junit.framework.AssertionFalsedError:
JDwA expected:<1> but was:<0>
at lia.searching.BasicSearchingTest.
→
testTerm(BasicSearchingTest.java:20)
这个失败指出我们的测试数据与预期的结果不同。
测试Lucene
本书中的大部分测试都是测试
Lucene本身的。实际上,这是否现实呢?难道要测的不是我们自己写的代码而是库本身?有个Test Driven Development的姊妹篇是用来学习API的:Test Driven Learning。它为新API写测试以了解它是如何工作以及你能从中得到什么时非常有帮助。这正是我们在大部分代码示例中所做的,所以测试都是测试Lucene它本身。但是不要把这些为学习而做的测试抛开。保留它们以确保你在升级到新版的API或因API改变而重构时,它们能够保持真值。
模型对象
在一些用例中,我们使用模型对象来测试。模型对象用来作为探测器传入真实的业务逻辑,以判断这个业务逻辑是否正常工作。例如,第
4章中有个SynonymEngine接口(4.6节)。使用这个接口的真实业务逻辑是个分析器。当我们想测试这个分析器本身时,SynonymEngine使用什么类型就不重要,我们只想使用一个定义良好并有可预见行为的对象。模型对象可以使得测试用例尽可能简单,这样它们每次只测试系统的一个方面,在测试失败要修正什么错误时没有纠缠的依赖。使用模型对象的一个好处来自于设计的变动,例如关系的分离和设计使用接口代替直接具体实现。
我们的测试数据
为了避免每个小节都要用完全不同的数据,书中大部多都是围绕一组公共的示例数据以提供一致性。这些示例数据由书籍详细资料组成。表
1显示了这些数据,你可以参考它来理解我们的例子。
表
1 本书中用到的示例数据
Title / Author
|
Category
|
Subject
|
A Modern Art of Education
Rudoif Steiner
|
/education/pedagogy
|
education philosophy
psychology practice Waldorf
|
Imperial Secrets of Health
and Logevity
Bob Flaws
|
/health/alternative/Chinese
|
diet chinese medicine qi
gong health herbs
|
Tao Te Ching 道德經
Stephen Mitchell
|
/philosophy/eastern
|
taoism
|
Gödel, Escher, Bach:
an Eternal Golden Braid
Douglas Hofstadter
|
/technology/computers/ai
|
artificial intelligence number theory mathematics music
|
Mindstorms
Seymour Papert
|
/technology/computers/programming/eduction
|
children computers powerful
ideas LOGO eduction
|
Java Development with Ant
Erik Hatcher,
Steve Loughran
|
/technology/computers/programming
|
apache jakarta ant build tool
junit java development
|
JUnit in Action
Vincent Massol, Ted Husted
|
/technology/computers/programming
|
junit unit testing mock
objects
|
Lucene in Action
Otis Gospodnetic,
Erik Hatcher
|
/technology/computers/programming
|
lucene search
|
Extreme Programming
Explained
Kent Beck
|
/technology/computers/programming/methodology
|
extreme programming agile
test driven development
methodology
|
Tapestry in Action
Howard Lewis-Ship
|
/technology/computers/programming
|
tapestry web user interface
components
|
The Pragmatic Programmer
Dave Thomas, Andy Hunt
|
/technology/computers/programming
|
pragmatic agile methodology
developer tools
|
除了表中所显示的数据字段,还有
ISBN、URL和出版日期。种类和标题字段是我们主观值,但是另一些都是有关这些书的真实客观值。
代码约定和下载
列表和正文中的代码都以等宽字体的形式出现以与普通文本区分。在正文中
Java方法名称通常都不包含完整声明。
<link href="new_page_1.files/filelist.xml" rel="File-List"><style type="text/css"> <!-- p.MsoNormal {mso-style-parent:""; margin-bottom:.0001pt; text-align:justify; text-justify:inter-ideograph; font-size:10.5pt;"Times New Roman"; margin-left:0cm; margin-right:0cm; margin-top:0cm} table.MsoTableGrid {border:1.0pt solid windowtext; text-align:justify; text-justify:inter-ideograph; font-size:10.0pt;"Times New Roman"; } --></style><style type="text/css"> v\:* { behavior: url(#default#VML) } o\:* { behavior: url(#default#VML) } .shape { behavior: url(#default#VML) }</style>
第一部分
Lucene核心
本书的前半部分覆盖了“盒子外的”
(嗯…,JAR外的)Lucene。你将以整体的观点接触Lucene并开发一个完整的索引和搜索程序。每个后续的章节系统地深入研究特定的内容。“索引”数据和文档和后来的对它们的“搜索”是使用Lucene的第一步。在从索引过程返回后,“分析”将使你深入理解在使用Lucene索引文本时发生了什么。搜索是Lucene真正的亮点:本部分以仅使用内建特征的“高级搜索”技术结束,“扩展搜索”显示了Lucene针对定制目的的扩展性。
第一章接触
Lucene
本章包括
n
理解
Lucene
n
使用基本的索引
API
n
使用搜索
API
n
考虑可替换产品
Lucene流行和成功的一个关键因素是它的简单。
… …
1.1 信息组织和访问的演化
1.2 理解
Lucene
不同的人使用不同的方法解决相同的问题
—即信息超负荷问题。一些人使用新的用户接口来工作,一些使用智能代理,还有一些使用发展较为成熟的搜索工具如Lucene。本章稍后我们展示代码示例之前,我们将提供给你一张高层次的图来说明Lucene是什么,它不是什么和它以后会变得怎样。
1.2.1 Lucene是什么
Lucene是一个高性能、可伸缩的信息搜索
(IR)库。它使你可以为你的应用程序添加索引和搜索能力。Lucene是用java实现的成熟的、免费的开源项目,是著名的Apache Jakarta大家庭的一员,并且基于在Apache软件许可 [ASF, License]。同样,
Lucene是当前与近几年内非常流行的免费的Java信息搜索(IR)库。
注意 贯穿这本书,我们将使用术语
IR(Information Retrieval)来描述像Lucene这样的搜索工具。人们常常将IR库归诸于搜索引擎,但是一定不要将IR库与web搜索引擎混为一谈。
正如你马上就会发现的,
Lucene提供了一组简单却足够强大的核心API,只需要最小限度地理解全文索引和搜索。你只须学习它的几个类从而把Lucene集成到一个应用程序中。因为Lucene是一个Java库,它并不限定要索引和搜索的内容,这使得它比其它一些搜索程序更具有优势。
刚接触
Lucene的人经常把它误解为一个现成的程序,类似文件搜索程序或web网络爬行器或是一个网站的搜索引擎。那些都不是Lucene:Lucene是一个软件库,一个开发工具包(如果你想这样称呼),而不是一个具有完整特征的搜索应用程序。它本身只关注文本的索引和搜索,并且这些事它完成的非常好。Lucene使得你的应用程序只针对它的问题域来处理业务规则,而把复杂的索引和搜索实现隐藏在一组简单易用的API之后。你可以把Lucene认为成一层,应用程序位于它之上,如图1.5所示。
图
1.5 一个集成Lucene的典型应用
大量基于
Lucene的完整的搜索程序已经构建出来。如果你正在寻找预创建的东西或是一个抓取、文档处理和搜索的框架,请参考Lucene Wiki 的“powered by”页
(http://wiki.apache.org/jakarta-lucene/PoweredBy)以获得更多选择:Zilverling、SearchBlox、Nutch、LARM和jSearch,还有其它一部分的命名。Nutch和SearchBlox的案例研究包含在第10章。
1.2.2 Lucene能做什么
Lucene使你可以为你的应用程序添加索引和搜索能力
(这些功能将在1.3节中描述)。Lucene可以索引并能使得可以转换成文本格式的任何数据能够被搜索。在图1.5可以看出,Lucene并不关心数据的来源、格式甚至它的语言,只要你能将它转换为文本。这就意味着你可经索引并搜索存放于文件中的数据:在远程服务器上的web页面,存于本地文件系统的文档,简单的文本文件,微软Word文档,HTML或PDF文件或任何其它能够提取出文本信息的格式。
同样,利用
Lucene你可以索引存放于数据库中的数据,提供给用户很多数据库没有提供的全文搜索的能力。一旦你集成了Lucene,你的应用程序的用户就能够像这样来搜索:+George +Rice –eat –pudding, Apple –pie +Tiger, animal:monkey AND food:banana等等。利用Lucene,你可以索引和搜索
email邮件,邮件列表档案,即时聊天记录,你的Wiki页面……等等更多。
1.2.3 Lucene的历史
Lucene最初是由
Doug Cutting开发的,在SourceForge的网站上提供下载。在2001年9月做为高质量的开源Java产品加入到Apache软件基金会的Jakarta家族中。随着每个版本的发布,这个项目得到明显的增强,也吸线了更多的用户和开发人员。2004年7月,Lucene1.4版正式发布,10月的1.4.2版本做了一次bug修正。表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月
|
复合索引格式,查询分析器增加,远程搜索,token定位,可扩展的API
|
1.4
|
2004年7月
|
Sorting, span queries, term vectors
|
1.4.1
|
2004年8月
|
排序性能的bug修正
|
1.4.2
|
2004年10月
|
IndexSearcher optimization and misc. fixes
|
1.4.3
|
2004年冬
|
Misc. fixes
|
注意 Lucene的创建者,
Doug Cutting,在信息搜索领域有很强的理论和实践经验。他发表过许多IR主题相关的研究论文并曾在Excite、Apple和Grand Central等公司工作。最近,考虑到web搜索引擎数目的减少和这个领域的潜在垄断,他创建了Nutch,第一个开源的万维网搜索引擎(http://www.nutch.org),它用来处理抓取、索引和搜索数十亿时常更新的网页。毫不奇怪,Lucene是Nutch的核心,10.1节包括Nutch如何使用Lucene的案例研究。
Doug Cutting 仍然是
Lucene的后台主力,但是自从Lucene加入到Apache Jakarta的庇护之后,更多的聪明智慧注入进来。在本书写作时,Lucene的核心工作组有数个积极的开发者,其中两位就是本书的作者。除了官方的项目开发人员,Lucene拥有大量积极的技术用户群,经常贡献补丁,Bug修复和新的特征。
1.2.4 谁在使用
Lucene
谁不使用呢?除了在
Lucene Wiki的Powered by Lucene页提到的那些组织外,还有大量的知名的跨图组织正在使用Lucene。它为Eclipse IDE、Encyclopedia Britannica CD-ROM/DVD、FedEx、Mayo Clinic、Hewlett-Packard、New Scientist杂志、Epiphany、MIT的OpenCourseware和Dspace、Akamai的EdgeComputing平台等等提供搜索能力。你的名字也将会出现在这个列表中。
1.2.5 Lucene其它版本:
Perl, Python, C++, .NET, Ruby
判断一个开源软件是否成功的一种方法是通过它被改编为其它语言版本的数量。使用这个标准,
Lucene是非常成功的!尽管开始时Lucene是用Java写的,Lucene已经有很多其它语言的版本了:Perl,Python,C++和.NET,并且一些基础已经用Ruby实现了。这对于那些需要访问用不同的语言写成的应用程序所得到的Lucene索引的开发者来说是个好消息。在第9章你将了解更多关于这方面的东西。
1.3 索引和搜索
所有搜索引擎的核心就是索引的概念:将原始数据处理成一个高效的交差引用的查找结构以便于快速的搜索。让我们对索引和搜索过程做一次快速的高层次的浏览。
1.3.1 什么是索引,为什么它很重要?
想像一下,你需要搜索大量的文件,并且你想找出包含一个指定的词或短语的文件。你如何编写一个程序来做到这个?一个幼稚的方法是针对给定的词或短语顺序扫描每个文件。这个方法有很多缺点,最明显的就是它不适合于大量的文件或者文件非常巨大的情况。这时就出现了索引:为了快速搜索大量的文本,你必须首先索引那个文本然后把它转化为一个可以让你快速搜索的格式,除去缓慢的顺序地扫描过程。这个转化过程称为索引,它的输出称为一条索引。
你可以把索引理解为一个可以让你快速随机访问存于其内部的词的数据结构。它隐含的概念类似于一本书最后的索引,可以让你快速找到讨论指定主题的页面。在
Lucene中,一个索引是一个精心设计的数据结构,在文件系统中存储为一组索引文件。我们在附录B中详细地说明了索引文件的结构,但是目前你只须认为Lucene的索引是一个能快速的词汇查找的工具。
1.3.2 什么是搜索?
搜索是在一个索引中查找单词来找出它们所出现的文档的过程。一个搜索的质量用精确度和召回率来描述。召回率衡量搜索系统搜索到相关文档的能力,精确度衡量系统过滤不相关文档的能力。然而,在考虑搜索时你必须考虑其它一些因素。我们已经提到速度和快速搜索大量文本的能力。支持单个和多个词汇的查询,短语查询,通配符,结果分级和排序也是很重要的,在输入这些查询的时候也是友好的语法。
Lucene强大的软件库提供了大量的搜索特征、bells和whistles,所以我们不得不把关于搜索的讨论展开为三章(第3、5、6章)。
1.4 Lucene实战:一个简单的程序
让我们来实战
Lucene。首先回忆在1.3.1节描述的索引和搜索文件的问题。此外,假设你要索引和搜索存放于一个目录树中的文件,并不只在一个目录中。为了向你展示Lucene的索引和检索能力,我们将用到两个命令行程序:Indexer和Searcher。首先我们将索引一个包含文本文件的目录树,然后我们搜索创建的索引。
这个示例程序将使你熟悉
Lucene的API,简单易用而功能强大。代码清单是完整的,现成的命令行程序。如果文件索引/搜索是你要解决的问题,那你可复制一份代码,用它来适应你的需要。在接下来的章节中,我们将更深入的描述Lucene使用中的每个方面。
在我们可以利用
Lucene搜索之前,需要创建一个索引,所以我们开始Indexer程序。
1.4.1 创建一个索引
在本节中,你将看到一个名为
Indexer的类和它的四个静态方法。它们共同递归遍历文件系统目录并索引所有具有.txt扩展名的文件。当Indexer执行完毕时,为它的后续Searcher(在1.4.2小节中介绍)留下一个创建好的Lucene索引。
我们不期望你熟悉例子中用到的几个
Lucene类和方法,我们马上就会解释它们。在有注释的代码列表之后,我们向你展示了如何使用Indexer。如果你感觉在看到编码之前学习Indexer如何使用很有帮助,直接跳到代码后面的用法讨论部分。
使用Indexer来索引文本文件
列表
1.1展示了Indexer命令行程序。它用到两个参数:
n
我们存放
Lucene索引的路径
n
包含我们要索引的文本文件的路径
列表 1.1 Indexer:遍历文件系统并且索引.txt文件
/**
* This code was originally written for
* Erik’s Lucene intro java.net article
*/
public class Indexer {
public static void main(String[] args) throws Exception {
if (args.length != 2) {
throw new Exception(“Usage: java ” + Indexer.class.getName()
+ “ <index dir> <data dir>
”);
}
File indexDir = new File(args[0]);
File dataDir = new File(args[1]);
long start = new Data().getTime();
int numIndexed = index(indexDir, dataDir);
long end = new Date().getTime();
System.out.println(“Indexing ” + numIndexed + “ files took ”
+ (end - start) + “ milliseconds”);
}
// open an index and start file directory traversal
public static int index(File indexDir, File dataDir)
throws IOException {
if (!dataDir.exists() || !dataDir.isDirectory()) {
throw new IOException(dataDir
+ “ does not exist or is not a directory”);
}
IndexWriter writer = new IndexWriter(indexDir,
① 创建Lucene索引
new StandardAnalyzer(), true);
writer.setUseCompoundFile(false);
indexDirectory(writer, dataDir);
int numIndexed = writer.docCount();
writer.optimize();
writer.close();
return numIndexed;
}
// recursive method that calls itself when it finds a directory
private static void indexDirectory(IndexWriter writer, File dir)
throws IOException {
File[] files = dir.listFiles();
for (int i = 0; i < files.length; i++) {
File f = files
;
if (f.isDirectory()) {
indexDirectory(writer, f);
② 递归
} else if (f.getName().endsWith(“.txt”)) {
indexFile(writer, f);
}
}
}
// method to actually index file using Lucene
private static void indexFile(IndexWriter writer, File f)
throws IOException {
if (f.isHidden() || !f.exists() || !f.canRead()) {
return;
}
System.out.println(“Indexing ” + f.getCanonicalPath());
Document doc = new Document();
doc.add(Field.Text(“contents”, new FileReader(f)));
③ 索引文件
内容
doc.add(Field.Keyword(“filename”, f.getCannicalPath()));
④ 索引
文件名称
writer.addDocument(doc);
⑤ 添加片段到Lucene索引
}
}
有趣的是,代码的大部分是执行目录的遍历(
②)。只有IndexWriter的创建和关闭(
①)和IndexFile方法中的四行(
③,
④,
⑤)使用了Lucene API—有效的6行代码。
这个示例只关注.txt扩展名的文本文件是为了在说明Lucene的用法和强大功能时保持尽量简单。在第7章,我们将向你展示如何处理非文本文件,并且我们开发了一个现成的小框架来分析和索引几种常见的格式的文档。
运行Indexer
在命令行中,我们针对包含Lucene本身的源文件的本地工作目录运行Indexer。我们使Indexer索引/lucene目录下的文件并将Lucene 索引保存在build/index目录中。
% java lia.meetlucene.Indexer build/index /lucene
Indexing /lucene/build/test/TestDoc/test.txt
Indexing /lucene/build/test/TestDoc/test2.txt
Indexing /lucene/BUILD.txt
Indexing /lucene/CHANGES.txt
Indexing /lucene/LICENSE.txt
Indexing /lucene/README.txt
Indexing /lucene/src/jsp/README.txt
Indexing /lucene/src/test/org/apache/lucene/analysis/ru/
→
stemsUnicode.txt
Indexing /lucene/src/test/org/apache/lucene/analysis/ru/
→
test1251.txt
Indexing /lucene/src/test/org/apache/lucene/analysis/ru/
→
testKOI8.txt
Indexing /lucene/src/test/org/apache/lucene/analysis/ru/
→
testUnicode.txt
Indexing /lucene/src/test/org/apache/lucene/analysis/rn/
→
wordsUnicode.txt
Indexing /lucene/todo.txt
Indexing 13 files took 2205 milliseconds
Indexer打印出索引的文件名称,你可以看出它只索引扩展名为.txt的文本文件。
注意 如果你在Windows平台的命令行中运行这个程序,你需要调整命令行的目录和路径分割符。Windows命令行是java build\index c:\lucene。
当索引完成后,Indexer输出它索引的文件数目和所花费的时间。因为报告的时间包含文件目录遍历和索引,你不能把它做为一个正式的性能衡量依据。在我们的示例中,每个索引的文件都很小,但只有了2秒索引这些文件还是不错的。
索引速度是要关注的,我们将在第2章中讨论它。但是通常,搜索更加重要。
1.4.2 在索引中搜索
在Lucene中搜索和索引一样高效和简单。它的功能惊人地强大,在第3章和第5章你将看到。现在,让我们看一下Searcher,一个我们用来搜索Indexer创建的索引的命令行程序。(记住我们的Seacher只是用来示范Lucene的搜索API的用法。你的搜索程序也可以是网页或带有GUI的桌面程序或EJB等形式。)
在上一部分,我们索引了一个目录中的文本文件。在本例中的索引,放在文件系统的一个目录中。我们让Indexer在build/index目录中创建Lucene索引,这个目录和我们调用Indexer的目录相关。在列表1.1中看出,这个索引包含被索引的文件和它们的绝对路径。现在我们要用Lucene来搜索这个索引以找出包含指定文本片段的文件。例如,我们可能想找出包含关键字java或Lucene的所有文件,或者可能想找出包含短语“system requirements”的所有文件。
使用Searcher实现搜索
Searcher程序和Indexer相辅相成并提供命令行搜索的能力。列表1.2展示了Searcher的全部代码。它接受两个命令行参数:
n
Indexer创建的索引的路径
n
搜索索引的查询
列表
1.2 Searcher:为参数传来的查询搜索Lucene索引
/**
* This code was originally written for
* Erik’s Lucene intro java.net article
*/
public class Searcher {
public static void main(String[] args) throws Exception {
if (args.length != 2) {
throw new Exception(“Usage: java ” + Searcher.class.getName()
+ “ <index dir> <auery>”);
}
File indexDir = new File(args[0]);
String q = args[1];
if (!indexDir.exists() || !indexDir.isDirectory()) {
throw new Exception(indexDir +
“ does not exist or is not a directory.”);
}
search(indexDir, q);
}
public static void search(File indexDir, String q)
throws Exception {
Directory fsDir = FSDirectory.getDirectory(indexDir, false);
IndexSearcher is = new IndexSearcher(fsDir);
① 打开索引
Query query = QueryParser.parse(q,
“contents”, ② 分析查询
new StandardAnalyzer());
long start = new Date().getTime();
Hits hits = is.search(query);
③ 搜索索引
long end = new Date().getTime();
System.err.println(“Found ” + hits.length() +
“ document(s) (in ” + (end - start) +
“ milliseconds) that matched query ‘” +
q + “’:”);
for (int i = 0; i < hits.length(); i++) {
Document doc = hits.doc(i); ④ 得到匹配的文档
System.out.println(doc.get(“filename”));
}
}
}
Searcher类似于
Indexer,只有几行代码与Lucene相关。在search方法中出现了几种特别的事物,
① 我们使用
Lucene的IndexSearcher和FSDirectory类来打开我们的索引以进行搜索。
② 我们使用
QueryParser来把human-readable查询分析成Lucene的查询类。
③ 搜索以一个
Hits对象的形式返回结果集。
④ 注意
Hits对象包含的仅仅是隐含的文档的引用。换句话说,不是在搜索的时候立即加载,而是采用从索引中惰性加载的方式—仅当调用hits.doc(int)时。
运行Searcher
让我们运行
Searcher并用‘lucene’查询在索引中找出几个文档:
%java lia.meetlucene.Searcher build/index ‘lucene’
Found 6 document(s) (in 66 milliseconds) that matched query ‘lucene’:
/lucene/README.txt
/lucene/src/jsp/README.txt
/lucene/BUILD.txt
/lucene/todo.txt
/lucene/LICENSE.txt
/lucene/CHANGES.txt
输出显示我们用
Indexer索引的13个文档中的6个含有lucene这个单词,而且这次搜索花费66毫秒。因为Indexer在索引中存放了文件的绝对路径,Searcher可以输出它们。在这个例子中,我们决定把文件和路径存为一个字段并没有考虑什么,但是以Lucene的观点,可以给要索引的文档附加任意元数据。
当然,你可以使用更多复杂的查询,例如‘lucene AND doug’或者‘lucene AND NOT slow’或‘+lucene +book’等等。第
3、5和第6章所有搜索的不同方面,包括Lucene的查询语法。
使用xargs工具
Searcher类对
Lucene的搜索特征的非常简化的示例。所以,它仅仅把匹配结果输出到标准输出上。然而,Searcher还有另一个技巧。考虑你需要找出含有指定的关键词或短语的文件,并且你想以某种方式处理这些匹配文件。为了保持简单性,让我们考虑你想使用UNIX命令ls列出每个匹配的文件,或许看看该文件的大小、许可位或拥有者。既然已经简单地把匹配文档的路径写到标准输出上,又把统计输出写到了标准错误上,你可以利用UNIX的xargs工具来处理匹配文件,如下:
%java lia.meetlucene.Searcher build/index
→
‘lucene AND NOT slow’ | xargs ls –l
Found 6 document(s) (in 131 milliseconds) that
→
matched query ‘lucene AND NOT slow’:
-rw-r--r-- 1 erik staff 4215 10 Sep 21:51 /lucene/BUILD.txt
-rw-r--r-- 1 erik staff 17889 28 Dec 10:53 /lucene/CHANGES.txt
-rw-r--r-- 1 erik staff 2670 4 Nov 2001 /lucene/LICENSE.txt
-rw-r--r-- 1 erik staff 683 4 Nov 2001 /lucene/README.txt
-rw-r--r-- 1 erik staff 370 26 Jan 2002 /lucene/src/jsp/
→
README.txt
-rw-r--r-- 1 erik staff 943 18 Sep 21:27 /lucene/todo.txt
在这个例子中,我们选择布尔查询‘lucene AND NOT slow’来找出所有含有单词lucene但不含有单词
slow的文件。这个查询花费了131毫秒并找出6个匹配文件。我们把Searcher的输入传递给xargs命令,它将会依次使用ls –l命令来列出每个匹配的文件。与之类似,这些匹配文件可以被复制、连接、email或打印到标准输出。
我们的索引和搜索应用程序示例展示了
Lucene的优点。它的API使用简洁。代码的大小(并且这适用于所有使用Lucene的程序)与业务目的密切相关--在这个示例中,是Indexer中负责寻找文本文件的文件系统爬行器和Searcher中打印某一查询的匹配文件名称到标准输出的代码。但是别让这个事实或者这个示例的简单让你觉得自满:在Lucene的表面下有相当多的事情发生,而且我们还用到了一些最好的实践经验。为了更好的使用Lucene,更深入地理解它如何工作及如何在需要的时候扩展它是很重要的。本书作者毫无保留地给出了这些。
1.5 理解核心索引类
在
Indexer类中可见,你需要以下类来执行这个简单的索引过程:
n
IndexWriter
n
Directory
n
Analyzer
n
Document
n
Field
接下来是对这些类的一个简短的浏览,针对它们在