Lucene 6.5.1
建立一个Lucene示例
数据写入
public class Writer {
private static final String PATH = "";
public static void main(String[] args) throws Exception {
String doc1 = "hello world";
// 创建IndexWriter
Directory d = FSDirectory.open(Paths.get(PATH));
IndexWriterConfig conf = new IndexWriterConfig(new SimpleAnalyzer());
IndexWriter indexWriter = new IndexWriter(d, conf);
// 把要创建的索引的文本数据放入Document中
Document ducument1 = new Document();
ducument1.add(new TextField("id", "1", Field.Store.YES));
ducument1.add(new TextField("title", "doc1", Field.Store.YES));
ducument1.add(new TextField("content", doc1, Field.Store.YES));
// 通过IndexWriter把Document写入
indexWriter.addDocument(ducument1);
indexWriter.commit();
indexWriter.close();
}
}
这里的** Path**便是我们索引文件的存储路径。在上面的示例中生成的索引文件如下:
nvd&&nvm
nvd&&nvm用来存储域的标准化值(normalization values),这两个索引文件记录了每一篇文档中每一种域的标准化值跟索引信息。
pos&&pay
position在Lucene中描述的是一个term在一篇文档中的位置,并且存在一个或多个position。
doc
索引文件.doc中按块(block)的方式存放了每一个term的文档号、词频,并且保存skip data来实现块之间的快速跳转。
payload是一个自定义的元数据(mete data)来描述term的某个属性,term在一篇文章中的多个位置可以一一对应多个payload,也可以只有部分位置带有payload。
offset是一对整数值(a pair of integers),即startOffset跟endOffset,它们分别描述了term的第一个字符跟最后一个在文档中的位置。
每一个term在所有文档中的position、payload、offset信息在IndexWriter.addDocument()的过程中计算出来,在内存中生成一张倒排表,最终持久化到磁盘时,通过读取倒排表,将position信息写入到.pos文件中,将payload、offset信息写入到.pay文件中。
tim&&tip
.tim(TermDictionary)文件中存放了每一个term的TermStats,TermStats记录了包含该term的文档数量,term在这些文档中的词频总和;另外还存放了term的TermMetadata,TermMetadata记录了该term在.doc、.pos、.pay文件中的信息,这些信息即term在这些文件中的起始位置,即保存了指向这些文档的索引;还存放了term的Suffix,对于有部分相同前缀值的term,只需存放这些term不相同的后缀值,即Suffix。
.tip文件中存放了指向tim文件的索引来实现随机访问tim文件中的信息,并且.tip文件还能用来快速判断某个term是否存在。
dim&&dii
从Lucene6.0开始出现点数据(Point Value)的概念,通过将多维度的点数据生成KD-tree结构,来实现快速的单维度的范围查询(比如 IntPoint.newRangeQuery)以及N dimesional shape intersection filtering。
liv
索引文件.liv只有在一个segment中包含被删除的文档时才会生成,它记录了当前段中没有被删除的文档号。
tvx&&tvd
当设置了TermVector的域生成了倒排表以后,将文档的词向量信息写到.tvx(vector_index)跟.tvd(vector_data)文件中。
fdx&&fdt
当STORE.YES的域生成了倒排表以后,将文档的域值信息写入到.fdt(field data)、.fdx(field index)文件中。
si
当生成一个新的segment时(执行flush、commit、merge、addIndexes(facet)),会生成一个描述段文件信息(segmentInfo)的.si索引文件。
fnm
索引文件.fnm用来描述域信息(FieldInfo)
segments_N
当IndexWriter执行commit()操作后,会生成一个segments_N文件,该文件描述了当前索引目录中所有有效的段信息文件(active segment info),即之前文章介绍的segmentInfo文件,仅仅通过flush()生成的段成为无效的段信息文件。
索引目录中可能存在多个Segments_N文件,每个Segment_N文件代表某次commit()时的索引状态,其中N值最大的Segments_N文件代表最新的一次提交,它包含当前索引目录中所有的索引信息。
cfs&&cfe
索引文件.cfs、.cfe被称为复合(compound)索引文件,在IndexWriterConfig可以配置是否生成复合索引文件,默认开启。
索引的层次结构
索引(Index)
Lucene中每个索引都是放在同一个文件夹中,文件夹中的所有文件构成一个索引。
段(Segment)
如图中的Segment_8,该文件保存的段的元数据信息,每个索引包含了多个段,在段的数据过多时会发生合并。这里还有一个比较重要的文件Segment.gen文件,该文件存在的目的是当存在多个segment_N时如何选择打开哪一个。Lucene会选择generation最大的。
The active segments in the index are stored in the segment info file, segments_N. There may be one or more segments_N files in the index; however, the one with the largest generation is the active one
(when older segments_N files are present it’s because they temporarily cannot be deleted, or a custom {@link IndexDeletionPolicy} is in use). This file lists each segment by name and has details about the codec and generation of deletes.
Lucene索引有至少1个Segment组成,每个seg又包含多个索引文件。位于同一个Seg的文件具有相同的前缀和不同的后缀。在上图中包含了一个Segments_8,这是如果indexriter有写入修改请求时会对Segment_9进行操作。
增量索引(Incremental Indexing)
Lucene采用的增量索引的模式写入文件,即每次索引新的文件都会放在一个新的段中,然后周期性的对以前的段进行合并。
文档(Document)
- 索引的基本单位
- 新添加的文档都是单独保存在一个新生成的段中。
要索引的数据记录、文档在lucene中的表示,是索引、搜索的基本单元。一个Document由多个字段Field构成。IndexWriter按加入的顺序为Document指定一个递增的id(从0开始),称为文档id。反向索引中存储的是这个id,文档存储中正向索引也是这个id。 业务数据的主键id只是文档的一个字段。
Document主要由一组IndexableFields构成,除了提供添加和删除的接口外,在Doc内部提供了各种API用于获取Doc内部的Fields。
Class IndexableField
其为一个接口,包含了字段名,字段值,字段类型。
public interface IndexableField {
// field名字
String name();
// 字段类型
IndexableFieldType fieldType();
// 下面的API都是获取各种字段值的接口。
TokenStream tokenStream(Analyzer var1, TokenStream var2);
/** @deprecated */
@Deprecated
float boost();
BytesRef binaryValue();
String stringValue();
Reader readerValue();
Number numericValue();
}
其中字段类型主要有以下几个内容:
- stored:是否存储
- tokenized:是否分词。
- omitNorms:是否忽略标准化。
- indexOptions:如何索引。
- storeTermVectors:是否存储词项向量。
- storeTermVectorOffset: 词项向量中是否存储偏移量。
- storeTermVectorPositions: 词项向量中是否存储偏位置。
- storeTermVectorPaykoads: 词项向量中是否存储偏附加信息。
域(Field)
- 一篇文档包含不同类型的信息,可以分开索引,比如标题,时间,正文,作者等,
都可以保存在不同的域里。 - 对于同一个Field来说,也会有不同的索引方式。比如说在ElasticSearch中,对一个double[]的属性进行存储,这时候的默认索引成两种形式,其中一种是DoublePoint另一种是SortedNumiecDocValue。
Lucene预定义的字段字类
- TextField:会自动被索引和分词的字段。一般被用在文章的正文部分。
- StringField:会被索引,但是不会被分词,即会被当作一个完整的token处理,一般用在“国家”或者“ID”。
- IntPoint/LongPoint/FloatPoint/DoublePoint:范围查询,ES默认数字的类型包含数组都会保存一份该类型的存储。
- SortedDocValuesField:通常用于排序,ES的数组默认也会保存一份DocValue的存储,本义是在一个文档中存在同一个field的多值情况,但是我们通过DocValue来获取该field数据时,其顺序相对于输入时改变的,且无法获取原始次序。
- SortedSetDocValuesField:同上
- NumericDocValuesField:针对field的只存在单值的情况。
- SortedNumericDocValuesField:同上的上
- SortedField: 一个默认会被存储的Field:这是行存的结构,默认是不开启的,且这中存储方式的效率比较低。
示例
public final class StringField extends Field {
/** Indexed, not tokenized, omits norms, indexes
* DOCS_ONLY, not stored. */
public static final FieldType TYPE_NOT_STORED = new FieldType();
/** Indexed, not tokenized, omits norms, indexes
* DOCS_ONLY, stored */
public static final FieldType TYPE_STORED = new FieldType();
static {
TYPE_NOT_STORED.setOmitNorms(true);
TYPE_NOT_STORED.setIndexOptions(IndexOptions.DOCS);
TYPE_NOT_STORED.setTokenized(false);
TYPE_NOT_STORED.freeze();
TYPE_STORED.setOmitNorms(true);
TYPE_STORED.setIndexOptions(IndexOptions.DOCS);
TYPE_STORED.setStored(true);
TYPE_STORED.setTokenized(false);
TYPE_STORED.freeze();
}
/*************************************/
}
以上的StringField的源码,在static中定义了作为field的相关的属性。
词(Term)
词是索引的最小单位,是经过词法分析和语言处理后的字符串。主要包含了两个成员:field也就是域名,另一个就是所对应的域值,在lucene中文本值都是以ByteRef的形式存储的
String field;
BytesRef bytes;
索引信息
在Lucene中不仅保存了正向信息也保存了反向信息。
- 正向信息:索引(Index) –> 段(segment) –> 文档(Document) –> 域(Field) –> 词(Term), 比如说StoredValue这种就是正向信息查询的。
- 反向信息:词(Term) –> 文档(Document),也就是Lucene的核心倒排索引。