zoukankan      html  css  js  c++  java
  • Lucene的分析资料【转】

    Lucene 源码剖析

    1 目录

    2 Lucene是什么

    2.1.1 强大特性

    2.1.2 API组成-

    2.1.3 Hello World!

    2.1.4 Lucene roadmap

    3 索引文件结构

    3.1 索引数据术语和约定 -

    3.1.1 术语定义

    3.1.2 倒排索引(inverted indexing)

    3.1.3 Fields的种类

    3.1.4 片断(segments)

    3.1.5 文档编号(document numbers)

    3.1.6 索引结构概述

    3.1.7 索引文件中定义的数据类型 -

    3.2 索引文件结构

    3.2.1 索引文件概述

    3.2.2 每个Index包含的文件

    3.2.2.1 Segments文件

    3.2.2.2 Lock文件

    3.2.2.3 Deletable文件

    3.2.2.4 Compound文件(.cfs)

    3.2.3 每个Segment包含的文件

    3.2.3.1 Field信息(.fnm)

    3.2.3.2 Field数据(.fdx和.fdt)

    3.2.3.3 Term字典(.tii和.tis)

    3.2.3.4 Term频率数据(.frq)

    3.2.3.5 Positions位置信息数据(.prx)

    3.2.3.6 Norms调节因子文件(.nrm)-

    3.2.3.7 Term向量文件  -

    3.2.3.8 删除的文档 (.del)

    3.3 局限性(Limitations)

    4 索引是如何创建的

    4.1 索引创建示例

    4.2 索引创建类IndexWriter

    4.2.1 org.apache.lucene.index.IndexWriter

    4.2.2 org.apache.lucene.index.DocumentsWriter

    4.2.3 org.apache.lucene.index.SegmentMerger -

    5 数据是如何存储的

    5.1 数据存储类Directory

    5.1.1 org.apache.lucene.store.Directory

    5.1.2 org.apache.lucene.store.FSDirectory

    5.1.3 org.apache.lucene.store.RAMDirectory

    5.1.4 org.apache.lucene.store.IndexInput

    5.1.5 org.apache.lucene.store.IndexOutput

    6 文档内容是如何分析的

    6.1 文档分析类Analyzer

    6.1.1 org.apache.lucene.store.Analyzer

    6.1.2 org.apache.lucene.store.StandardAnalyzer -

    7 如何给文档评分

    7.1 文档评分类Similarity

    7.1.1 org.apache.lucene.search.Similarity

    7.2 Similarity评分公式

    1Lucene是什么

    Apache Lucene是一个高性能(high-performance)的全能的全文检索(full-featured text search engine)的搜索引擎框架库,完全(entirely)使用Java开发。它是一种技术(technology),适合于(suitable for)几乎(nearly)任何一种需要全文检索(full-text search)的应用,特别是跨平台(cross-platform)的应用。

    wpsBEFC.tmp

    Lucene 通过一些简单的接口(simple API)提供了强大的特征(powerful features):

    可扩展的高性能的索引能力(Scalable, High-Performance Indexing

    ü 超过20M/分钟的处理能力(Pentium M 1.5GHz)

    ü 很少的RAM内存需求,只需要1MB heap

    ü 增量索引(incremental indexing)的速度与批量索引(batch indexing)的速度一样快

    ü 索引的大小粗略(roughly)为被索引的文本大小的20-30%

    强大的精确的高效率的检索算法(Powerful, Accurate and Efficient Search Algorithms

    ü 分级检索(ranked searching)能力,最好的结果优先推出在前面

    ü 很多强大的query种类:phrase queries, wildcard queries, proximity queries, range queries等

    ü 支持域检索(fielded searching),如标题、作者、正文等

    ü 支持日期范围检索(date-range searching)

    ü 可以按任意域排序(sorting by any field)

    ü 支持多个索引的检索(multiple-index searching)并合并结果集(merged results)

    ü 允许更新和检索(update and searching)并发进行(simultaneous)

    跨平台解决方案(Cross-Platform Solution

    ü 以Open Source方式提供并遵循Apache License,允许你可以在即包括商业应用也包括Open Source程序中使用Lucene

    ü 100%-pure Java(纯Java实现)

    ü 提供其他开发语言的实现版本并且它们的索引文件是兼容的

    Lucene API被分成(divide into)如下几种包(package)

    · org.apache.lucene.analysis

    定义了一个抽象的Analyser API,用于将text文本从一个java.io.Reader转换成一个TokenStream,即包括一些Tokens的枚举容器(enumeration)。一个TokenStream的组成(compose)是通过在一个Tokenizer的输出的结果上再应用TokenFilters生成的。一些少量的Analysers实现已经提供,包括StopAnalyzer和基于语法(gramar-based)分析的StandardAnalyzer。

    · org.apache.lucene.document

    提供一个简单的Document类,一个document只不过包括一系列的命名了(named)的Fields(域),它们的内容可以是文本(strings)也可以是一个java.io.Reader的实例。

    · org.apache.lucene.index

    提供两个主要类,一个是IndexWriter用于创建索引并添加文档(document),另一个是IndexReader用于访问索引中的数据。

    · org.apache.lucene.search

    提供数据结构(data structures)来呈现(represent)查询(queries):TermQuery用于单个的词(individual words),PhraseQuery用于短语,BooleanQuery用于通过boolean关系组合(combinations)在一起的queries。而抽象的Searcher用于转变queries为命中的结果(hits)。IndexSearcher实现了在一个单独(single)的IndexReader上检索。

    · org.apache.lucene.queryParser

    使用JavaCC实现一个QueryParser。

    · org.apache.lucene.store

    定义了一个抽象的类用于存储呈现的数据(storing persistent data),即Directory(目录),一个收集器(collection)包含了一些命名了的文件(named files),它们通过一个IndexOutput来写入,以及一个IndexInput来读取。提供了两个实现,FSDirectory使用一个文件系统目录来存储文件,而另一个RAMDirectory则实现了将文件当作驻留内存的数据结构(memory-resident data structures)。

    · org.apache.lucene.util

    包含了一小部分有用(handy)的数据结构,如BitVector和PriorityQueue等。

    2Hello World!

    下面是一段简单的代码展示如何使用Lucene来进行索引和检索(使用JUnit来检查结果是否是我们预期的):

    1wpsBF0D.tmp // Store the index in memory:
    2wpsBF0E.tmp    Directory directory = new RAMDirectory();
    3wpsBF1E.tmp // To store an index on disk, use this instead:
    4wpsBF1F.tmp    //Directory directory = FSDirectory.getDirectory(”/tmp/testindex”);
    5wpsBF30.tmp    IndexWriter iwriter = new IndexWriter(directory, analyzer, true);
    6wpsBF31.tmp    iwriter.setMaxFieldLength(25000);
    7wpsBF32.tmp    Document doc = new Document();
    8wpsBF43.tmp    String text = “This is the text to be indexed.“;
    9wpsBF44.tmp    doc.add(new Field(“fieldname“, text, Field.Store.YES,
    10wpsBF54.tmp        Field.Index.TOKENIZED));
    11wpsBF55.tmp    iwriter.addDocument(doc);
    12wpsBF66.tmp    iwriter.optimize();
    13wpsBF67.tmp    iwriter.close();
    14wpsBF68.tmp
    15wpsBF78.tmp // Now search the index:
    16wpsBF79.tmp    IndexSearcher isearcher = new IndexSearcher(directory);
    17wpsBF8A.tmp // Parse a simple query that searches for ”text”:
    18wpsBF8B.tmp    QueryParser parser = new QueryParser(“fieldname“, analyzer);
    19wpsBF9C.tmp    Query query = parser.parse(“text“);
    20wpsBF9D.tmp    Hits hits = isearcher.search(query);
    21wpsBFAD.tmp    assertEquals(1, hits.length());
    22wpsBFAE.tmp // Iterate through the results:
    23wpsBFAF.tmpwpsBFC0.tmp for (int i = 0; i < hits.length(); i++) wpsBFC1.tmp{
    24wpsBFD1.tmp      Document hitDoc = hits.doc(i);
    25wpsBFD2.tmp      assertEquals(“This is the text to be indexed.“, hitDoc.get(“fieldname“));
    26wpsBFE3.tmp    }
    27wpsBFE4.tmp    isearcher.close();
    28wpsBFE5.tmp    directory.close();

    为了使用Lucene,一个应用程序需要做如下几件事:

    1. 通过添加一系列Fields来创建一批Documents对象。

    2. 创建一个IndexWriter对象,并且调用它的AddDocument()方法来添加进Documents。

    3. 调用QueryParser.parse()处理一段文本(string)来建造一个查询(query)对象。

    4. 创建一个IndexReader对象并将查询对象传入到它的search()方法中。

    3Lucene Roadmap

    wpsBFF6.tmp

    Lucene 源码剖析

    2索引文件

    为了使用Lucene来索引数据,首先你得把它转换成一个纯文本(plain-text)tokens的数据流(stream),并通过它创建出Document对象,其包含的Fields成员容纳这些文本数据。一旦你准备好些Document对象,你就可以调用IndexWriter类的addDocument(Document)方法来传递这些对象到Lucene并写入索引中。当你做这些的时候,Lucene首先分析(analyzer)这些数据来使得它们更适合索引。详见《Lucene In Action》

    wpsBFF7.tmp

    下面先了解一下索引结构的一些术语。

    2.1       索引数据术语和约定

    2.1.1    术语定义

    Lucene中基本的概念(fundamental concepts)是index、Document、Field和term。

    1  一条索引(index)包含(contains)了一连串(a sequence of)文档(documents)。

    2  一个文档(document)是由一连串fields组成。

    3  一个field是由一连串命名了(a named sequence of)的terms组成。

    4  一个term是一个string(字符串)。

    相同的字符串(same string)但是在两个不同的fields中被认为(considered)是不同的term。因此(thus)term被描述为(represent as)一对字符串(a pair of strings),第一个string取名(naming)为该field的名字,第二个string取名为包含在该field中的文本(text within the field)。

    2.1.2    倒排索引(inverted indexing)

    索引(index)存储terms的统计数据(statistics about terms),为了使得基于term的检索(term-based search)效率更高(more efficient)。Lucene的索引分成(fall into)被广为熟悉的(known as)索引种类(family of indexex)叫做倒排索引(inverted index)。这是因为它可以列举(list),对一个term来说,所有包含它的文档(documents that contain it)。这与自然关联规则(natural relationship)是相反,即由documents列举它所包含的terms。

    2.1.3    Fields的种类

    在Lucene中,fields可以被存储(stored),在这种情况(in which case)下它们的文本被逐字地(literally)以一种非倒排的方式(in non-inverted manner)存储进index中。那些被倒排的fields(that are inverted)称为(called)被索引(indexed)。一个field可以都被存储(stored)并且被索引(indexed)。

    一个field的文本可以被分解为(be tokenized into)terms以便被索引(indexed),或者field的文本可以被逐字地使用为(used literally as)一个term来被索引(be indexed)。大多数fields被分解(be tokenized),但是有时候对某种唯一性(certain identifier)的field来逐字地索引(be indexed literally)又是非常有用的,如url。

    2.1.4    片断(segments)

    Lucene的索引可以由多个复合的子索引(multiple sub-indexes)或者片断(segments)组成(be composed of)。每一个segment都是一个完全独立的索引(fully independent index),它能够被分离地进行检索(be searched seperately)。索引按如下方式进行演化(evolve):

    1. 为新添加的文档(newly added documents)创建新的片断(segments)。

    2. 合并已存在的片断(merging existing segments)。

    检索可以涉及(involve)多个复合(multiple)的segments,并且/或者多个复合(multiple)的indexes。每一个index潜在地(potentially)包含(composed of)一套(a set of)segments。

    wpsC007.tmp

    2.1.5    文档编号(document numbers)

    在内部(internally),Lucene通过一个整数的(interger)文档编号(document number)来表示文档。第一篇被添加到索引中的文档编号为0(be numbered zero),每一篇随后(subsequent)被添加的document获得一个比前一篇更大的数字(a number one greater than the previous)。

    需要注意的是一篇文档的编号(document’s number)可以更改,所以在Lucene之外(outside of)存储这些编号时需要特别小心(caution should be taken)。详细地说(in particular),编号在如下的情况(following situations)可以更改:

    1  存储在每个segment中的编号仅仅是在所在的segment中是唯一的(unique),在它能够被使用在(be used in)一个更大的上下文(a larger context)中前必须被转变(converted)。标准的技术(standard technique)是给每一个segment分配(allocate)一个范围的值(a range of values),基于该segment所使用的编号的范围(the range of numbers)。为了将一篇文档的编号从一个segment转变为一个扩展的值(an external value),该片断的基础的文档编号(base document number)被添加(is added)。为了将一个扩展的值(external value)转变回一个segment的特定的值(specific value),该segment将该扩展的值所在的范围标识出来(be indentified),并且该segment的基础值(base value)将被减少(substracted)。例如,两个包含5篇文档的segments可能会被合并(combined),所以第一个segment有一个基础的值(base value)为0,第二个segment则为5。在第二个segment中的第3篇文档(document three from the second segment)将有一个扩展的值为8。

    2  当文档被删除的时候,在编号序列中(in the numbering)将产生(created)间隔段(gaps)。这些最后(eventually)在索引通过合并演进时(index evolves through merging)将会被清除(removed)。当segments被合并后(merged),已删除的文档将会被丢弃(dropped),一个刚被合并的(freshly-merged)segment因此在它的编号序列中(in its numbering)不再有间隔段(gaps)。

    2.1.6    索引结构概述

    每一个片断的索引(segment index)管理(maintains)如下的数据:

    Fields名称:这包含了(contains)在索引中使用的一系列fields的名称(the set of field names)。

    已存储的field的值:它包含了,对每篇文档来说,一个属性-值数据对(attribute-value pairs)的清单(a list of),其中属性即为field的名字。这些被用来存储关于文档的备用信息(auxiliary information),比如它的标题(title)、url、或者一个访问一个数据库(database)的唯一标识(identifier)。这套存储的fields就是那些在检索时对每一个命中的(hits)文档所返回的(returned)信息。这些是通过文档编号(document number)来做为key得到的。

    Term字典(dictionary):一个包含(contains)所有terms的字典,被使用在所有文档中所有被索引的fields中。它还包含了该term所在的文档的数目(the number of documents which contains the term),并且指向了(pointer to)term的频率(frequency)和接近度(proximity)的数据(data)。

    Term频率数据(frequency data):对字典中的每一个term来说,所有包含该term(contains the term)的文档的编号(numbers of all documents),以及该term出现在该文档中的频率(frequency)。

    Term接近度数据(proximity data):对字典中的每一个term来说,该term出现在(occur)每一篇文档中的位置(positions)。

    调整因子(normalization factors):对每一篇文档的每一个field来说,为一个存储的值(a value is stored)用来加入到(multiply into)命中该field的分数(score for hits on that field)中。

    Term向量(vectors):对每一篇文档的每一个field来说,term向量(有时候被称做文档向量)可以被存储。一个term向量由term文本和term的频率(frequency)组成(consists of)。怎么添加term向量到你的索引中请参考Field类的构造方法(constructors)。

    删除的文档(deleted documents):一个可选的(optional)文件标示(indicating)哪一篇文档被删除。

    关于这些项的详细信息在随后的章节(subsequent sections)中逐一介绍。

    2.1.7    索引文件中定义的数据类型

    数据类型

    所占字节长度(字节)

    说明

    Byte

    1

    基本数据类型,其他数据类型以此为基础定义

    UInt32

    4

    32位无符号整数,高位优先

    UInt64

    8

    64位无符号整数,高位优先

    VInt

    不定,最少1字节

    动态长度整数,每字节的最高位表明还剩多少字节,每字节的低七位表明整数的值,高位优先。可以认为值可以为无限大。其示例如下

    字节1

    字节2

    字节3

    0

    00000000

       

    1

    00000001

       

    2

    00000010

       

    127

    01111111

       

    128

    10000000

    00000001

     

    129

    10000001

    00000001

     

    130

    10000010

    00000001

     

    16383

    10000000

    10000000

    00000001

    16384

    10000001

    10000000

    00000001

    16385

    10000010

    10000000

    00000001

    Chars

    不定,最少1字节

    采用UTF-8编码[20]的Unicode字符序列

    String

    不定,最少2字节

    由VInt和Chars组成的字符串类型,VInt表示Chars的长度,Chars则表示了String的值

    3.1索引文件结构

      Lucene使用文件扩展名标识不同的索引文件,文件名标识不同版本或者代(generation)的索引片段(segment)。如.fnm文件存储域Fields名称及其属性,.fdt存储文档各项域数据,.fdx存储文档在fdt中的偏移位置即其索引文件,.frq存储文档中term位置数据,.tii文件存储term字典,.tis文件存储term频率数据,.prx存储term接近度数据,.nrm存储调节因子数据,另外segments_X文件存储当前最新索引片段的信息,其中X为其最新修改版本,segments.gen存储当前版本即X值,这些文件的详细介绍上节已说过了。

        下面的图描述了一个典型的lucene索引文件列表:

    wpsC018.tmp

    如果将它们的关系划成图则如下所示

    wpsC019.tmp

    这些文件中存储数据的详细结构是怎样的呢,下面几个小节逐一介绍它们,熟悉它们的结构非常有助于优化Lucene的查询和索引效率和存储空间等。

    3.2每个Index包含的单个文件

    下面几节介绍的文件存在于每个索引index中,并且只有一份。

    3.2.1Segments文件

    索引中活动(active)的Segments被存储在segment info文件中,segments_N,在索引中可能会包含一个或多个segments_N文件。然而,最大一代的那个文件(the one with largest generation)是活动的片断文件(这时更旧的segments_N文件依然存在(are present)是因为它们暂时(temporarily)还不能被删除,或者,一个writer正在处理提交请求(in the process of committing),或者一个用户定义的(custom)IndexDeletionPolicy正被使用)。这个文件按照名称列举每一个片断(lists each segment by name),详细描述分离的标准(seperate norm)和要删除的文件(deletion files),并且还包含了每一个片断的大小。

    对2.1版本来说,还有一个文件segments.gen。这个文件包含了该索引中当前生成的代(current generation)(segments_N中的_N)。这个文件仅用于一个后退处理(fallback)以防止(in case)当前代(current generation)不能被准确地(accurately)通过单独地目录文件列举(by directory listing alone)来确定(determened)(由于某些NFS客户端因为基于时间的目录(time-based directory)的缓存终止(cache expiration)而引起)。这个文件简单地包含了一个int32的版本头(version header)(SegmentInfos.FORMAT_LOCKLESS=-2),遵照代的记录(followed by the generation recorded)规则,对int64来说会写两次(write twice)。

    版本

    包含的项

    数目

    类型

    描述

    2.1之前版本

    Format

    1

    Int32

    在Lucene1.4中为-1,而在Lucene 2.1中为-3(SegmentsInfos.FORMAT_SINGLE_NORM_FILE)

    Version

    1

    Int64

    统计在删除和添加文档时,索引被更改了多少次。

    NameCounter

    1

    Int32

    用于为新的片断文件生成新的名字。

    SegCount

    1

    Int32

    片断的数目

    SegName

    SegCount

    String

    片断的名字,用于所有构成片断索引的文件的文件名前缀。

    SegSize

    SegCount

    Int32

    包含在片断索引中的文档的数目。

    2.1及之后版本

    Format

    1

    Int32

    在Lucene 2.1和Lucene 2.2中为-3(SegmentsInfos.FORMAT_SINGLE_NORM_FILE)

    Version

    1

    Int64

    同上

    NameCounter

    1

    Int32

    同上

    SegCount

    1

    Int32

    同上

    SegName

    SegCount

    String

    同上

    SegSize

    SegCount

    Int32

    同上

    DelGen

    SegCount

    Int64

    为分离的删除文件的代的数目(generation count of the separate deletes file),如果值为-1,表示没有分离的删除文件。如果值为0,表示这是一个2.1版本之前的片断,这时你必须检查文件是否存在_X.del这样的文件。任意大于0的值,表示有分离的删除文件,文件名为_X_N.del

    HasSingleNormFile

    SegCount

    Int8

    该值如果为1,表示Norm域(field)被写为一个单一连接的文件(single joined file)中(扩展名为.nrm),如果值为0,表示每一个field的norms被存储为分离的.fN文件中,参考下面的“标准化因素(Normalization Factors)”

    NumField

    SegCount

    Int32

    表示NormGen数组的大小,如果为-1表示没有NormGen被存储。

    NormGen

    SegCount * NumField

    Int64

    记录分离的标准文件(separate norm file)的代(generation),如果值为-1,表示没有normGens被存储,并且当片断文件是2.1之前版本生成的时,它们全部被假设为0(assumed to be 0)。而当片断文件是2.1及更高版本生成的时,它们全部被假设为-1。这时这个代(generation)的意义与上面DelGen的意义一样。

    IsCompoundFile

    SegCount

    Int8

    记录是否该片断文件被写为一个复合的文件,如果值为-1表示它不是一个复合文件(compound file),如果为1则为一个复合文件。另外如果值为0,表示我们需要检查文件系统是否存在_X.cfs

    2.3

    Format

    1

    Int32

    在Lucene 2.3中为-4 (SegmentInfos.FORMAT_SHARED_DOC_STORE)

    Version

    1

    Int64

    同上

    NameCounter

    1

    Int32

    同上

    SegCount

    1

    Int32

    同上

    SegName

    SegCount

    String

    同上

    SegSize

    SegCount

    Int32

    同上

    DelGen

    SegCount

    Int64

    同上

    DocStoreOffset

    1

    Int32

    如果值为-1则该segment有自己的存储文档的fields数据和term vectors的文件,并且DocStoreSegment, DocStoreIsCompoundFile不会存储。在这种情况下,存储fields数据(*.fdt和*.fdx文件)以及term vectors数据(*.tvf和*.tvd和*.tvx文件)的所有文件将存储在该segment下。另外,DocStoreSegment将存储那些拥有共享的文档存储文件的segment。DocStoreIsCompoundFile值为1如果segment存储为compound文件格式(如.cfx文件),并且DocStoreOffset值为那些共享文档存储文件中起始的文档编号,即该segment的文档开始的位置。在这种情况下,该segment不会存储自己的文档数据文件,而是与别的segment共享一个单一的数据文件集。

    [DocStoreSegment]

    1

    String

    如上

    [DocStoreIsCompoundFile]

    1

    Int8

    如上

    HasSingleNormFile

    SegCount

    Int8

    同上

    NumField

    SegCount

    Int32

    同上

    NormGen

    SegCount * NumField

    Int64

    同上

    IsCompoundFile

    SegCount

    Int8

    同上

    2.4及以上

    Format

    1

    Int32

    在Lucene 2.4中为-7 (SegmentInfos.FORMAT_HAS_PROX)

    Version

    1

    Int64

    同上

    NameCounter

    1

    Int32

    同上

    SegCount

    1

    Int32

    同上

    SegName

    SegCount

    String

    同上

    SegSize

    SegCount

    Int32

    同上

    DelGen

    SegCount

    Int64

    同上

    DocStoreOffset

    1

    Int32

    同上

    [DocStoreSegment]

    1

    String

    同上

    [DocStoreIsCompoundFile]

    1

    Int8

    同上

    HasSingleNormFile

    SegCount

    Int8

    同上

    NumField

    SegCount

    Int32

    同上

    NormGen

    SegCount * NumField

    Int64

    同上

    IsCompoundFile

    SegCount

    Int8

    同上

    DeletionCount

    SegCount

    Int32

    记录该segment中删除的文档数目

    HasProx

    SegCount

    Int8

    值为1表示该segment中至少一个fields的omitTf设置为false,否则为0

    Checksum

    1

    Int64

    存储segments_N文件中直到checksum的所有字节的CRC32 checksum数据,用来校验打开的索引文件的完整性(integrity)。

    3.2.2Lock文件

    写锁(write lock)文件名为“write.lock”,它缺省存储在索引目录中。如果锁目录(lock directory)与索引目录不一致,写锁将被命名为“XXXX-write.lock”,其中“XXXX”是一个唯一的前缀(unique prefix),来源于(derived from)索引目录的全路径(full path)。当这个写锁出现时,一个writer当前正在修改索引(添加或者清除文档)。这个写锁确保在一个时刻只有一个writer修改索引。

    需要注意的是在2.1版本之前(prior to),Lucene还使用一个commit lock,这个锁在2.1版本里被删除了。

    3.2.3Deletable文件

    在Lucene 2.1版本之前,有一个“deletable”文件,包含了那些需要被删除文档的详细资料。在2.1版本后,一个writer会动态地(dynamically)计算哪些文件需要删除,因此,没有文件被写入文件系统。

    3.2.4 Compound文件s(.cfs)

    从Lucene 1.4版本开始,compound文件格式成为缺省信息。这是一个简单的容器(container)来服务所有下一章节(next section)描述的文件(除了.del文件),格式如下:

    版本

    包含的项

    数目

    类型

    描述

    1.4之后版本

    FileCount

    1

    VInt

     

    DataOffset

    FileCount

    Long

     

    FileName

    FileCount

    String

     

    FileData

    FileCount

    raw

    Raw文件数据是上面命名的所有单个的文件数据(the individual named above)。

    结构如下图所示:

    wpsC039.tmp

    3.3每个Segment包含的文件

    剩下的文件(remaining files)都是per-segment(每个片断文件),因此(thus)都用后缀来定义(defined by suffix)。

    3.3.1Fields域数据文件

    3.3.1.1Field信息(.fnm

    Field的名字都存储在Field信息文件中,后缀是.fnm。

    文件

    包含的项

    数目

    类型

    版本

    描述

    FieldsInfo(.fnm)

    FieldsCount

    1

    VInt

       

    FieldName

    FieldsCount

    String

       

    FieldBits

    FieldsCount

    Byte

     

    最低阶的bit位(low-order bit)值为1表示是被索引的Fields,0表示非索引的Fields。

     

    第二个最低阶的bit位(second lowest-order bit)值为1表示该Field有term向量存储(term vectors stored),0表示该field没有term向量。

    >=1.9

    如果第三个最低阶的bit位(third lowest-order bit)设置(0×04),term的位置(term positions)将和term向量一起被存储(stored with term vectors)。

    >=1.9

    如果第四个最低阶的bit位(fourth lowest-order bit)设置(0×08),term的偏移(term offsets)将和term向量一起被存储(stored with term vectors)。

    >=1.9

    如果第五个最低阶的bit位(fifth lowest-order bit)设置(0×10),norms将对索引的field忽略掉(norms are omitted for the indexed field)。

    >=1.9

    如果第六个最低阶的bit位(sixth lowest-order bit)设置(0×20),payloads将为索引的field存储(payloads are stored for the indexed field)。

    注明:payloads概念:

    词条载荷(payloads)――允许用户将任意二进制数据和索引中的任意词条(term)相关联。

    词条载荷是一个允许信息在索引中按逐词条储存的新特性。例如,当索引Web页面时,储存某个关键词的额外信息可能会很有用,例如这个关键词关联的URL或者经过文字分析后得出的权重系数。在更高级的应用中,为了突出语句中的名次成分相对于其它成分的重要性,储存语句中这个关键词出现的部分可能会很有帮助。我今年在ApacheCon Europe会议上的演讲中就有几张讲述词条载荷的幻灯片,感兴趣的读者可以去看看。

    Fields将使用它们在这个文件中的顺序来编号(fields are numbered by their order in this file)。需要注意的是,就像文档编号(document numbers)一样,field编号(field numbers)与片断是相关的(are segment relative)。结构如下图所示:

    wpsC04A.tmp

    3.3.1.2存储的Field.fdx.fdt

    存储的fields(stored fields)通过两个文件来呈现(represented by two files),即field索引文件(.fdx)和field数据文件(.fdt)。

    文件

    包含的项

    父项

    数目

    类型

    版本

    描述

    Fields Index(.fdx) 对每个文档来说,存储指向它的fields数据的指针(pointer)

    FieldValuesPosition

     

    SegSize

    UInt64

     

    用于找详细文档(a particular document)的所有fields的field数据文件中的位置(position),因为它包含的(contains)是固定长度的数据(fixed-length data),这个文件可以很容易地进行随机访问(randomly accessed)。

     

    文档n的field数据的位置是在该文件中n*8的位置中(UInt64类型)。

    Fields Data(.fdt)这个文件存储每个文档的field数据

    DocFieldData

     

    SegSize

         

    FieldCount

    DocFieldData

    1

    VInt

       

    FieldNum

    DocFieldData

    FieldCount

    VInt

       

    Bits

    DocFieldData

    FieldCount

    Byte

    <=1.4

    只有最低阶的bit位(low-order bits of Bits)被使用,值为1表示tokenized field(分解过的field),0表示non-tokenized field。

    Byte

    >=1.9

    最低阶的bit位表示tokenized field

    >=1.9

    第二个bit(second bit)用于表示该field存储binary数据。

    >=1.9

    第三个bit(third bit)表示该field的压缩选项被开启(field with compression enabled),如果压缩选项开启,采用的压缩算法(algorithm)是ZLIB

    Value

    DocFieldData

    FieldCount

    String

    <=1.4

     

    String | BinaryValue

    >=1.9

    依赖于Bits的值

    BinaryValue

    >=1.9

    ValueSize,<Byte>^ValueSize

    ValueSize

    Value

    1

    VInt

    >=1.9

     

    结构如下图所示:

    wpsC05A.tmp

    3.3.2存储的term字典(.tii.tis

    Term字典使用如下两种文件存储,第一种是存储term信息(TermInfoFile)的文件,即.tis文件,格式如下:

    版本

    包含的项

    数目

    类型

    描述

    全部版本

    TIVersion

    1

    UInt32

    记录该文件的版本,1.4版本中为-2

    TermCount

    1

    UInt64

     

    IndexInterval

    1

    UInt32

     

    SkipInterval

    1

    UInt32

     

    MaxSkipLevels

    1

    UInt32

     

    TermInfos

    1

    TermInfo…

     

    TermInfos->TermInfo

    TermCount

    TermInfo

     

    TermInfo->Term

    TermCount

    Term

     

    Term->PrefixLength

    TermCount

    VInt

    Term文本的前缀可以共享,该项的值表示根据前一个term的文本来初始化的字符串前缀长度,前一个term必须已经预设成后缀文本以便构成该term的文本。比如,如果前一个term为“bone”,而当前term为“boy”,则该PrefixLength值为2,suffix值为“y”

    Term->Suffix

    TermCount

    String

    如上

    Term->FieldNum

    TermCount

    VInt

    用来确定term的field,它们存储在.fdt文件中。

    TermInfo->DocFreq

    TermCount

    VInt

    包含该term的文档数目

    TermInfo->FreqDelta

    TermCount

    VInt

    用来确定包含在.frq文件中该term的TermFreqs的位置。特别指出,它是该term的数据在文件中位置与前一个term的位置的差值,当为第一个term时,该值为0

    TermInfo->ProxDelta

    TermCount

    VInt

    用来确定包含在.prx文件中该term的TermPositions的位置。特别指出,它是该term的数据在文件中的位置与前一个term的位置地差值,当为第一个term时,该值为0。如果fields的omitTF设置为true,该值也为0,因为prox信息没有被存储。

    TermInfo->SkipDelta

    TermCount

    VInt

    用来确定包含在.frq文件中该term的SkipData的位置。特别指出,它是TermFreqs之后即SkipData开始的字节数目,换句话说,它是TermFreq的长度。SkipDelta只有在DocFreq不比SkipInteval小的情况下才会存储。

    TermInfoFile文件按照Term来排序,排序方法首先按照Term的field名称(按照UTF-16字符编码)排序,然后按照Term的Text字符串(UTF-16编码)排序。 结构如下图所示:

    wpsC06B.tmp

    另一种是存储term信息的索引文件,即.tii文件,该文件包含.tis文件中每一个IndexInterval的值,与它在.tis中的位置一起被存储,这被设计来完全地读进内存中(read entirely into memory),以便用来提供随机访问.tis文件。该文件的结构与.tis文件非常相似,只是添加了一项数据,即IndexDelta。格式如下

    版本

    包含的项

    数目

    类型

    描述

    全部版本

    TIVersion

    1

    UInt32

    同tis

    IndexTermCount

    1

    UInt64

    同tis

    IndexInterval

    1

    UInt32

    同tis

    SkipInterval

    1

    UInt32

    是TermDocs存储在skip表中的分数(fraction),用来加速(accelerable)TermDocs.skipTo(int)的调用。在更小的索引中获得更大的结果值(larger values result),将获得更高的速度,但却更小开销?(fewer accelerable cases while smaller values result in bigger indexes, less acceleration (in case of a small value for MaxSkipLevels)

    MaxSkipLevels

    1

    UInt32

    是.frq文件中为每一个term存储的skip levels的最大数目,A low value results in smaller indexes but less acceleration, a larger value results in slighly larger indexes but greater acceleration.参见.frq文件格式中关于skip levels的详细介绍。

    TermIndices

    IndexTermCount

    TermIndice

    同tis

    TermIndice->TermInfo

    IndexTermCount

    TermInfo

    同tis

    TermIndice->IndexDelta

    IndexTermCount

    VLong

    用来确定该Term的TermInfo在.tis文件中的位置,特别指出,它是该term的数据的位置与前一个term位置的差值。

    结构如下图所示:

    wpsC06C.tmp

    Lucene源代码剖析

    3.3.3Term频率数据(.frq)

        Term频率数据文件(.frq文件)存储容纳了每一个term的文档列表,以及该term出现在该文档中的频率(出现次数frequency,如果omitTf设置为fals时才存储)。

    版本

    包含的项

    父类型

    类型

    描述

    全部版本

    TermFreqs

    TermCount

    TermFreq

    按照term顺序排序,term是隐含的(?implicit),来自.tis文件。TermFreq按文档编号递增的顺序排序。

    SkipData

    TermCount

    SkipData

     

    TermFreq->DocDelta

    TermCount

    VInt

    如果omitTf设置为false,要同时检测文档编号和频率,特别指出,DocDelta/2时该文档编号与上一个文档编号的差值(如果是第一个文档值为0)。当DocDelta为单数时频率为1,当DocDelta为偶数时频率为读取下一个VInt的值。如果omitTf设置为true,DocDelta为文档编号之间的差值(gap,不用乘以2,multiplited),频率信息则不被存储。

    TermFreq->[Freq?]

    TermCount

    VInt

     

    SkipData->SkipLevelLength

    NumSkipLevels-1

    VInt

     

    SkipData->SkipLevel

    TermCount

    SkipDatums

     

    SkipLevel->SkipDatum

    DocFreq/(SkipInterval^(Level + 1))

    SkipDatum

     

    SkipData->SkipDatum

    TermCount

    SkipDatum

     

    SkipDatum->DocSkip

    1

    VInt

     

    SkipDatum->PayloadLength?

    1

    VInt

     

    SkipDatum->FreqSkip

    1

    VInt

     

    SkipDatum->ProxSkip

    1

    VInt

     

    SkipDatum->SkipChildLevelPointer?

    1

    VLong

     

    结构如下图所示:

    wpsC07C.tmp

    举例来说,当omitTf设置为false时,一个term的TermFreqs在文档7出现1次并且在文档11中出现3次,则为如下的VInt数字序列:
        15, 8, 3
    如果omitTf设置为true时,则为如下数字序列:
        7, 4
        DocSkip记录在TermFreqs中每隔SkipInterval个文档之前的文档编号。如果该term的域fields中被禁用payloads时,则DocSkip呈现在序列中(in the sequence)与上一个值之间的差值(difference)。如果payloads启用时,则DocSkip/2表示序列中与上一个值之间的差值。如果payloads启用并且DocSkip为奇数时,PayloadLength将被存储并表示(indicating)在TermPositions中第SkipInterval个文档之前的最后一个payload的长度。FreqSkip和ProxSkip分别(respectively)记录在FreqFile和ProxFile文件中每SkipInterval个记录(entry)的位置。文件的位置信息对序列中前一个SkipDatumn来说与TermFreqs和Positions的起始信息相关。

    例如,如果DocFreq=35并且SkipInterval=16,则在TermFreqs中有两个SkipData记录,容纳第15和第31个文档编号。第一个FreqSkip代表第16个SkipDatumn起始的TermFreqs数据开始之后的字节数目,第二个FreqSkip表示第32个SkipDatumn开始之后的字节数目。第一个ProxSkip代表第16个SkipDatumn起始的Positions数据开始之后的字节数目,第二个ProxSkip表示第32个SkipDatumn开始之后的字节数目。
    在Lucene 2.2版本中介绍了skip levels的想法(notion),每一个term可以有多个skip levels。一个term的skip levels的数目等于NumSkipLevels = Min(MaxSkipLevels, floor(log(DocFreq/log(SkipInterval))))。对一个skip level来说SkipData记录的数目等于DocFreq/(SkipInterval^(Level + 1))。然而(whereas)最低的(lowest)skip level等于Level = 0。
    例如假设SkipInterval = 4, MaxSkipLevels = 2, DocFreq = 35,则skip level 0有8个SkipData记录,在TermFreqs序列中包含第3、7、11、15、19、23、27和31个文档的编号。Skip level 1则有2个SkipData记录,在TermFreqs中包含了第15和第31个文档的编号。
    在所有level>0之上的SkipData记录中包含一个SkipChildLevelPointer,指向(referencing)level-1中相应)(corresponding)的SkipData记录。在这个例子中,level 1中的记录15有一个指针指向level 0中的记录15,level 1中的记录31有一个指针指向level 0中的记录31。

    3.3.4Positions位置信息数据(.prx)

    Positions位置信息数据文件(.prx文件)容纳了每一个term出现在所有文档中的位置的列表。注意如果在fields中的omitTf设置为true时将不会在此文件中存储任何信息,并且如果索引中所有fields中的omitTf都设置为true,此.prx文件将不会存在。

    版本

    包含的项

    数目

    类型

    描述

    全部版本

    TermPositions

    TermCount

    TermPositions

    按照term顺序排序,term是隐含的(?implicit),来自.tis文件。

    TermPositions->Positions

    DocFreq

    Positions

    按文档编号递增的顺序排序。

    Positions->PositionDelta

    Freq

    VInt

    如果term的fields中payloads被禁用,则取值为term出现在该文档中当前位置与前一个位置的差值(第一个位置取值0)。如果payloads被启用,则取值为当前位置与上一个位置之间差值的2倍。如果payloads启用并且PositionDelta为单数,则PayloadLength被存储,表示当前位置的payloads的长度。

    Positions->Payload?

    Freq

    Payload

     

    Payload->PayloadLength?

    1

    VInt

     

    Payload->PayloadData

    PayloadLength

    byte

     

    结构如下图所示:

    wpsC08D.tmp

    例如,如果一个term的TermPositions为一个文档中出现的第4个term,并且为后来的文档(subsequent document)中出现的第5个和第9个term,则将被存储为下面的VInt数据序列(payloads禁用):

        4, 5, 4

        PayloadData是与term的当前位置相关联元数据(metadata),如果该位置的PayloadLength被存储,则它表示此payload的长度。如果PayloadLength没存储,则此payload与前一个位置的payload拥有相等的PayloadLength。

    3.3.5Norms调节因子文件(.nrm)

    在Lucene 2.1版本之前,每一个索引都有一个norm文件给每一个文档都保存了一个字节。对每一个文档来说,那些.f[0-9]*包含了一个字节容纳一个被编码的分数,值为对hits结果集来说在那个field中被相乘得出的分数(multiplied into the score)。每一个分离的norm文件在适当的时候(when adequate)为复合的(compound)和非复合的segment片断创建,格式如下:

    Norms (.f[0-9]*) –> <Byte> SegSize 在Lucene 2.1及以上版本,只有一个norm文件容纳了所有norms数据:

    版本

    包含的项

    数目

    类型

    描述

    2.1及之后版本

    NormsHeader

    1

    raw

    ‘N’,'R’,'M’,Version:4个字节,最后字节表示该文件的格式版本,当前为-1

    Norms

    NumFieldsWithNorms

    Norms

     

    Norms->Byte

    SegSize

    Byte

    每一个字节编码了一个float指针数值,bits 0-2 容纳 3-bit 尾数(mantissa),bits 3-8容纳 5-bit 指数(exponent),这些被转换成一个IEEE单独的float数值,如图所示

    NormsHeader->Version

    1

    Byte

     

    结构如下图所示:

    wpsC08E.tmp

    一个分离的norm文件在一个存在的segment的norm数据被更改的时候被创建,当field N被修改时,一个分离的norm文件.sN被创建,用来维护该field的norm数据。

    Lucene 源码剖析

    3.3.6Term向量文件

    Term向量(vector)的支持是field基本组成中对一个field来说的可选项,它包含如下4种文件:

                 1. 文档索引或.tvx文件:对每个文档来说,它把偏移(offset)存储进文档数据(.tvd)文件和域field数据(.tvf)文件

    版本

    包含的项

    数目

    类型

    描述

    全部版本

    TVXVersion

    1

    Int

    在Lucene 2.4中为3 (TermVectorsReader.FORMAT_VERSION2)

    DocumentPosition

    NumDocs

    UInt64

    在.tvd文件中的偏移

    FieldPosition

    NumDocs

    UInt64

    在.tvf文件中的偏移

    结构如下图所示:

    wpsC09F.tmp

        2. 文档或.tvd文件:对每个文档来说,它包含fields的数目,有term向量的fields的列表,还有指向term向量域文件(.tvf)中的域信息的指针列表。该文件用于映射(map out)出那些存储了term向量的fields,以及这些field信息在.tvf文件中的位置。

    版本

    包含的项

    数目

    类型

    描述

    全部版本

    TVDVersion

    1

    Int

    在Lucene 2.4中为3 (TermVectorsReader.FORMAT_VERSION2)

    NumFields

    NumDocs

    VInt

     

    FieldNums

    NumDocs

    FieldNums

     
     

    FieldNums->FieldNumDelta

    NumFields

    VInt

     
     

    FieldPositions

    NumDocs

    FieldPositions

     
     

    FieldPositions->FieldPositionDelta

    NumField-1

    VLong

     

    结构如下图所示:

    wpsC0AF.tmp

        3. 域field或.tvf文件:对每个存储了term向量的field来说,该文件包含了一个term的列表,及它们的频率,还有可选的位置和偏移信息。

    版本

    包含的项

    数目

    类型

    描述

    全部版本

    TVFVersion

    1

    Int

    在Lucene 2.4中为3 (TermVectorsReader.FORMAT_VERSION2)

    NumTerms

    NumFields

    VInt

     

    Position/Offset

    NumFields

    Byte

     

    TermFreqs

    NumFields

    TermFreqs

     

    TermFreqs->TermText

    NumTerms

    TermText

     

    TermText->PrefixLength

    NumTerms

    VInt

     

    TermText->Suffix

    NumTerms

    String

     

    TermFreqs->TermFreq

    NumTerms

    VInt

     

    TermFreqs->Positions?

    NumTerms

    Positions

     

    Positions->Position

    TermFreq

    VInt

     

    TermFreqs->Offsets?

    NumTerms

    Offsets

     

    Offsets->StartOffset

    TermFreq

    VInt

     

    Offsets->EndOffset

    TermFreq

    VInt

     

    结构如下图所示:

    wpsC0B0.tmp

    备注:

    l Positions/Offsets 字节存储的条件是当该term向量含有存储的位置或偏移信息时。
    l Term Text prefixes文本前缀是共享的,表示根据前一个term的文本来初始化的字符串前缀长度,前一个term必须已经预设成后缀文本以便构成该term的文本。比如,如果前一个term为“bone”,而当前term为“boy”,则该PrefixLength值为2,suffix值为“y”。
    l Positions存储为Delta编码的VInts,意思是我们只能存储当前位置与最后位置的差值。
    l Offsets存储为Delta编码的VInts,第一个VInt是startOffset,第二个VInt是endOffset。

    3.3.7删除的文档 (.del)

    删除的文档(.del)文件是可选的,而且仅当一个segment存在有被删除的文档时才存在。即使对每一单个segment,它也是维护复合segment的外部数据(exterior)。

    对Lucene 2.1及以前版本,它的格式为:Deletions (.del) –> ByteCount,BitCount,Bits   

    对2.2及以上版本,格式如下:

    版本

    包含的项

    数目

    类型

    描述

    2.2之后版本

    [Format]

    1

    UInt32

    可选,-1表示为DGaps,非负数(negative)值表示为Bit,并且此时不存储Format

    ByteCount

    1

    UInt32

    代表Bit里的字节数目,而且一般值为(SegSize/8)+1

    BitCount

    1

    UInt32

    表示Bit里当前设置的字节数目

    Bit|DGaps

    1

     

    Bit还是DGaps取决于Format。Bits中对每一个索引的文档均包含一个字节,当一个bit对应的一个文档编号被设置时,表示该文档被删除。Bit从最低(least)到最重要(significant)的文档排序。所以Bits包含两个字节,0×00和0×02,则文档9被标记为删除。DGaps表示松散(sparse)的bit-vector向量比Bits更有效率(efficiently)。DGaps由索引中非0的Bits位生成,以及非0的字节数据本身。Bits中非0字节数目(NonzeroBytesCoun)不会存储。

    Bit->Byte

    ByteCount

    Byte

     

    DGaps->DGap

    NonzeroBytesCount

    VInt

     

    DGaps-> NonzeroBytes

    NonzeroBytesCount

    Byte

     

    结构如下图所示:

    wpsC0C1.tmp

    举例来说,如果有8000 bits,并且只有bits 10, 12, 32 被设置,DGaps将会存储如下数据:

    (VInt) 1 , (byte) 20 , (VInt) 3 , (Byte) 1  

    3.3.8局限性(Limitations)

    有几个地方这些文件格式会让terms和文档的最大数目受限于32-bit的大小,大约最大40亿。这在今天不是一个问题,长远来看(in the long term)可能会成为个问题。因此它们应该替换为UInt64类型或者更好的类型,如VInt则没有大小限制。

    Lucene 源码剖析

    4索引是如何创建的

    为了使用Lucene来索引数据,首先你比把它转换成一个纯文本(plain-text)tokens的数据流(stream),并通过它创建出Document对象,其包含的Fields成员容纳这些文本数据。一旦你准备好些Document对象,你就可以调用IndexWriter类的addDocument(Document)方法来传递这些对象到Lucene并写入索引中。当你做这些的时候,Lucene首先分析(analyzer)这些数据来使得它们更适合索引。详见《Lucene In Action》

    4.1 索引创建示例

       下面的代码示例如何给一个文件建立索引。

    // Store the index on disk
        Directory directory = FSDirectory.getDirectory(“/tmp/testindex“);
    // Use standard analyzer
        Analyzer analyzer = new StandardAnalyzer(); 
    // Create IndexWriter object
        IndexWriter iwriter = new IndexWriter(directory, analyzer, true);
        iwriter.setMaxFieldLength(25000);
    // make a new, empty document
        Document doc = new Document();
        File f = new File(“/tmp/test.txt“);
    // Add the path of the file as a field named ”path”.  Use a field that is 
        // indexed (i.e. searchable), but don’t tokenize the field into words.
        doc.add(new Field(“path“, f.getPath(), Field.Store.YES, Field.Index.UN_TOKENIZED));
        String text = “This is the text to be indexed.“;
        doc.add(new Field(“fieldname“, text, Field.Store.YES,      Field.Index.TOKENIZED));
    // Add the last modified date of the file a field named ”modified”.  Use 
        // a field that is indexed (i.e. searchable), but don’t tokenize the field
        // into words.
        doc.add(new Field(“modified“,
            DateTools.timeToString(f.lastModified(), DateTools.Resolution.MINUTE),
            Field.Store.YES, Field.Index.UN_TOKENIZED));
    // Add the contents of the file to a field named ”contents”.  Specify a Reader,
        // so that the text of the file is tokenized and indexed, but not stored.
        // Note that FileReader expects the file to be in the system’s default encoding.
        // If that’s not the case searching for special characters will fail.
        doc.add(new Field(“contents“, new FileReader(f)));
        iwriter.addDocument(doc);
        iwriter.optimize();
        iwriter.close();

    下面详细介绍每一个类的处理机制。

    4.2 索引创建类IndexWriter

    一个IndexWriter对象创建并且维护(maintains) 一条索引并生成segment,使用DocumentsWriter类来建立多个文档的索引数据,SegmentMerger类负责合并多个segment。

    4.2.1 org.apache.lucene.store.IndexWriter

    一个IndexWriter对象只创建并维护一个索引。IndexWriter通过指定存放的目录(Directory)以及文档分析器(Analyzer)来构建,direcotry代表索引存储(resides)在哪里;analyzer表示如何来分析文档的内容;similarity用来规格化(normalize)文档,给文档算分(scoring);IndexWriter类里还有一些SegmentInfos对象用于存储索引片段信息,以及发生故障回滚等。以下是它们的类图:

    wpsC0D1.tmp

    它的构造函数(constructor)的create参数(argument)确定(determines)是否一条新的索引将被创建,或者是否一条已经存在的索引将被打开。需要注意的是你可以使用create=true参数打开一条索引,即使有其他readers也在在使用这条索引。旧的readers将继续检索它们已经打开的”point in time”快照(snapshot),并不能看见那些新已创建的索引,直到它们再次打开(re-open)。另外还有一个没有create参数的构造函数,如果提供的目录(provided path)中没有已经存在的索引,它将创建它,否则将打开此存在的索引。

    另一方面(in either case),添加文档使用addDocument()方法,删除文档使用removeDocument()方法,而且一篇文档可以使用updateDocument()方法来更新(仅仅是先执行delete在执行add操作而已)。当完成了添加、删除、更新文档,应该需要调用close方法。

    这些修改会缓存在内存中(buffered in memory),并且定期地(periodically)刷新到(flush)Directory中(在上述方法的调用期间)。一次flush操作会在如下时候触发(triggered):当从上一次flush操作后有足够多缓存的delete操作(参见setMaxBufferedDeleteTerms(int)),或者足够多已添加的文档(参见setMaxBufferedDocs(int)),无论哪个更快些(whichever is sooner)。对被添加的文档来说,一次flush会在如下任何一种情况下触发,文档的RAM缓存使用率(setRAMBufferSizeMB)或者已添加的文档数目,缺省的RAM最高使用率是16M,为得到索引的最高效率,你需要使用更大的RAM缓存大小。需要注意的是,flush处理仅仅是将IndexWriter中内部缓存的状态(internal buffered state)移动进索引里去,但是这些改变不会让IndexReader见到,直到commit()和close()中的任何一个方法被调用时。一次flush可能触发一个或更多的片断合并(segment merges),这时会启动一个后台的线程来处理,所以不会中断addDocument的调用,请参考MergeScheduler。

    构造函数中的可选参数(optional argument)autoCommit控制(controls)修改对IndexReader实体(instance)读取相同索引的能见度(visibility)。当设置为false时,修改操作将不可见(visible)直到close()方法被调用后。需要注意的是修改将依然被flush进Directory,就像新文件一样(as new files),但是却不会被提交(commit)(没有新的引用那些新文件的segments_N文件会被写入(written referencing the new files))直道close()方法被调用。如果在调用close()之前发生了某种严重错误(something goes terribly wrong)(例如JVM崩溃了),于是索引将反映(reflect)没有任何修改发生过(none of changes made)(它将保留它开始的状态(remain in its starting state))。你还可以调用rollback(),这样可以关闭那些没有提交任何修改操作的writers,并且清除所有那些已经flush但是现在不被引用的(unreferenced)索引文件。这个模式(mode)对防止(prevent)readers在一个错误的时间重新刷新(refresh)非常有用(例如在你完成所有delete操作后,但是在你完成添加操作前的时候)。它还能被用来实现简单的single-writer的事务语义(transactional semantics)(“all or none”)。你还可以执行两条语句(two-phase)的commit,通过调用prepareCommit()方法,之后再调用commit()方法。这在Lucene与外部资源(例如数据库)交互的时候是很需要的,而且必须执行commit或rollback该事务。

    当autoCommit设为true的时候,该writer会周期性地提交它自己的数据。已过时:注意在3.0版本中,IndexWriter将不会接收autoCommit=true,它会硬设置(hardwired)为false。你可以自己在需要的时候经常调用commit()方法。这不保证什么时候一个确定的commit会处理。它被曾经用来在每次flush的时候处理,但是现在会在每次完成merge操作后处理,如2.4版本中即如此。如果你想强行执行commit,请调用commit方法或者close这个writer。一旦一个commit完成后,新打开的IndexReader实例将会看到索引中该commit更改的数据。当以这种模式运行时,当优化(optimize)或者片断合并(segment merges)正在进行(take place)的时候需要小心地重新刷新(refresh)你的readers,因为这两个操作会绑定(tie up)可观的(substantial)磁盘空间。

    不管(Regardless)autoCommit参数如何,一个IndexReader或者IndexSearcher只会看到索引在它打开的当时的状态。任何在索引被打开之后提交到索引中的commit信息,在它被重新打开之前都不会见到。当一条索引暂时(for a while)将不会有更多的文档被添加,并且期望(desired)得到最理想(optimal)的检索性能(performance),于是optimize()方法应该在索引被关闭之前被调用。

    打开IndexWriter会为使用的Directory创建一个lock文件。尝试对相同的Directory打开另一个IndexWriter将会导致(lead to)一个LockObtainFailedException异常。如果一个建立在相同的Directory的IndexReader对象被用来从这条索引中删除文档的时候,这个异常也会被抛出。

    专家(Expert):IndexWriter允许指定(specify)一个可选的(optional)IndexDeletionPolicy实现。你可以通过这个控制什么时候优先的提交(prior commit)从索引中被删除。缺省的策略(policy)是KeepOnlyLastCommitDeletionPolicy类,在一个新的提交完成的时候它会马上所有的优先提交(prior commit)(这匹配2.2版本之前的行为)。创建你自己的策略能够允许你明确地(explicitly)保留以前的”point in time”提交(commit)在索引中存在(alive)一段时间。为了让readers刷新到新的提交,在它们之下没有被删除的旧的提交(without having the old commit deleted out from under them)。这对那些不支持“在最后关闭时才删除”语义(”delete on last close” semantics)的文件系统(filesystem)如NFS,而这是Lucene的“point in time”检索通常所依赖的(normally rely on)。

    专家(Expert):IndexWriter允许你分别修改MergePolicy和MergeScheduler。MergePolicy会在该索引中的segment有更改的任何时候被调用。它的角色是选择哪一个merge来做,如果有(if any)则传回一个MergePolicy.MergeSpecificatio来描述这些merges。它还会选择merges来为optimize()做处理,缺省是LogByteSizeMergePolicy。然后MergeScheduler会通过传递这些merges来被调用,并且它决定什么时候和怎么样来执行这些merges处理,缺省是ConcurrentMergeScheduler。

    4.2.2 org.apache.lucene.index.DocumentsWriter

    DocumentsWriter是由IndexWriter调用来负责处理多个文档的类,它通过与Directory类及Analyzer类、Scorer类等将文档内容提取出来,并分解成一组term列表再生成一个单一的segment所需要的数据文件,如term频率、term位置、term向量等索引文件,以便SegmentMerger将它合并到统一的segment中去。以下是它的类图:

    wpsC0D2.tmp

    该类可接收多个添加的文档,并且直接写成一个单独的segment文件。这比为每一个文档创建一个segment(使用DocumentWriter)以及对那些segments执行合作处理更有效率。

    每一个添加的文档都被传递给DocConsumer类,它处理该文档并且与索引链表中(indexing chain)其它的consumers相互发生作用(interacts with)。确定的consumers,就像StoredFieldWriter和TermVectorsTermsWriter,提取一个文档的摘要(digest),并且马上把字节写入“文档存储”文件(比如它们不为每一个文档消耗(consume)内存RAM,除了当它们正在处理文档的时候)。

    其它的consumers,比如FreqProxTermsWriter和NormsWriter,会缓存字节在内存中,只有当一个新的segment制造出的时候才会flush到磁盘中。

    一旦使用完我们分配的RAM缓存,或者已添加的文档数目足够多的时候(这时候是根据添加的文档数目而不是RAM的使用率来确定是否flush),我们将创建一个真实的segment,并将它写入Directory中去。

    4.3 索引创建过程

        文档的索引过程是通过DocumentsWriter的内部数据处理链完成的,DocumentsWriter可以实现同时添加多个文档并将它们写入一个临时的segment中,完成后再由IndexWriter和SegmentMerger合并到统一的segment中去。DocumentsWriter支持多线程处理,即多个线程同时添加文档,它会为每个请求分配一个DocumentsWriterThreadState对象来监控此处理过程。处理时通过DocumentsWriter初始化时建立的DocFieldProcessor管理的索引处理链来完成的,依次处理为DocFieldConsumers、DocInverter、TermsHash、FreqProxTermsWriter、TermVectorsTermsWriter、NormsWriter以及StoredFieldsWriter等。

    索引创建处理过程及类的主线请求链表如下图所示:

    wpsC0E3.tmp

    下面介绍主要步骤的处理过程

    4.3.1 DocFieldProcessorPerThread.processDocument()

        该方法是处理一个文档的调度函数,负责整理文档的各个fields数据,并创建相应的DocFieldProcessorPerField对象来依次处理每一个field。该方法首先调用索引链表的startDocument()来初始化各项数据,然后依次遍历每一个fields,将它们建立一个以field名字计算的hash值为key的hash表,值为DocFieldProcessorPerField类型。如果hash表中已存在该field,则更新该FieldInfo(调用FieldInfo.update()方法),如果不存在则创建一个新的DocFieldProcessorPerField来加入hash表中。注意,该hash表会存储包括当前添加文档的所有文档的fields信息,并根据FieldInfo.update()来合并相同field名字的域设置信息。

        建立hash表的同时,生成针对该文档的fields[]数组(只包含该文档的fields,但会共用相同的fields数组,通过lastGen来控制当前文档),如果field名字相同,则将Field添加到DocFieldProcessorPerField中的fields数组中。建立完fields后再将此fields数组按field名字排序,使得写入的vectors等数据也按此顺序排序。之后开始正式的文档处理,通过遍历fields数组依次调用DocFieldProcessorPerField的processFields()方法进行(下小节继续讲解),完成后调用finishDocument()完成后序工作,如写入FieldInfos等。

        下面举例说明此过程,假设要添加如下一个文档:

    文档域

    内容

    是否索引

    title

    Lucene 源码分析

    true

    url

    http://javenstudio.org

    false

    content

    索引是如何创建的

    true

    content

    索引的创建过程

    true

        下图描述处理后fields数组的数据结构

    wpsC0E4.tmp

    Lucene 源码剖析

    5索引是如何存储的

    5.1 数据存储类Directory

        Directory及相关类负责文档索引的存储。

    5.1.1 org.apache.lucene.store.Directory

    一个Directory对象是一系列统一的文件列表(a flat list of files)。文件可以在它们被创建的时候一次写入,一旦文件被创建,它再次打开后只能用于读取(read)或者删除(delete)操作。并且同时在读取和写入的时候允许随机访问(random access)。

    在这里并不直接使用Java I/O API,但是更确切地说,所有I/O操作都是通过这个API处理的。这使得读写操作方式更统一起来,如基于内存的索引(RAM-based indices)的实现(即RAMDirectory)、通过JDBC存储在数据库中的索引、将一个索引存储为一个文件的实现(即FSDirectory)。

    Directory的锁机制是一个LockFactory的实例实现的,可以通过调用Directory实例的setLockFactory()方法来更改。

    wpsC0F5.tmp

    5.1.2 org.apache.lucene.store.FSDirectory

    FSDirectory类直接实现Directory抽象类为一个包含文件的目录。目录锁的实现使用缺省的SimpleFSLockFactory,但是可以通过两种方式修改,即给getLockFactory()传入一个LockFactory实例,或者通过调用setLockFactory()方法明确制定LockFactory类。

    目录将被缓存(cache)起来,对一个指定的符合规定的路径(canonical path)来说,同样的FSDirectory实例通常通过getDirectory()方法返回。这使得同步机制(synchronization)能对目录起作用。

    wpsC105.tmp

    5.1.3 org.apache.lucene.store.RAMDirectory

    RAMDirectory类是一个驻留内存的(memory-resident)Directory抽象类的实现。目录锁的实现使用缺省的SingleInstanceLockFactory,但是可以通过setLockFactory()方法修改。

    wpsC106.tmp

    5.1.4 org.apache.lucene.store.IndexInput

    IndexInput类是一个为了从一个目录(Directory)中读取文件的抽象基类,是一个随机访问(random-access)的输入流(input stream),用于所有Lucene读取Index的操作。BufferedIndexInput是一个实现了带缓冲的IndexInput的基础实现。

    wpsC117.tmp

    5.1.5 org.apache.lucene.store.IndexOutput

    IndexOutput类是一个为了写入文件到一个目录(Directory)中的抽象基类,是一个随机访问(random-access)的输出流(output stream),用于所有Lucene写入Index的操作。BufferedIndexOutput是一个实现了带缓冲的IndexOutput的基础实现。RAMOuputStream是一个内存驻留(memory-resident)的IndexOutput的实现类。

    wpsC118.tmp

    文档内容是如何分析的

        Analyzer类负责分析文档结构并提取内容。

    6.1 文档分析类Analyzer

    6.1.1 org.apache.lucene.store.Analyzer

    Analyzer类构建用于分析文本的TokenStream对象,因此(thus)它表示(represent)用于从文本中分解(extract)出组成索引的terms的一个规则器(policy)。典型的(typical)实现首先创建一个Tokenizer,它将那些从Reader对象中读取字符流(stream of characters)打碎为(break into)原始的Tokens(raw Tokens)。然后一个或更多的TokenFilters可以应用在这个Tokenizer的输出上。警告:你必须在你的子类(subclass)中覆写(override)定义在这个类中的其中一个方法,否则的话Analyzer将会进入一个无限循环(infinite loop)中。

    wpsC128.tmp

    6.1.2 org.apache.lucene.store.StandardAnalyzer

    StandardAnalyzer类是使用一个English的stop words列表来进行tokenize分解出文本中word,使用StandardTokenizer类分解词,再加上StandardFilter以及LowerCaseFilter以及StopFilter这些过滤器进行处理的这样一个Analyzer类的实现。

    wpsC129.tmp

    Lucene 源码剖析

    7如何给文档评分

    Similarity类负责给文档评分。

    7.1 文档评分类Similarity

    7.1.1 org.apache.lucene.search. Similarity

    Similarity类实现算分(scoring)的API,它的子类实现了检索算分的算法。DefaultSimilarity类是缺省的算分的实现,SimilarityDelegator类是用于委托算分(delegating scoring)的实现,在Query.getSimilarity(Searcher)}的实现里起作用,以便覆写(override)一个Searcher中Similarity实现类的仅有的确定方法(certain methods)。

    wpsC13A.tmp

    查询q相对于文档d的分数与在文档和查询向量(query vectors)之间的余弦距离(cosing-distance)或者点乘积(dot-product)有关系(correlates to),文档和查询向量存于一个信息检索(Information Retrieval)的向量空间模型(Vector Space Model (VSM))之中。一篇文档的向量与查询向量越接近(closer to),它的得分也越高(scored higher),这个分数按如下公式计算:

    wpsC13B.tmp

    其中:

    1. tf(t in d) 与term的出现次数(frequency)有关系(correlate to),定义为(defined as)term t在当前算分(currently scored)的文档d中出现(appear in)的次数(number of times)。对一个给定(gived)的term,那些出现此term的次数越多(more occurences)的文档将获得越高的分数(higher score)。缺省的tf(t in d)算法实现在DefaultSimilarity类中,公式如下:

    wpsC14C.tmp

    2. idf(t) 代表(stand for)反转文档频率(Inverse Document Frequency)。这个分数与反转(inverse of)的docFreq(出现过term t的文档数目)有关系。这个分数的意义是越不常出现(rarer)的term将为最后的总分贡献(contribution)更多的分数。缺省idff(t in d)算法实现在DefaultSimilarity类中,公式如下:

    wpsC14D.tmp

    3. coord(q,d) 是一个评分因子,基于(based on)有多少个查询terms在特定的文档(specified document)中被找到。通常(typically),一篇包含了越多的查询terms的文档将比另一篇包含更少查询terms的文档获得更高的分数。这是一个搜索时的因子(search time factor)是在搜索的时候起作用(in effect at search time),它在Similarity对象的coord(q,d)函数中计算。

    4. queryNorm(q) 是一个修正因子(normalizing factor),用来使不同查询间的分数更可比较(comparable)。这个因子不影响文档的排名(ranking)(因为搜索排好序的文档(ranked document)会增加(multiplied)相同的因数(same factor)),更确切地说只是(but rather just)为了尝试(attempt to)使得不同查询条件(甚至不同索引(different indexes))之间更可比较性。这是一个搜索时的因子是在搜索的时候起作用,由Similarity对象计算。缺省queryNorm(q)算法实现在DefaultSimilarity类中,公式如下:

    wpsC15D.tmp

    sumOfSquaredWeights(查询的terms)是由查询Weight对象计算的,例如一个布尔(boolean)条件查询的计算公式为:

    wpsC15E.tmp

    5. t.getBoost() 是一个搜索时(search time)的代表查询q中的term t的boost数值,具体指定在(as specified in)查询的文本中(参见查询语法),或者由应用程序调用setBoost()来指定。需要注意的是实际上(really)没有一个直接(direct)的API来访问(accessing)一个多个term的查询(multi term query)中的一个term 的boost值,更确切地说(but rather),多个terms(multi terms)在一个查询里的表示形式(represent as)是多个TermQuery对象,所以查询里的一个term的boost值的访问是通过调用子查询(sub-query)的getBoost()方法实现的。

    6. norm(t,d) 是提炼取得(encapsulate)一小部分boost值(在索引时间)和长度因子(length factor):

    ú document boost – 在添加文档到索引之前通过调用doc.setBoost()来设置。

    ú Field boost – 在添加Field到文档之前通过调用field.setBoost()来设置。

    ú lengthNorm(field) – 在文档添加到索引的时候,根据(in accordance with)文档中该field的tokens数目计算得出,所以更短(shorter)的field会贡献更多的分数。lengthNorm是在索引的时候起作用,由Similarity类计算得出。

    当一篇文档被添加到索引的时候,所有上面计算出的因子将相乘起来(multiplied)。如果文档拥有多个相同名字的fields(multiple fields with same name),所有这些fields的boost值也会被一起相乘起来(multiplied together):

    wpsC16F.tmp

    然而norm数值的结果在被存储(stored)之前被编码成(encoded as)一个单独的字节(single byte)。在检索的时候,这个norm字节值从索引目录(index directory)中读取出来,并解码回(decoded back)一个norm浮点数值(float value)。这个编/解码(encoding/decoding)行为,会缩减(reduce)索引的大小(index size),这得自于(come with)精度损耗的代价(price of precision loss)- 它不保证decode(encode(x))=x,举例来说decode(encode(0.89))=0.75。还有需要注意的是,检索的时候再修改评分(scoring)的这个norm部分已近太迟了,例如,为检索使用不同的Similarity。

    以前收集的关于lucene分析的资料,版本有点老,已经忘了是在哪找到的了,如果你是文档作者,侵犯了你的版权,@我即可

  • 相关阅读:
    查看端口有没有被占用
    微信公众号2()
    How to insert a segment of noise to music file
    puppet practice
    Docker Commands
    LempelZiv algorithm realization
    The algorithm of entropy realization
    Java network programmingguessing game
    Deploy Openstack with RDO and Change VNC console to Spice
    puppet overview
  • 原文地址:https://www.cnblogs.com/2018/p/5809548.html
Copyright © 2011-2022 走看看