lucene的原理以及核心过程

 Lucene是一个核心的索引和搜索库。

1. 其中索引包括四个核心的过程:

(1) 将源数据转化为文本格式。数据的来源多种多样,比如word文档、PDF文档、excel文档等等。我们需要将其中的数据使用相应的api提取出来。 

(2) 分析。这一步主要包括:去除标点符号,停词,大小写转换等等。获取有效的字符流(即词元Term)。

(3) 索引组件利用文档处理后的字符流,构建字典。合并所有文档的字典集合。去重,构建倒排索引表。

(4) 将索引数据加入到索引中。

(5) 索引的核心API如下:

Field:类似于数据库中的一个字段,存储了key-value值。

Document:类似于数据库中的一条数据。包含了多个字段。

数据库中存储的是结构化数据,即表中任何一条数据的结构都是一样的。具有相同的字段,以及字段数据类型。而索引中存储的是非结构化的数据,任何两个文档的字段都可以不同。这就是数据库与索引的一大区别。但是为了搜索的方便以及通用性,我们也会尽可能构造通用的结构。

Analyzer:对TokenStream流进行处理,比如去除停词,大小写转换,词根转换,等等。最后得到词元(Term)流。

IndexWriter:对词元流进行处理,获取字典结构,构造倒排索引表。将数据存储到内存或者磁盘。

Directory:索引数据存放的目录。

下面已lucene6.0来看看如何进行索引操作:

public class IndexExample {
    public static void main(String[] args) throws IOException {
        File indexDirectory = new File("E:/lucene/index");//此处是索引存放地址
        Path path = indexDirectory.toPath();
        Directory directory = FSDirectory.open(path);//磁盘操作使用FSDirectory
        File files = new File("E:/lucene/data");//此处是源文件地址
        // 创建一个分析器对象,使用标准分析器
        Analyzer analyzer = new StandardAnalyzer();
        // 创建一个IndexwriterConfig对象, 分析器
        IndexWriterConfig config = new IndexWriterConfig(analyzer); //analyzer进行分词处理
        // 创建一个IndexWriter对象,对于索引库进行写操作
        IndexWriter indexWriter = new IndexWriter(directory, config);
        for (File f : files.listFiles()) {
            //提取数据
            String fileName = f.getName();// 文件名
            @SuppressWarnings("deprecation")
            String fileContent = FileUtils.readFileToString(f);// 文件内容
            String filePath = f.getPath();// 文件路径
            long fileSize = FileUtils.sizeOf(f); // 文件大小
            // 创建一个Document对象
            Document document = new Document();
            // 向Document对象中添加域信息
            Field nameField = new TextField("name", fileName, Store.YES);// 参数:1、域的名称;2、域的值;3、是否存储;
            Field contentField = new TextField("content", fileContent , Store.YES);
            Field pathField = new StoredField("path", filePath);// storedFiled默认存储
            Field sizeField = new StoredField("size", fileSize);
            // 将域添加到document对象中
            document.add(nameField);
            document.add(contentField);
            document.add(pathField);
            document.add(sizeField);
            // 将信息写入到索引库中
            indexWriter.addDocument(document);
        }
        // 关闭indexWriter
        indexWriter.close();
    }
}

2. 接下来看看索引存储的东西到底是什么。

      非结构化数据中所存储的信息是每个文件包含哪些字符串,也即已知文件,欲求字符串相对容易。而我们想搜索的信息是哪些文件包含此字符串,也即已知字符串,欲求文件。两者恰恰相反。于是如果索引总能够保存从字符串到文件的映射,则会大大提高搜索速度。由于从字符串到文件的映射是文件到字符串映射的反向过程,于是保存这种信息的索引称为反向索引 。反向索引的所保存的信息一般如下:

假设我的文档集合里面有200篇文档,为了方便表示,我们为文档编号从1到200,得到下面的结构:

左边保存的是一系列字符串,称为词典 。每个字符串都指向包含此字符串的文档(Document)链表,此文档链表称为倒排表 (Posting List)。

更详细的信息可以查看这篇博文,讲的非常好:http://blog.chinaunix.net/uid-22679909-id-1771453.html

当然上面只是一个索引文件的原理图。整个索引文件是包含多个文件的。下面已Lucene6.x系列为例。

(1) 默认的话是复合文件格式。

(2) IndexWriterConfig org.apache.lucene.index.IndexWriterConfig.setUseCompoundFile(boolean useCompoundFile):调用此函数可以设为多文件索引模式。该模式下索引文件如下图:

_x.fdt

       通常在搜索打分完毕后,IndexSearcher会返回一个docID序列,但是仅仅有docID我们是无法看到存储在索引中的document,这时候就需要通过docID来得到完整Document信息,这个过程就需要对fdx/fdt文件进行读操作。

       fdx/fdt文件就是Lucene的正向文件。有一个比喻:如果fdt是一本书的正文,那么fdx则是书的目录。显然fdt文件大于fdx。图中正文数据量很小,所以看不出来。通过docID读取到document需要完成Segment、Block、Chunk、document四级查询。Segment、Block、Chunk的查找都是二分查找,速度很快,但是Chunk中定位document则是顺序查找,所以Chunk的大小直接影响着读取的性能。fdt文件的基本单位是Chunk。当你在程序中存储某个域时(使用Field.Store.YES选项),该域会被写入两个文件:.fdx与.fdt。

_x.fdx    

      fdx的基本单位是Block。一个Block由三个部分组成,最顶层的ChunkIndex跟倒数第三层的BlockCunks不是一个东西。

      BlockChunks表示当前Block中Chunk的个数;

      <DocBases>表示当前Block中每个Chunk的doc个数,可以看作一个数组;

      <StartPointers>表示当前Block中每个Chunk在fdt文件中的起始位置,其结构与<DocBases>相同。

_x.fnm

      .fnm文件存储了段中相关文档的所有field信息。包括“该域是否被索引?该域是否允许使用项向量?”等。此文件中的field不一定按字母顺序排列,每个field都有一个fieldNo编号,它会在其他索引文件中被用到,用来节省空间。

关于索引文件更详细的信息,请参考这篇博文:http://xiaodongdong.iteye.com/blog/1868670


2. 搜索

通常搜索词典是相当快的,因为词典本身已经经过处理,是一个有序列表。如果是因为就会首字母排序,如果是中文也可能按照拼音的首字母进行排序。因此,搜索一个有序列表的速度非常快。

未完待续。。。。。







展开阅读全文

没有更多推荐了,返回首页