《开发自己的搜索引擎》读书笔记——索引的建立

来源:互联网 发布:此谓知本 此谓知之至也 编辑:程序博客网 时间:2024/05/17 06:10
  1. Lucene的Document

    Document的意义为文档,在Lucene中,它代表一种逻辑文件。Lucene本身无法对物理文件建立索引,而只能识别并处理Document类型的文件。在某些时候可以将一个Document与一个物理文件进行对应,用一个Document来代替一个物理文件,然而更多的时候,Document和物理的文件没有关系,它作为一种数据源的集合,向Lucene提供原始的要索引的文本内容。Lucene会从Document取出相关的数据源内容,并根据属性配置进行相应的处理。

    按这样的方式,也可以从不同的物理文件来提取数据源,放入同一个Document中。

    不仅如此,由于Document只是负责收集数据源,因此,甚至也可以不使用物理文件来构建一个Document,一段文本、几个数字、甚至是一些链接都可以作为构建Document的数据源。只要将它们加入到Document对象中,Lucene就可以为这些数据源建立索引,并且使用户查找到它们。

  2. 为Document添加多种Field

    对于一个Document来说,该如何表示其所汇集的数据源呢?其实,在Lucene中,数据源是由一个被称为Field的类来表示的。我们可以把Field理解为字段

    通常情况下,可以直接通过Field的构建函数来创建一个Field类型的对象。这个Field类型主要是用来标识当前的数据源的各种属性,存储来自数据源的数据内容。Lucene在对每个Field进行处理时,会充分考虑到数据源的各种属性,以便做出不同的处理。

    事实上,这种Document-Field结构与关系数据库有异曲同工之处。在关系数据库中一个表(table)可以看成Lucene中的索引,表中的每一个记录,就是Lucene中的Document,而表中的每一个字段,也就是Lucene中的Field。表中的每一个字段有各种属性,可以是字符型,可以是数字型,这也正如Field一样,具有各种属性。

  3. 上面讲到的数据源的各种属性,其实是指以下几种:

    是否存储(该数据源的数据是否要完整地存储于索引中);

    是否索引(该数据源的数据是否要在用户检索时被检索,如果一个数据源没有被索引,用户是无法在该数据源上进行搜索的);

    是否分词(分词是指对数据源的文本按照某种规则进行切分)。

  4. Document的内部实现

    Document的实现相对简单,它主要起到对Field的信息进行记录和管理的作用,以便Lucene遍历所有的Field信息。在Document内部,Field是保存在一个Vector类型的对象数组中。

    Document的主要作用就是维护它内部的Field信息,包括对Field的增、删、查等功能。

  5. Field的内部实现

    在Lucene2.0版本中,Field内部包含了两个静态的内部类:Store和Index。它们分别表示Field的存储方式和索引方式。

    其中,Store类有3个公有的静态属性:

    Store.NO:表示该Field不需要存储;

    Store.YES:表示该Field需要存储;

    Store.COMPRESS:表示使用压缩方式来保存这个Field的值。

       Index类有四个公有的静态属性:

              Index.NO:表示该Field不需要索引(也就是用户不需要去查找该Field的值);

              Index.TOKENIZED:表示该Field先被分词再被索引;

              Index.UN_TOKENIZED:表示不对该Field进行分词,但是要对它进行索引(也就是该Field会被用户查找);

              Index.NO_NORMS:表示对该Field进行索引,但是不使用Analyzer,同时禁止它参加评分,主要是为了减少内存的消耗。

       通过Store和Index类的组合使用,就可以表示出当前这个Field的所有状态。

  1. Field类的构造方法

    在Lucene2.0版本中,Field类本身提供了5种不同的公有构造方法:

    public Field(String name , Stringvalue,Store store,Index index)

    public Field(String name, Stringvalue,Store stor, Index index,TermVector termVector)

    public Field(String name,Reader reader)

    public Field(String name,Readerreader,TermVector termVector)

    publicField(String name,byte[] value,Store store)

    其中,name参数统一指的是Field的名字。在为Field添加值时具体的方式有三种:

              直接的字符串方式;

              使用Reader从外部传入;

              使用直接的二进制byte传入。

    termVector属性是用于表明是否要对Field的词条向量进行存储,其实就是一个有关词条及出现次数的记录。

7、Lucene的索引工具IndexWriter,其主要作用是对索引进行创建,加入Document,合并索引段,以及控制与索引相关的方方面面,它是Lucene的索引的主要操作者。

8、IndexWriter的三个构造函数:

publicIndexWriter(String path,Analyzer a,boolean create)

publicIndexWriter(File path,Analyzer a,boolean create)

public IndexWriter(Directoryd,Analyzer a,boolean create)

       它们的第一个参数,都是代表索引的存放位置。String类型的是绝对路径,File类型是经过包装的绝对路径,Directory类型是Lucene内部的一种目录表示方式。

       Analyzer是Lucene中很重要的一个工具,主要负责对各种输入的数据源进行分析,包括过滤分词等功能。

       第三个参数是一个boolean型的值,该参数是在由第一个参数所指定的路径处,删除原目录内的所有内容重新构建索引,还是在其中已经存在的索引上追加新的Document。

9、在使用addDocument方法加入所有的Document后,一定要使用IndexWriter的close方法来关闭索引器,使所有在I/O缓存中的数据都写入到磁盘上,关闭各种流。这样才能最终完成索引的建立。如果没有关闭,就会发现索引目录内除了一个segments文件外一无所有。

10


11、Segment

       在每个segment里,有许多的Document,在一个索引中,可能有多个segment。Lucene对索引管理的最大单位就是segment。每个segment内的所有索引文件都具有相同的前缀。

       在一个索引中,只有一个“segments”文件,这个文件没有后缀,它记录着当前的索引内有多少个segments,每个segment中有多少个Document这样的信息。

12

       .fdt文件是主要的保存数据源数据的文件,.fdx文件只是记录下当前Document在.fdt中的位置,以便后面读取时方便。需要注意,在.fdt文件中存储的field的值仅为Document中具有Store.YES属性的field。

13、Lucene的索引部分invertDocument方法负责了调用底层分析器的接口,来对数据源进行分析,并统计词条的位置和频率信息,然后将其存入postingTable中。

       在invertDocument方法中,需要注意:

       (1)、对所有需要加入索引的field进行遍历。对于那些不需要分词的field,就将其整个field的数据作为一个大词条,放入postingTable中。

       (2)、对于需要分词的field,则调用底层分词接口进行分词,然后将每个分出来的词都放入postingTable中去。

14对postingTable进行排序

       所有词条被加入postingTable后,Lucene首先将这个postingTable转化为一个Posting类型的数组,然后对这个数组进行排序,使所有的词条按其字典序排列。那样,就可以将词条信息写入.tii和.tis文件。另外,将频率和位置信息写入.frq和.prx文件中去。(在Lucene中采用了快速排序法对这个Posting的数组进行了排序)。

为什么Lucene要对Posting的数组进行排序呢?

       这里涉及一个索引文件的存储方式的问题。索引是整个Lucene工作的基础,没有索引,搜索引擎也就失去了意义。那么,索引文件的存取和访问效率就成了制约搜索引擎性能的重要参数。通常情况下,索引的存储效率可以比读取效率略差一些,这是因为,索引的建立时间对用户体验并无太大影响,所以,应当创建一种索引格式,使之更有助于加速搜索进程。这种格式应当具有索引内容少、可进行有序查找等特点。

15、将Posting信息写入索引

16索引文件格式

(1)、索引的segment

每个segment代表Lucene的一个完整索引段。通常,在一个索引中,会包含有多个segment。每个segment都有统一的前缀,这个签字追的确定是根据当前索引的Document的数量确立的。前缀名等于Document数量转化成36进制后,在前面加上下划线而组成。

       通常,在一个完整的索引中,有且只有一个“segment”文件,这个文件没有后缀,它记录了当前索引中所有segment的信息。

(2).fnm格式的文件中包含了Document中的所有的field名称(field name)。

(3).fdx和.fdt是综合使用的两个文件,其中.fdt用于存储具有Store.YES属性的field的数据。而.fdx则是一个索引,用于存储Document在.fdt中的位置。

(4).tis文件用于存储分词后的词条(Term),而.tii就是它的索引文件,它标明了每个.tis文件中的词条的位置。

(5)在Lucene的索引中,所有的文档被删除后并不是立刻从索引中取出,而是留待下一次合并索引或是对索引进行优化时才真正删除,这点有点类似于Windows的回收站原理。这种功能是通过deletable文件实现的。所有的文档在被删除后,会首先在deletable文件中留一个记录,要真正删除时,才将索引除去。

(6)在IndexWriter中有一个属性:useCompoundFile,默认值为true,表示是否使用复合索引格式来保存索引。在索引内容非常大,文件数量很多的情况下,系统打开文件将会极大地耗费系统资源。因此Lucene提供了一种单文件索引格式,也就是所谓的复合索引格式。使用复合索引格式存储Document内容时,只需要在初始化完成一个IndexWriter对象后,使用setUseCompoundFile(boolean)方法,将属性值设置为true就可以了。

17索引过程的调优

(1)合并因子mergeFactor

该因子决定segment该如何被addDocument()方法进行合并。该值取较小时,建索引时需要更小的内存,搜索未优化索引的速度会更快,但索引建立的速度会比较慢,适合于间歇性地向索引加入文档。该值较大时,适合于批量索引建立

举一个例子:

将mergeFactor的因子设置为10,那么,每向索引添加10个Document时就会有一个新的segment建立。

当第10个这样的segment建立好后,它们会被合并成一个具有100个Document的新segment。

接下来,每100个Document又会创建一个新的segment,当第999个文档被加入索引时,此时磁盘上应该已经有了9个segment,其中每个都有100个Document,而第901个到999个Document此时正在内存中,还未被写入磁盘中。

倘若此时再向索引中加入一个Document,那么,前9个segment就会和这第10个新创建的segment进行合并,成为一个具有1000个Document的segment。过程一次类推。

(2)、maxMergeDocs

该参数用于实现对mergeFactor参数的限制,表示在一个segment中,最多可以拥有的Document数量。

(3)minMergeDocs

当索引被刷到磁盘中,需要首先保存在内存中,minMergeDocs就是用来限制这个内存中文档数量的。

18索引的合并与索引的优化

(1)、Directory类有两个子类:RAMDirectory(与文件系统的内存有关)和FSDirectory(与文件系统的目录有关)。

       FSDirectory指的是在文件系统中的一个路径。因此,当Lucene向其中写入索引时,会直接写入到磁盘上。而RAMDirectory则是内存中的一个区域,当虚拟机退出后,里面的内容会随之消失。因此需要将RAMDirectory中的内容转到FSDirectory中。

       对于RAMDirectory,只需要简单的使用构造函数就可以生成实例。而FSDirectory的实例则需要使用静态方法来生成,这个静态方法有两个参数,第一个参数为所需要存入索引的文件系统的路径,第二个参数为一个boolean型的参数,表示是否将原目录中的所有内容清空。

(2)、使用IndexWriter来合并索引

Lucene的IndexWriter类提供了一个接口,以合并不同的索引。这并不是合并具体的目录,而是合并不同的Directory型对象。不仅可以将存放于不同文件系统路径下的索引合并,还可以将内存中的索引与文件系统中的索引合并,以此来保存那些存放于RAMDirectory中的索引。

       在合并内存中的索引时,一定要先将相应的IndexWriter关闭,以保证滞留在缓存中的文档被“刷”到RAMDirectory中去,这点与使用FSDirectory时一样,如果不使用close方法关闭IndexWriter,就会发现索引文件并未真正写入目录中去。

(3)、索引的优化

IndexWriter的optimize方法能够对当前IndexWriter所指定的索引目录及其所使用的缓存目录下的所有segment做优化,是所有的segments合并成一个完整的segment,即整个索引目录内只出现一种文件前缀。

19从索引中删除文档

(1)、在Lucene的index包中,有一个很重要的工具IndexReader。它主要负责对索引的各种读取和维护工作。如打开一个索引、取得索引中的某个文档、获取索引中总文档的数量,甚至从索引中删除某个文档。

(2)、使用文档ID号来删除特定文档

方法名IndexReader.deleteDocument(intid)。在Lucene的内部使用类似回收站的机制来管理Document的删除。在每个Document被从索引中删除时,它只相当于被扔进了回收站,并未实际删除,如果不使用reader.close()方法将删除文档的信息写入磁盘,一旦进程退出索引会恢复成原来的状态。

IndexReader的undeleteAll()方法可以实现反删除(相当于回收站的还原)。

只需要使用IndexWriter对索引optimize一次,Lucene就会重新为每一个文档分配ID值,这样,那些被标记为已删除的Document就真正的被物理删除了。(清空回收站)。

(3)、使用Field信息来删除批量文档

IndexReader的deleteDocument()方法是一个能够批量删除索引的方法,它删除索引是按照词条来进行的。Term类是用于表示词条的一个工具,它能够将词条表示成<field,value>对。

20、Lucene的同步问题

(1)在Lucene中,对索引发生修改的类主要集中在IndexWriter(主要负责对索引的写入与索引整体的维护,如合并、优化等操作)和IndexReader(主要负责从索引中删除文档)。

       任一时刻,在系统中只能有一个IndexWriter的实例对索引进行操作,不允许有多个IndexWriter向索引添加Document,或是优化索引、合并segment;

       任一时刻,不能有多个IndexReader在执行文档的删除操作。下一个IndexReader的操作应该在上一个IndexReader的close方法执行未完成后运行;

       在使用IndexWriter向索引加入文档前,必须先关闭执行删除操作的IndexReader实例;

       在使用IndexReader删除前,必须先关闭执行添加Document操作的IndexReader的实例。

(2)Lucene中的锁

       write.lock和commit.lock
21、IndexModifier集成了IndexWriter的大部分功能和IndexReader中的对索引删除的功能。

0 0
原创粉丝点击