[Lucene]核心类和概念介绍

先上一个使用Lucene读写文件的DEMO

import java.io.IOException;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.RAMDirectory;
public class IndexAndSearchExample {
    public static void main(String[] args) throws IOException {
        // 创建内存中的索引目录
        Directory indexDir = new RAMDirectory();

        // 创建分词器。
        Analyzer analyzer = new StandardAnalyzer();

        // 配置IndexWriter
        IndexWriterConfig config = new IndexWriterConfig(analyzer);
        //IndexWriter构建时会检查目录下是否有索引,没有则写入新索引。如果有则只向索引添加内容
        IndexWriter writer = new IndexWriter(indexDir, config);

        // 创建文档
        Document doc1 = new Document();
        doc1.add(new TextField("title", "Lucene in Action", Field.Store.YES));
        doc1.add(new TextField("description", "Lucene is a powerful search library", Field.Store.YES));
        writer.addDocument(doc1);

        Document doc2 = new Document();
        doc2.add(new TextField("title", "Java Development with Ant", Field.Store.YES));
        doc2.add(new TextField("description", "Learn how to use Ant to build, test, and deploy your Java applications", Field.Store.YES));
        writer.addDocument(doc2);

        // 提交文档,IndexWriter会向Directory提交写入变化。IndexWriter还可以继续使用
        writer.commit();
        //关闭IndexWriter.也会触发提交文档
        writer.close();

        // 创建IndexSearcher,能够对指定Directory搜索
        DirectoryReader reader = DirectoryReader.open(indexDir);
        IndexSearcher searcher = new IndexSearcher(reader);

        // 构建查询
        Query query = new TermQuery(new Term("title", "lucene"));

        // 执行查询
        ScoreDoc[] hits = searcher.search(query, 10).scoreDocs;

        // 遍历结果
        for (ScoreDoc hit : hits) {
            Document result = searcher.doc(hit.doc);
            System.out.println(result.get("title") + " : " + result.get("description"));
        }

        // 关闭IndexReader
        reader.close();
    }
}


Directory

索引文件所在目录,存储一批文件的抽象层。提供文件增删查改的方法。

Analyzer

分词器。用于将text分割成更小的term(词汇单元),还可能会执行大小写转换、无用词删除等操作

Field

document组成部分,有name,type,value属性。值可以是string、number、byte[]。包含以下实例化选项

  • stored 是否需要存储域的真实值(source),例如网页正文就不需要存储真实值,聊天记录就需要 。对应索引文件中的stored_field
  • tokenized 代表是否做分词,在lucene中只有TextField这一个字段需要做分词。
  • termVector: term vector保存了一个文档内所有的term的相关信息,包括Term值、出现次数(frequencies)以及位置(positions)等,是一个per-document inverted index,提供了根据docid来查找该文档内所有term信息的能力。对于长度较小的字段不建议开启term verctor,因为只需要重新做一遍分词即可拿到term信息,而针对长度较长或者分词代价较大的字段,则建议开启term vector。Term vector的用途主要有两个,一是关键词高亮(知道offset才能高亮),二是做文档间的相似度匹配(more-like-this)。
  • omitNorms: Norms是normalization的缩写,lucene允许每个文档的每个字段都存储一个normalization factor,是和搜索时的相关性计算有关的一个系数。Norms的存储只占一个字节,但是每个文档的每个字段都会独立存储一份,且Norms数据会全部加载到内存。所以若开启了Norms,会消耗额外的存储空间和内存。但若关闭了Norms,则无法做index-time boosting
  • IndexOptions 控制字段的多少信息会被索引。有以下信息:doc, term frequencues, positions, offsets. 如果为none则不索引。存储的词频、出现次数、位置和偏移量信息,用于执行查询和计算文档得分等操作。
  • docValuesType 正向索引(docid到field的一个列存),大大优化了sorting、faceting或aggregation的效率。DocValues是一个强schema的存储结构,开启DocValues的字段必须拥有严格一致的类型,不同的doc的该字段的类型必须相同 。对应索引文件中的doc_values
  • dimension:Lucene支持多维数据的索引,采取特殊的索引来优化对多维数据的查询

Document

索引和查询的单元,由一些field组成

doc id

Lucene的Index通过DocId来唯一标识一个Doc.

  • DocId实际上并不在Index内唯一,而是Segment内唯一,Lucene这么做主要是为了做写入和压缩优化。那既然在Segment内才唯一,又是怎么做到在Index级别来唯一标识一个Doc呢?方案很简单,基于segment内值的范围给他一个附加值,例如两个有5个doc的segment,第二个segment的附加值是5,第二个segment中的doc 3的外部值为8
  • DocId在Segment内唯一,取值从0开始递增。但不代表DocId取值一定是连续的,如果有Doc被删除,那可能会存在空洞。
  • 一个文档对应的DocId可能会发生变化,主要是发生在Segment合并时。

IndexWriter

IndexWriter创建并维护索引。同一时间一个索引只能有一个IndexWriter,该功能通过文件锁实现。
每个更改索引的方法都会返回一个序列号,它表示应用每个更改的有效顺序。 commit 还返回一个序列号,描述哪些更改在提交点中,哪些不在。序列号是暂时的(不会以任何方式保存到索引中)并且仅在单个 IndexWriter 实例中有效。
这些数据修改缓存在内存中并周期性的flush到Directory,自上次flush之后当有足够的doc时将触发flush。flush只是将缓存的数据修改移动到索引,但这些数据修改对Reader不可见,直到调用commit或close。
DWPT线程的数量等于调用IndexWriter的写接口的线程数量

IndexWriter.addDocument

将doc加到索引中。这个方法周期性调用flush和segment merge。

  • 多线程并发调用IndexWriter的写接口,在IndexWriter内部具体请求会由DocumentsWriter来执行。DocumentsWriter内部在处理请求之前,会先根据当前执行操作的Thread来分配DocumentsWriterPerThread。
  • 每个线程在其独立的DocumentsWriterPerThread空间内部进行数据处理,包括分词、相关性计算、索引构建等。每个DocumentsWriterPerThread空间都包含一个In-memory buffer,这个buffer最终会flush成不同的独立的segment文件。
  • 数据处理完毕后,在DocumentsWriter层面执行一些后续动作,例如触发FlushPolicy的判定等。

IndexWriter.optimize()

IndexWriter.optimize()可以进行段合并,可以提升搜索速度,合并期间会消耗大量的CPU和IO资源,并且需要额外的磁盘空间保存新的段,调用commit之前旧的段不会被删除,结束之后占用的磁盘空间比之前要少

IndexWriter.flush()

将所有内存中的segments移动到Directory(有FSDirectory,RAMDirectory),不会等待数据真正写入磁盘(对于linux数据会先写到filesystem cache),但不会进行commit,所以数据不可读。

IndexWriter.commit()

IndexWriter.commit()将提交所有pending的更改(add doc\delete doc\ segment merge)到索引,并同步相关的索引文件(会等待文件真正的持久存储而不是写到filesystem cache就返回),所以这些更改对IndexReader来说是可见的。并且更新内容不会因为系统崩溃而丢失。
换句话说,commit会触发flush并使数据可读

IndexWriter.getReader

flush内存中缓存的doc,创建并返回一个包含这些doc的Reader,涵盖对索引的所有已提交(commited)和未提交(un-commited)的更改,这些更改可读并且不需要调用commit。
请注意,这在功能(结果而不是过程)上等同于调用 {flush} 然后打开一个新的reader。但是这种方法的周转时间应该更快,因为它避免了可能代价高昂的commit。
这提供了near real-time search,无需commit就能使数据可以搜索。叫做近实时的原因是——没有保证当对IndexWriter进行更改后你能多快的获得新的Reader。

IndexReader

提供接口访问某一时间点的索引。直到新的IndexReader被打开前,所有通过IndexWriter完成的更改都不会可见。同一时间一个索引可以有多个reader

DirectoryReader.openIfChanged

获取一个新Reader,包含最新的数据。
DirectoryReader.openIfChanged底层会调用IndexWriter.getReader,该方法会使IndexWriter立马刷新出新的段内容(flush),而不是等到内存缓冲区满。

IndexSearcher

通过IndexReader实现查询。出于性能的原因,如果索引未更改应该在多个查询之间共享IndexSearcher,而不是创建新的索引。如果索引更改了,需要调用DirectoryReader.openIfChanged并创建新的IndexSearcher。

你可能感兴趣的:(lucene,全文检索,java)