开发源代码全文检索技术Lucene(科生毕业论文)


中国人民大学本科生毕业论文 第 1 页 共 27 页 目 录 前 言····································································································································································· 2 第一节 全文检索系统与 Lucene 简介······················································································································ 3 一、 什么是全文检索与全文检索系统?··································································································· 3 二、 什么是 Lucene? ································································································································4 三、 Lucene 的应用、特点及优势············································································································· 4 四、 本文的重点问题与 cLucene 项目 ······································································································ 5 第二节 Lucene 系统结构分析 ·································································································································· 5 一、 系统结构组织······································································································································ 5 二、 数据流分析·········································································································································· 6 三、 基于 Lucene 的应用开发···················································································································· 8 第三节 Lucene 索引文件格式分析··························································································································· 9 一、 Lucene 源码实现分析的说明············································································································· 9 二、 Lucene 索引文件格式······················································································································· 10 三、 一些公用的基础类···························································································································· 12 四、 存储抽象············································································································································ 13 五、 关于 cLucene 项目···························································································································· 15 第四节 Lucene 索引构建逻辑模块分析················································································································· 15 一、 绪论 ··················································································································································· 15 二、 对象体系与 UML 图························································································································· 16 1. 项(Term) ··································································································································· 16 2. 域(Field)···································································································································· 17 3. 文档(document) ························································································································ 18 4. 段(segment)······························································································································· 19 5. IndexReader 类与 IndexWirter 类·································································································· 23 三、 数据流逻辑········································································································································ 24 四、 关于 cLucene 项目···························································································································· 25 结论··········································································································································································· 26 中国人民大学本科生毕业论文 第 2 页 共 27 页 开放源代码的全文检索引擎 Lucene ――介绍、系统结构与源码实现分析 前 言 2003 年 2 月份起,分别来自中国人民大学信息学院 99 级本科计算机科学技术、数学与应用数学和信息 管理与信息系统专业的 6 名同学,在方美琪老师的引荐下,奔赴深圳香港盈科动公司 Intelligent Business Technology(HK) ltd 进行毕业项目。我们一行 6 人,分别是李宇(数学系,本文作者)、龚丽娟(数学系)、 邓钦(计算机系)、吴俊杰(计算机系)、郑伟博(信息系)和蔡奕栋(信息系),所开展的毕业设计项目分别 是全文检索系统 Lucene 的分析与 cLucene 系统的实现(李宇、吴俊杰、郑伟博)和新闻领域中英文自动摘要 系统的实现(邓钦、龚丽娟、蔡奕栋)。本文是作者所进行的毕业设计项目的工作总结。 作者所在的项目组完成了完整的分析开放源代码的全文检索引擎 Lucene 的系统分析与源码分析工作,并 且在分析的基础上用 C++语言对 Lucene 系统做了重新实现,形成 cLucene 系统。作者与同在项目组的同学一 起,完成了分析与实现的大量工作,编写了所有 cLucene 系统的源代码。同时,因为语言背景的原因,我们 还翻译了大量的与 Lucene 有关的英文资料。 但是时间紧迫,Lucene 系统本身也具有复杂性,并且由于采用了边分块分析,边实现 cLucene 的工作方 式,我们针对 Lucene 1.2 版本的源码分析工作并没有全部完成,只完成了索引文件格式分析、系统分析和索 引核心的源代码分析,因此本文的涉及范围也相应只覆盖到这些内容。 经过小组成员之间的不懈努力,作者才能将我们的工作成果以本文的形式展现出来,在这里首先需要感 谢的是与作者一起奋斗过的小组成员。在项目进行的过程中,来自香港理工大学的硕士研究生罗伟东、本文 的指导老师方美琪教授,还有香港理工大学的陈文中博士,均给予了极大的帮助,这里一并致谢。最后,还 需要感谢盈科动公司,为我们在深圳提供了住宿条件和试验环境。 谨以此文献给所有帮助指导过我们的人! 中国人民大学本科生毕业论文 第 3 页 共 27 页 第一节 全文检索系统与 Lucene 简介 一、 什么是全文检索与全文检索系统? 全文检索是指计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章 中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用 户的检索方式。这个过程类似于通过字典中的检索字表查字的过程。 全文检索的方法主要分为按字检索和按词检索两种。按字检索是指对于文章中的每一个字都建立索引, 检索时将词分解为字的组合。对于各种不同的语言而言,字有不同的含义,比如英文中字与词实际上是合一 的,而中文中字与词有很大分别。按词检索指对文章中的词,即语义单位建立索引,检索时按词检索,并且 可以处理同义项等。英文等西方文字由于按照空白切分词,因此实现上与按字处理类似,添加同义处理也很 容易。中文等东方文字则需要切分字词,以达到按词索引的目的,关于这方面的问题,是当前全文检索技术 尤其是中文全文检索技术中的难点,在此不做详述。 全文检索系统是按照全文检索理论建立起来的用于提供全文检索服务的软件系统。一般来说,全文检索 需要具备建立索引和提供查询的基本功能,此外现代的全文检索系统还需要具有方便的用户接口、面向 WWW[1]的开发接口、二次应用开发接口等等。功能上,全文检索系统核心具有建立索引、处理查询返回结果 集、增加索引、优化索引结构等等功能,外围则由各种不同应用具有的功能组成。结构上,全文检索系统核 心具有索引引擎、查询引擎、文本分析引擎、对外接口等等,加上各种外围应用系统等等共同构成了全文检 索系统。图 1.1 展示了上述全文检索系统的结构与功能。 在上图中,我们看到:全文检索系统中最为关键的部分是全文检索引擎,各种应用程序都需要建立在这 磁盘索引文 件 索引引擎 文本分析引擎查询引擎 核 心 开 发 接 口 图 1.1 全文检索系统结构 检 索引 擎 传统应用程序 Web 应用程序 其它应用 全文检索系统 中国人民大学本科生毕业论文 第 4 页 共 27 页 个引擎之上。一个全文检索应用的优异程度,根本上由全文检索引擎来决定。因此提升全文检索引擎的效率 即是我们提升全文检索应用的根本。另一个方面,一个优异的全文检索引擎,在做到效率优化的同时,还需 要具有开放的体系结构,以方便程序员对整个系统进行优化改造,或者是添加原有系统没有的功能。比如在 当今多语言处理的环境下,有时需要给全文检索系统添加处理某种语言或者文本格式的功能,比如在英文系 统中添加中文处理功能,在纯文本系统中添加 XML[2]或者 HTML[3]格式的文本处理功能,系统的开放性和扩 充性就十分的重要。 二、 什么是 Lucene? Lucene 是 apache 软件基金会[4] jakarta 项目组的一个子项目,是一个开放源代码[5]的全文检索引擎工具包, 即它不是一个完整的全文检索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,部 分文本分析引擎(英文与德文两种西方语言)。Lucene 的目的是为软件开发人员提供一个简单易用的工具包, 以方便的在目标系统中实现全文检索的功能,或者是以此为基础建立起完整的全文检索引擎。 Lucene 的原作者是 Doug Cutting,他是一位资深全文索引/检索专家,曾经是 V-Twin 搜索引擎[6]的主要开 发者,后在 Excite[7]担任高级系统架构设计师,目前从事于一些 Internet 底层架构的研究。早先发布在作者自 己的 www.lucene.com,后来发布在 SourceForge[8],2001 年年底成为 apache 软件基金会 jakarta 的一个子项目: http://jakarta.apache.org/lucene/。 三、 Lucene 的应用、特点及优势 作为一个开放源代码项目,Lucene 从问世之后,引发了开放源代码社群的巨大反响,程序员们不仅使用 它构建具体的全文检索应用,而且将之集成到各种系统软件中去,以及构建 Web 应用,甚至某些商业软件也 采用了 Lucene 作为其内部全文检索子系统的核心。apache 软件基金会的网站使用了 Lucene 作为全文检索的 引擎,IBM 的开源软件 eclipse[9]的 2.1 版本中也采用了 Lucene 作为帮助子系统的全文索引引擎,相应的 IBM 的商业软件 Web Sphere[10]中也采用了 Lucene。Lucene 以其开放源代码的特性、优异的索引结构、良好的系统 架构获得了越来越多的应用。 Lucene 作为一个全文检索引擎,其具有如下突出的优点: (1)索引文件格式独立于应用平台。Lucene 定义了一套以 8 位字节为基础的索引文件格式,使得兼容系 统或者不同平台的应用能够共享建立的索引文件。 (2)在传统全文检索引擎的倒排索引的基础上,实现了分块索引,能够针对新的文件建立小文件索引, 提升索引速度。然后通过与原有索引的合并,达到优化的目的。 (3)优秀的面向对象的系统架构,使得对于 Lucene 扩展的学习难度降低,方便扩充新功能。 (4)设计了独立于语言和文件格式的文本分析接口,索引器通过接受 Token 流完成索引文件的创立,用 户扩展新的语言和文件格式,只需要实现文本分析的接口。 (5)已经默认实现了一套强大的查询引擎,用户无需自己编写代码即使系统可获得强大的查询能力, Lucene 的查询实现中默认实现了布尔操作、模糊查询(Fuzzy Search[11])、分组查询等等。 面对已经存在的商业全文检索引擎,Lucene 也具有相当的优势。首先,它的开发源代码发行方式(遵守 Apache Software License[12]),在此基础上程序员不仅仅可以充分的利用 Lucene 所提供的强大功能,而且可以 深入细致的学习到全文检索引擎制作技术和面相对象编程的实践,进而在此基础上根据应用的实际情况编写 出更好的更适合当前应用的全文检索引擎。在这一点上,商业软件的灵活性远远不及 Lucene。其次,Lucene 秉承了开放源代码一贯的架构优良的优势,设计了一个合理而极具扩充能力的面向对象架构,程序员可以在 Lucene 的基础上扩充各种功能,比如扩充中文处理能力,从文本扩充到 HTML、PDF[13]等等文本格式的处理, 编写这些扩展的功能不仅仅不复杂,而且由于 Lucene 恰当合理的对系统设备做了程序上的抽象,扩展的功能 中国人民大学本科生毕业论文 第 5 页 共 27 页 也能轻易的达到跨平台的能力。最后,转移到 apache 软件基金会后,借助于 apache 软件基金会的网络平台, 程序员可以方便的和开发者、其它程序员交流,促成资源的共享,甚至直接获得已经编写完备的扩充功能。 最后,虽然 Lucene 使用 Java 语言写成,但是开放源代码社区的程序员正在不懈的将之使用各种传统语言实现 (例如.net framework[14]),在遵守 Lucene 索引文件格式的基础上,使得 Lucene 能够运行在各种各样的平台上, 系统管理员可以根据当前的平台适合的语言来合理的选择。 四、 本文的重点问题与 cLucene 项目 作为中国人民大学信息学院 99 级本科生的一个毕业设计项目,我们对 Lucene 进行了深入的研究,包括 系统的结构,索引文件结构,各个部分的实现等等。并且我们启动了 cLucene 项目,做为一个 Lucene 的 C++ 语言的重新实现,以期望带来更快的速度和更加广泛的应用范围。我们先分析了系统结构,文件结构,然后 在研究各个部分的具体实现的同时开始进行的 cLucene 实现。限于时间的限制,到本文完成为止,cLucene 项 目并没有完成,对于 Lucene 的具体实现部分也仅仅完成到了索引引擎部分。 接下来的部分,本文将对 Lucene 的系统结构、文件结构、索引引擎部分做一个彻底的分析。以期望提供 对 Lucene 全文检索引擎的系统架构和部分程序实现的清晰的了解。cLucene 项目则作为一个开放源代码的项 目,继续进行的开发。 有关 cLucene 项目的一些信息: „ 开发语言:ISO C++[15],STLport 4.5.3[16],OpenTop 1.1[17] „ 目标平台:Win32,POSIX „ 授权协议:GNU General Public License (GPL)[18] 第二节 Lucene 系统结构分析 一、 系统结构组织 Lucene 作为一个优秀的全文检索引擎,其系统结构具有强烈的面向对象特征。首先是定义了一个与平台 无关的索引文件格式,其次通过抽象将系统的核心组成部分设计为抽象类,具体的平台实现部分设计为抽象 类的实现,此外与具体平台相关的部分比如文件存储也封装为类,经过层层的面向对象式的处理,最终达成 了一个低耦合高效率,容易二次开发的检索引擎系统。 以下将讨论 Lucene 系统的结构组织,并给出系统结构与源码组织图: 中国人民大学本科生毕业论文 第 6 页 共 27 页 从图中我们清楚的看到,Lucene 的系统由基础结构封装、索引核心、对外接口三大部分组成。其中直接 操作索引文件的索引核心又是系统的重点。Lucene的将所有源码分为了7个模块(在java语言中以包即package 来表示),各个模块所属的系统部分也如上图所示。需要说明的是 org.apache.lucene.queryPaser 是做为 org.apache.lucene.search 的语法解析器存在,不被系统之外实际调用,因此这里没有当作对外接口看待,而是 将之独立出来。 从面象对象的观点来考察,Lucene 应用了最基本的一条程序设计准则:引入额外的抽象层以降低耦合性。 首先,引入对索引文件的操作 org.apache.lucene.store 的封装,然后将索引部分的实现建立在 (org.apache.lucene.index)其之上,完成对索引核心的抽象。在索引核心的基础上开始设计对外的接口 org.apache.lucene.search 与 org.apache.lucene.analysis。在每一个局部细节上,比如某些常用的数据结构与算法 上,Lucene 也充分的应用了这一条准则。在高度的面向对象理论的支撑下,使得 Lucene 的实现容易理解,易 于扩展。 Lucene在系统结构上的另一个特点表现为其引入了传统的客户端服务器结构以外的的应用结构。Lucene 可以作为一个运行库被包含进入应用本身中去,而不是做为一个单独的索引服务器存在。这自然和 Lucene 开 放源代码的特征分不开,但是也体现了 Lucene 在编写上的本来意图:提供一个全文索引引擎的架构,而不是 实现。 二、 数据流分析 理解 Lucene 系统结构的另一个方式是去探讨其中数据流的走向,并以此摸清楚 Lucene 系统内部的调用 时序。在此基础上,我们能够更加深入的理解 Lucene 的系统结构组织,以方便以后在 Lucene 系统上的开发 工作。这部分的分析,是深入 Lucene 系统的钥匙,也是进行重写的基础。 我们来看看在 Lucene 系统中的主要的数据流以及它们之间的关系图: org.apache.lucene.store org.apache.lucene.index org.apache.lucene.analysis org.apache.lucene.search org.apache.lucene.queryPaser org.apache.lucene.document org.apache.lucene.util 索引文件 被索引文件 查询语句 查询结果 基础结构封装 图 2.1 系统结构与源码组织结构图 索引核 心 对外接 口 中国人民大学本科生毕业论文 第 7 页 共 27 页 图 2.2 很好的表明了 Lucene 在内部的数据流组织情况,并且沿着数据流的方向我们也可以对与 Lucene 内部的执行时序有一个清楚的了解。现在将图中的涉及到的流的类型与各个逻辑对应系统的相关部分的关系 说明一下。 图中共存在 4 种数据流,分别是文本流、token 流、字节流与查询语句对象流。文本流表示了对于索引目 标和交互控制的抽象,即用文本流表示了将要索引的文件,用文本流向用户输出信息;在实际的实现中,Lucene 中的文本流采用了 UCS-2[19]作为编码,以达到适应多种语言文字的处理的目的。Token 流是 Lucene 内部所使 用的概念,是对传统文字中的词的概念的抽象,也是 Lucene 在建立索引时直接处理的最小单位;简单的讲 Token 就是一个词和所在域值的组合,后面在叙述文件格式时也将继续涉及到 token,这里不详细展开。字节 流则是对文件抽象的直接操作的体现,通过固定长度的字节(Lucene 定义为 8 比特位长,后面文件格式将详 细叙述)流的处理,将文件操作解脱出来,也做到了与平台文件系统的无关性。查询语句对象流则是仅仅在 查询语句解析时用到的概念,它对查询语句抽象,通过类的继承结构反映查询语句的结构,将之传送到查找 逻辑来进行查找的操作。 图中的涉及到了多种逻辑,基本上直接对应于系统某一模块,但是也有跨模块调用的问题发生,这是因 为 Lucene 的重用程度非常好,因此很多实现直接调用了以前的工作成果,这在某种程度上其实是加强了模块 耦合性,但是也是为了避免系统的过于庞大和不必要的重复设计的一种折衷体现。词法分析逻辑对应于 org.apache.lucene.analysis 部分。查询语句语法分析逻辑对应于 org.apache.lucene.queryParser 部分,并且调用了 org.apache.lucene.analysis 的代码。查询结束之后向评分排序逻辑输出 token 流,继而由评分排序逻辑处理之后 给出文本流的结果,这一部分的实现也包含在了 org.apache.lucene.search 中。索引构建逻辑对应于 org.apache.lucene.index 部分。索引查找逻辑则主要是 org.apache.lucene.search,但是也大量的使用了 ⑴ - 文本流 ⑵ - token 流 ⑶ - 字节流 ⑷ - 查询语句对象流 ⑴ ⑴ ⑴ ⑵ ⑵ ⑶ ⑶ ⑶ ⑷ 图 2.2 数据流图 评分排 序逻辑 ⑵ 被索引文件 词法分 析逻辑 索引文件 索引构建 逻辑 存储抽象 查询语句 查询结果 查询语句语 法分析逻辑 索引查 找逻辑 中国人民大学本科生毕业论文 第 8 页 共 27 页 org.apache.lucene.index 部分的代码和接口定义。存储抽象对应于 org.apache.lucene.store。没有提到的模块则是 做为系统公共基础设施存在。 三、 基于 Lucene 的应用开发 通过以上的系统结构分析和数据流分析,我们已经很清楚的了解了 Lucene 的系统的结构特征。在此基础 上,我们可以通过扩充 Lucene 系统来完成一个完备的全文检索引擎,紧接着还可以在全文检索引擎的基础上 构建各种应用系统。鉴于本文的目的并不在此,以下我们只是略为叙述一下相关的步骤,从而给出应用开发 的一些思路。 首先,我们需要的是按照目标语言的词法结构来构建相应的词法分析逻辑,实现 Lucene 在 org.apache.lucene.analysis 中定义的接口,为 Lucene 提供目标系统所使用的语言处理能力。Lucene 默认的已经 实现了英文和德文的简单词法分析逻辑(按照空格分词,并去除常用的语法词,如英语中的 is,am,are 等等)。 在这里,主要需要参考实现的接口在 org.apache.lucene.analysis 中的 Analyzer.java 和 Tokenizer.java 中定义, Lucene 提供了很多英文规范的实现样本,也可以做为实现时候的参考资料。其次,需要按照被索引的文件的 格式来提供相应的文本分析逻辑,这里是指除开词法分析之外的部分,比如 HTML 文件,通常需要把其中的 内容按照所属于域分门别类加入索引,这就需要从 org.apache.lucene.document 中定义的类 document 继承,定 义自己的 HTMLDocument 类,然后就可以将之交给 org.apache.lucene.index 模块来写入索引文件。完成了这两 步之后,Lucene 全文检索引擎就基本上完备了。这个过程可以用下图表示: 当然,上面所示的仅仅只是对于 Lucene 的基本扩充过程,它将 Lucene 由不完备的变成完备的(尤其是 对于非英语的语言检索)。除此之外我们还可以在很多方面对 Lucene 进行改造。第一个方面即为按照文档索 引的域,比如标题,作者之类的信息对返回的查询结果排序,这即需要改造 Lucene 的评分排序逻辑。默认的, Lucene 采用其内部的相关性方法来处理评分和排序,我们可以根据需要改变它。遗憾的是,这部分 Lucene 并没有做到如同扩充词法解析和文档类型那样的条理清晰,没有留下很好的接口,因此需要仔细的分析其源 代码的实现,自行扩充等等。其他的方面,比如改进其索引的效率,改进其返回结果时候的缓冲机制等等, 都是加强 Lucene 系统的方面,在此也不再叙述。 org.apache.lucene.analysis org.apache.lucene.document 用户自定义的 analyzer 用户自定义的 document 类型 ……. Lucene 继承 实现接口 完备的 Lucene 图 2.3 Lucene 扩充过程 中国人民大学本科生毕业论文 第 9 页 共 27 页 完成了 Lucene 系统,之后就可以开始考虑其上的应用系统开发。如果应用系统也使用 java 语言开发,那 么 Lucene 系统能够方便的嵌入到整个系统中去,作为一个 API 集来调用。这个过程十分简单,以下便是一个 示例程序,配合注释理解起来很容易。 或者,Lucene 全文检索引擎也可作为服务器程序启动,但是这就需要用户自行扩充其他应用与 Lucene 的接口。这个可以通过传统的包装方式,比如客户服务器结构,或者采用现在流行的 Web 方式。诸如此类的 应用方案,本文也不再继续叙述。参考 Lucene 的项目网站中的用户邮件列表能找到更多的信息。 第三节 Lucene 索引文件格式分析 一、 Lucene 源码实现分析的说明 通过以上对 Lucene 系统结构的分析,我们已经大致的清楚了 Lucene 系统的组成,以及在 Lucene 系统之 上的开发步骤。接下来,我们试图来分析 Lucene 项目(采用 Lucene 1.2 版本)的源码实现,考察其实现的细 public class IndexFiles { //使用方法:: IndexFiles [索引输出目录] [索引的文件列表] ... public static void main(String[] args) throws Exception { String indexPath = args[0]; IndexWriter writer; //用指定的语言分析器构造一个新的写索引器(第 3 个参数表示是否为追加索引) writer = new IndexWriter(indexPath, new SimpleAnalyzer(), false); for (int i=1; i值对的能力,通过这个接口就可以获 得某个项(Term)在某个文档中出现的频数。TermPositions 则是在 TermDocs 上的扩展,将项(Term)在文 档中的位置信息也表示出来。TermDocs(TermPositions)接口的使用方式类似于 java 中的 Enumration 接口, 即通过 next 方法跳转,通过 doc,freq 等方法获得当前的属性值。 2. 域(Field) 由于 Field 的基本概念在 org.apache.lucene.document 中已经做了定义,因此在这部分主要是针对项文件 (.fnm 文件、.fdx 文件、.fdt 文件)所需要的信息再来设计一些类。 FieldInfo name : String isIndexed : boolean number : int FieldInfo() 图 4.3 UML 图(三) 图 4.3 中展示的,就是表示与域(Field)所关联的属性信息的类。其中 isIndexed 表示的这个域的值是否 被索引过,即值是否被分词然后索引;另外两个属性所表示的意思则很明显:一个是域的名字,一个是域的 编号。 接下来我们来看关于域表和存取逻辑的 UML 图。 中国人民大学本科生毕业论文 第 18 页 共 27 页 FieldsReader FieldsReader() close() size() doc() FieldInfos byNumber : java.util.Vector = new Vector () byName : java.util.Hashtable = new Hashtable () FieldInfos() FieldInfos() add() add() add() addInternal() fieldNumber() fieldInfo() fieldName() fieldInfo() size() write() write() read() -fieldInfos FieldsWriter FieldsWriter() close() addDocument() -fieldInfos 图 4.4 UML 图(四) FieldInfos 即为域表的概念表示,内部采用了冗余的方式以获取在通过域的编号访问或者通过域的名字来 访问时候的高效率。FieldsReader 与 FieldsWriter 则分别是写出和读入的代理类。在功能和实现上,这两个类 都比较简单。至于 FieldInfos 中采用的冗余方式,则是基于域的数目相对比较少而做出的一种折衷处理。 3. 文档(document) 文档(document)同样也是在 org.apache.lucene.document 中定义过的结构。由于对于这部分比较重要,我 们也来看看其 UML 图。 Field name : String = "body" stringValue : String = null isStored : boolean = false isIndexed : boolean = true isTokenized : boolean = true Keyword() UnIndexed() Text() UnStored() Text() name() stringValue() readerValue() Field() Field() isStored() isIndexed() isTokenized() toString() DocumentFieldEnumeration DocumentFieldEnumeration() hasMoreElements() nextElement() Document Document() add() getField() get() fields() toString() DocumentFieldList DocumentFieldList() field next fields fieldList 图 4.5 UML 图(五) 在图 4.5 中我们看到,Document 的设计基本上沿用了链表的处理方法。左边的 Document 类作为一个数 中国人民大学本科生毕业论文 第 19 页 共 27 页 据外包类,用来提供对于内部结构 DocumentFieldList 的增加删除访问操作等等。DocumentFieldList 才是实际 上的数据存储单位,它用了链表的处理方法,直接指向一个当前的 Field 对象和下一个 DocumentFieldList 对 象,这个与前面的类似。为了能够逐个访问链表中的节点,还设计了 DocumentFieldEnumeration 枚举类。 DocumentWriter maxFieldLength : int postingTable : java.util.Hashtable = new Hashtable () fieldLengths[] : int DocumentWriter() addDocument() invertDocument() addPosition() sortPostingTable() quickSort() writePostings() writeNorms() 图 4.6 UML 图(六) 实际上定义于 org.apache.lucene.index 中的有关于 Document 的就是永久化的代理类。在图 4.6 中给出了其 UML 图。需要说明的是为什么没有出现读入的方法:这个方法已经隐含在图 4.5 中 Document 类中的 add 方 法中了,结合图 2.4 中的程序代码段,我们就能够清楚的理解这种设计。 4. 段(segment) 段(Segment)这一部分设计的比较特殊,在实现简单的对象结构之上,还特意的设计了用于段之间合并 的类。接下来,我们仍然采取对照 UML 分析的方式逐个叙述。接下来我们看 Lucene 中如何表示段这个概念。 SegmentInfo name : String docCount : int dir : Directory SegmentInfo() SegmentInfos counter : int = 0 info() read() write() Vector (from util ) 图 4.7 UML 图(七) Lucene 定义了一个类 SegmentInfo 用来表示每一个段(Segment)的信息,包括名字(name)、含有的文 档的数目(docCount)和段所位于的目录的位置(dir)。根据索引文件中的段的意义,有了这三点,就能唯一 确定一个段了。SegmentInfos 这个类则是用来表示一个段的链表(从标准的 java.util.Vector 继承而来),实际 上,也就是索引(index)的意思了。需要注意的是,这里并没有在 SegmentInfo 中安插一个文档(document) 的链表。这样做的原因牵涉到 Lucene 内部对于文档(相当于一个被索引文件)的处理;Lucene 内部采用了赋 予文档编号,给域赋值的方式来处理文档,即加入的文档顺次编号,以后用文档号表示文档,而路径信息, 文件名字等等在以后索引查找需要的属性,都作为域存储下来;因此 SegmentInfo 中并没有另外存储一个文档 (document)的链表,对于这些的写出和读入,则交给了永久化的代理类来做。 中国人民大学本科生毕业论文 第 20 页 共 27 页 SegmentReader closeDirectory : boolean = false segment : String deletedDocsDirty : boolean = false norms : java.util.Hashtable = new Hashtable... SegmentReader() SegmentReader() doClose() hasDeletions() doDelete() files() terms() terms() document() isDeleted() termDocs() termPositions() docFreq() numDocs() maxDoc() norms() norms() normStream() openNorms() closeNorms() SegmentsReader starts[] : int normsCache : java.util.Hashtable = new Hashtable ... maxDoc : int = 0 numDocs : int = - 1 SegmentsReader() numDocs() maxDoc() document() isDeleted() doDelete() readerIndex() norms() terms() terms() docFreq() termDocs() termPositions() doClose() #readers[] 图 4.8 UML 图(八) 图 4.8 给出了负责段(segment)的读入操作的代理类,而负责段(segment)的写出操作也同样没有定义, 这些操作都直接实现在了类 IndexWriter 类中(后面会详细分析)。段的操作同样采用了之前的数组或者说是 缓冲的处理方式,相关的细节也不在这里详细叙述了。 然后,针对前面项(term)那部分定义的几个接口,段(segment)这部分也需要做相应的接口实现,因 为提供直接遍历访问段中的各个项的能力对于检索来说,无疑是十分重要的。即这部分的设计,实际上都是 在为了检索在服务。 TermEnum next() term() docFreq() close() SegmentsTermEnum SegmentsTermEnum() next() term() docFreq() close() SegmentTermEnum SegmentTermEnum() clone() seek() next() readTerm() growBuffer() term() termInfo() termInfo() docFreq() freqPointer() proxPointer() close() 图 4.9 UML 图(九) 中国人民大学本科生毕业论文 第 21 页 共 27 页 TermDocs seek() doc() freq() next() read() skipTo() close() TermPositions nextPosition() SegmentTermDocs SegmentTermDocs() seek() seek() close() doc() freq() skippingDoc() next() read() skipTo() SegmentsTermDocs SegmentsTermDocs() doc() freq() seek() next() read() skipTo() term Docs() term Docs() close() -segTermDocs[] #current SegmentsTermPositions SegmentsTermPositions() term Docs() nextPosition() SegmentTermPositions SegmentTermPositions() seek() close() nextPosition() skippingDoc() next() read() 图 4.10 UML 图(十) 图 4.9 和图 4.10 分别展示了前面项(term)那里定义的接口是如何在这里通过继承实现的。Lucene 在处 理这部分的时候,也是分成两部分(Segment 与 Segments 开头的类)来实现,而且很合理的运用了数组的技 法,以及注意了继承重用。但是细化到局部,终归是比较简单的按照语义来获得结果而已了,因此关于更多 的也就不多做分析了,我们完全可以通过阅读源代码来解决。 接下来所介绍的,就是在 Lucene 的设计过程中比较特殊的一个部分:段合并类(SegmentMerger)。这首 先需要介绍 Lucene 中的建立索引时的段合并策略。 Lucene 为了兼顾建立索引时的效率和读取索引查找的速度,引入了分小段建立索引的方式,即每一次批 量建立索引时,先在内存中的虚拟文件系统中为每一个文档单独建立一个段,然后在输出的时候将这些段合 并之后输出成为索引文件,这时仅仅存在一个段。多次建立的索引后,如果想优化索引文件,也可采取合并 中国人民大学本科生毕业论文 第 22 页 共 27 页 段的方法,将索引中的段合并成为一个段。我们来看一下在 IndexWriter 类中相应的方法的实现,来了解一下 这中建立索引的实现。 对于上面的代码,我们不做过多注释了,结合源码中的注解应该很容易理解。在最后那个 mergeSegments 函数中,将用到几个重要的类结构,它们记录了合并时候的一些重要信息,完成合并时候的工作。接下来, 我们来看这几个类的 UML 图。 /** Adds a document to this index.*/ public final void addDocument(Document doc) throws IOException { DocumentWriter dw = new DocumentWriter(ramDirectory, analyzer, maxFieldLength); String segmentName = newSegmentName(); dw.addDocument(segmentName, doc); synchronized (this) { //这里即为为每一个文档建立内存虚拟文件系统中的段 segmentInfos.addElement(new SegmentInfo(segmentName, 1, ramDirectory)); //然后紧接着就开始合并 maybeMergeSegments(); } } //这里是为新段产生名字函数,段和文档一样,也是用编号来标识的 private final synchronized String newSegmentName() { return "_" + Integer.toString(segmentInfos.counter++, Character.MAX_RADIX); } …………… //以下代码段即为优化(合并段)索引的操作实现 /** Merges all segments together into a single segment, optimizing an index for search. */ public final synchronized void optimize() throws IOException { flushRamSegments(); while (segmentInfos.size() > 1 || (segmentInfos.size() == 1 && (SegmentReader.hasDeletions(segmentInfos.info(0)) || segmentInfos.info(0).dir != directory))) { int minSegment = segmentInfos.size() - mergeFactor; //合并段开始,这个函数比较复杂,不在文章中列出了,请阅读源码 mergeSegments(minSegment < 0 ? 0 : minSegment); } } 图 4.11 IndexWriter 代码节选 中国人民大学本科生毕业论文 第 23 页 共 27 页 SegmentMergeInfo base : int docMap[] : int = null term : Term termEnum : SegmentTermEnum reader : SegmentReader postings : SegmentTermPositions SegmentMergeInfo() next() close() SegmentMergeQueue SegmentMergeQueue() lessThan() close() SegmentMerger segment : String readers : java.util.Vector = new Vector () SegmentMerger() add() segmentReader() merge() mergeFields() mergeTerms() mergeTermInfos() mergeTermInfo() appendPostings() mergeNorms() -queue PriorityQueue heap[] : Object size : int lessThan() initialize() put() top() pop() adjustTop() size() clear() upHeap() downHeap() (from util) 图 4.12 UML 图(十一) 从图 4.12 中,我们看到 Lucene 设计一个类 SegmentMergeInfo 用来保存每一个被合并的段的信息,也保 存能够访问其内部的接口句柄,也就是说合并时的操作使用这个类作为对被合并的段的操作代理。类 SegmentMergeQueue 则设计为 org.apache.lucene.util.PriorityQueue 的子类,做为 SegmentMergeInfo 的容器类, 而且附带能够自动排序。SegmentMerger 是主要进行操作的类,里面各个方法环环相扣,分别完成合并各个数 据项的问题。 5. IndexReader 类与 IndexWirter 类 最后剩下的,就是整个索引逻辑部分的使用接口类了。外界通过这两个类以及文档(document)类的构造 函数调用之,比如图 2.4 中的代码示例所示。下面我们来看一下这部分最后两个类的 UML 图。 中国人民大学本科生毕业论文 第 24 页 共 27 页 IndexReader IndexReader() open() open() open() lastModified() lastModified() lastModified() indexExists() indexExists() indexExists() numDocs() maxDoc() document() isDeleted() norms() terms() terms() docFreq() termDocs() termDocs() termPositions() termPositions() delete() doDelete() delete() close() doClose() finalize() isLocked() isLocked() unlock() IndexWriter maxFieldLength : int = 10000 mergeFactor : int = 10 maxMergeDocs : int = Integer.MAX_VALUE IndexWriter() IndexWriter() IndexWriter() close() finalize() docCount() addDocument() newSegmentName() optimize() addIndexes() flushRamSegments() maybeMergeSegments() mergeSegments() deleteSegments() deleteFiles() deleteFiles() readDeleteableFiles() writeDeleteableFiles() 图 4.13 UML 图(十二) IndexWriter 的设计与 IndexReader 的设计很不相同,前者是一个实现类,而后者是一个抽象类,带有没有 实现的接口。IndexWriter 的主要作用就是接收新加入的文档(document),然后在内部为之生成相应的小段, 最后再合并并向索引文件中输出,图 4.11 中已经给出了一些实现的代码。由于 Lucene 在面向对象上封装的努 力,通过各个构造函数就已经完成了对于各个概念的构造过程,剩下部分的代码主要是依据各个数组或者是 链表中的信息,逐个逐个的将信息写出到相应的文件中去了。IndexReader 部分则只是做了接口设计,没有具 体的实现,这个和本部分所完成的主要功能有关:索引构建逻辑。设计这个抽象类的目的是,预先完成一些 函数,为以后的检索(search)部分的各种形式的 IndexReader 铺平道路,也是利用了在同一个包内可以方便 访问其它类的保护变量这个 java 语言的限制。 到此,在索引构建逻辑部分出现的类我们就分析完毕了,需要说明主要是做的一个宏观上的组成结构上 的分析,并指出一些实现上的要点。具体的实现,由于 Lucene 的开放源码而显得并不是非常的重要,因为 Lucene 在做到良好的面相对象设计之后,实际带来的是局部复杂性的减小,因此某一些单独的函数或者实现 就比较容易编写,也容易让人阅读。本文不再继续叙述这方面的细节,作为一个总结,下一个部分我们通过 索引构建逻辑的数据流图的方式,再来理清楚一下索引构建逻辑这部分的调用时序。 三、 数据流逻辑 中国人民大学本科生毕业论文 第 25 页 共 27 页 从宏观上明白一个系统的设计,理清楚其中的运行规律,最好的方式应该是通过数据流图。在分析了各 个位于索引构建逻辑部分的类的设计之后,我们接下来就通过分析数据流图的方式来总结一下。但是由于之 前提到的原因:索引读入部分在这一部分并没有完全实现,所以我们在数据流图中主要给出的是索引构建的 数据流图。 对于图 4.14 中所描述的内容,结合 Lucene 源代码中的一些文件看,能够加深理解。准备阶段可以参考 demo 文件夹中的 org.apache.lucene.demo.IndexFiles 类和 java 文件夹中的 org.apache.lucene.document 文件包。 索引构建阶段的主要源码位于 java 文件夹中 org.apache.lucene.index.IndexWriter 类,因此这部分可以结合这个 类的实现来看。至于内存文件系统,比较复杂,但是这时的逻辑相对简单,因此也不难理解。 上面的数据流图十分清楚的勾画除了整个索引构建逻辑这部分的设计:通过层层嵌套的类结构,在构建 时候即分步骤有计划的生成了索引结构,将之存储到内存中的文件系统中,然后通过对内存中的文件系统优 化合并输出到实际的文件系统中。 四、 关于 cLucene 项目 前面的三个部分,已经完成了分析索引构建逻辑的任务,这里我们还是有针对性的谈谈我们这次的毕业 设计项目 cLucene 在这一部分的情况。 在实现这部分的时候,为了将一些 java 语法中比较特殊的部分,比如内隐类、同步函数、同步对象等等, 我们不得不采用了一些比较晦涩和艰深的 C++语法,在 OpenTop 这个类库所提供的类似于 java 语言的设施上 来实现。这个尤其体现在实现 Segment 相关类时,为了处理原来 java 源代码中用内隐类实现的 Lock 文件创建 机制的时候,我们不得不定义了大量的 cLucene::store::With 的子类,并为之传入调用类的指针,设置它为调 准备阶段 被索引文件 通过 java 语言的 io 类以输入流方式传入 生成 document 对象,调用 add 方法加入 field 对象 生成 field 对象,根据对象性质不同,为值赋予 String 值,或者是 Reader 值 调用 索引构建阶段 以 document 对象方式传入 加入 document 对象 索引文件 addDocument 生成小段 invertDocument 分析文档 sortPostingTable 排序位置信息 writePostings 写出索引信息 writeNorms 写出标准化因子 内存文件系统 顺次调用 字节流输入 合并输出 图 4.14 索引构建部分的数据流逻辑 中国人民大学本科生毕业论文 第 26 页 共 27 页 用类的友元,才得以精确的模拟了原有的语义。陷于我们这次的重写以移植为主,系统结构基本上没有大的 变化,不得不产生这种重复而且大量的工作。如果需要改进这中状况,我们应该考虑按照 C++语言的特点来 设计索引构建部分的类库继承结构,但是很可惜在本文成文之前,时间不允许我们这样做。 来自 java 语法的特殊性只是我们解决问题的一个方面,我们还需要处理引用的调用方式。由于 java 语言 拥有了垃圾收集机制,因此得以将一切的参数形式看作为引用,而不考虑其分配与消亡的问题。C++语言并 不具备这种机制,它需要程序员自行管理分配空间与销毁对象的问题。在这里,我们使用的是来自 OpenTop 中所引入的计数指针 RefPtr<>模板,它能够模拟指针的语义,并且计算指针被引用的次数,在引用次数为 0 时就自动释放资源:这是一种类似于 java 语言中引用的方式,不过它显得更加高效率。我们在 cLucene 的实 现中大量的使用了计数指针模板。 除此之外,我们没有改变 Lucene 所定义的索引构建逻辑的结构和语义,我们实现的是一个完全和 java 版本 Lucene 兼容的版本。 结论 本文深入细致的分析了开放源代码全文检索系统 Lucene 的系统结构、索引文件格式和源码的部分实现, 介绍了在 Lucene 系统下面的应用和一个用 C++语言重写的全文检索系统 cLucene。全文通过图表和文字说明 结合的方式,理清了 Lucene 的系统结构关系,揭示了其运行时的数据流程,总结了其索引文件格式,并且从 有助于理解源代码的角度宏观的分析了索引核心部分的源代码实现。 在分析 Lucene 系统的同时,本文作者及同在项目组的同学共同进行了 cLucene 系统的开发工作,以 C++ 语言实现了一个兼容 Lucene 索引文件格式的新的全文检索系统。这个系统的目的旨在通过 C++语言使之能够 在更多范围内应用以及提高其运行的效率。由于时间的关系,这个项目并没有完成,还需要继续的开发。 注解: [1] WWW :Word Wide Web 的缩写词,但是此处指应用程序具有被 web 访问的能力。 [2] XML :Extensible Markup Language(可扩展标记语言)的缩写,这里指一种文件的存储结构,关于 XML 的更多信息见 http://www.w3c.org/xml [3] HTML :Hypertext Markup Language(超文本标记语言)的缩写,这里也是指一种文件的存储结构,关于 HTML 的更多信 息见 http://www.w3c.org/MarkUp [4] apache 软件基金会 :著名的开放源代码组织,开发了 Apache Web 服务器、Ant 编译工具等等著名程序,jakarta 项目组是其 下面一个大的子项目组,主要用 java 语言开发开放源代码软件,更多信息可见 http://www.apache.org, http://jakarta.apache.org [5] 开放源代码 :英文为 Open Source,自 70 年代开始由自由软件基金会(Free Software Foundation)倡导而迅速流行于互联网 络的软件开发和授权方式,最大的特点是在用户遵守一定协议的情况下软件源代码开放,更多信息可见 http://www.opensource.org/ [6] V-Twin 搜索引擎 :Apple 的 Copland 操作系统的成就之一,作为 Apple 操作系统内建的检索引擎存在 [7] Excite :这里指搜索引擎提供商 Excite 公司,互联网上的最早的搜索引擎之一,更多信息可见 http://www.excite.com/ [8] SourceForge : 互联网上最大的网上开发平台,提供 CVS 等各种开发者服务,是大量开放源代码软件的发布地,更多信息 可见 http://www.sourcefoege.net [9] eclipse :IBM 公司所主创地一个开放源代码的集成开发工具,用 java 实现,在 2.1 版本中开始使用 Lucene 作为索引引擎, 中国人民大学本科生毕业论文 第 27 页 共 27 页 更多信息可见 http://www.eclipse.org [10] Web Sphere :IBM 公司的软件开发套件,主要是基于 eclipse 改进而来 [11] Fuzzy Search :模糊查询,指可以按照相似度或者各种转义字符查找,比如查找“roam~”就可能查找到 foam 或者 roams [12] Apache Software License :apache 软件基金会所制定的用户协议,基本要点是软件开放源代码,保留 apache 软件基金会在 源码中的声明,但是不强制要求修改过的软件也开放源代码。更多信息可见 http://www.opensource.org/licenses/apachepl.php [13] PDF :可移植文档格式文件,由 Adobe 公司提出,已经成为标准的文档交换格式。 [14] .net framework :微软公司所推出的软件开发平台,面向下一代的 Web 开发与应用程序开发,与 java 有一定的相似之处。 更多信息可见 http://www.microsoft.com/net/ [15] ISO C++ :经过国际标准化组织(ISO)所标准化之后的 C++语言版本,这里指开发语言只使用标准化之后的特性 [16] STLport 4.5.3 :C++语言的标准模板库(STL)的一个实现版本,由 SGI STL 版本改进而来,特点是能够适应多个平台与 多个编译器。更多信息可见 http://www.stlport.org [17] OpenTop 1.1 :一套 C++语言库,提供了类似于 java 语言的多种封装,比如文件系统,容器等等。更多信息可见 http://www.sourceforge.net/projects/opentop [18] GNU General Public License (GPL) :一个软件许可协议,特点是不仅仅开放源代码,而且要求修改者也开放源代码。更多 信息可见 http://www.opensource.org/licenses/gpl-license.php [19] UCS-2 :Unicode 编码方式的一种,对于文字使用定长的两字节编码,可以最多容纳 65535 个字符的编码方案。更多信息 可见 http://www.unicode.org/ [20] UTF-8 : Unicode 编码方式的一种,特点是采用不定长字节编码,适合做为文件存储的编码方案。更多信息可见 http://www.unicode.org [21] 倒排索引结构 :经典索引结构的一种,特点是存储的是字串与所在位置信息的一个配对,刚好与文章中位置与字串的关 系相反,是一种普遍采用得建立索引方式。 [22] UML :Unified Modeling Language(统一建模语言)的缩写,其定义了一套共通的软件建模的标准语言,用于描述软件的 构造、模型等等。更多信息可见 http://www.uml.org/ [23] 工厂模式(Factory parttern) :设计模式的一种,主要思想是用静态方法或者工厂类封装类的初始化程序,以获得更多的 灵活性。 参考文献: 1. 车东,《在应用中加入全文检索功能 — 基于 Java 的全文索引引擎 Lucene 简介》,http://www.chedong.com/tech/lucene.html 2. Brian Goetz,《The Lucene search engine: Powerful, flexible, and free》, http://www.javaworld.com/javaworld/jw-09-2000/jw-0915-lucene.html 3. Apache 软件基金会 Lucne 项目文档,《Index File Formats》,http://jakarta.apache.org/lucene/docs/fileformats.html 4. Bruce Exkel 著,侯捷译,《Java 编程思想(第二版)》,机械工业出版社(北京),2002,ISBN-7-111-10441-2 5. Erich Gamma,Richard Helm,Ralph Johnson,John Vlissides 著,李英军,马晓星等译,《设计模式 可复用面相对象软件的 基础》,机械工业出版社(北京),2000,ISBN 7-111-07575-7
还剩26页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 5 金币 [ 分享pdf获得金币 ] 0 人已下载

下载pdf