网站首页 > 开源技术 正文
IndexWriter
之前讲解了Lucene的分词器,这节记录下Lucene的索引。
Lucene索引创建API示例
Lucene索引创建代码示例
// 创建使用的分词器
Analyzer analyzer = new IKAnalyzer4Lucene7(true);
// 索引配置对象
IndexWriterConfig config = new IndexWriterConfig(analyzer);
try (
// 索引存放目录
// 存放到文件系统中
Directory directory = FSDirectory
.open((new File("d:/test/indextest")).toPath());
// 也可以存放到内存中
// Directory directory = new RAMDirectory();
// 创建索引写对象
IndexWriter writer = new IndexWriter(directory, config);) {
// 准备document
Document doc = new Document();
// 商品id:字符串,不索引、但存储
String prodId = "p0001";
doc.add(new StoredField("prodId", prodId));
// 往document中添加 商品名称字段 String name = "ThinkPad X1 Carbon 20KH0009CD/25CD 超极本轻薄笔记本电脑联想";
doc.add(new TextField("name", name, Store.YES));
.......
IndexWriter涉及类
IndexWriterConfig:写索引配置,装载着分词器,提供着配置信息
- 使用的分词器,
- 如何打开索引(是新建,还是追加)。
- 还可配置缓冲区大小、或缓存多少个文档,再刷新到存储中。
- 还可配置合并、删除等的策略
Directory: 索引存储的方式,文件系统或者内存或数据库
Document:索引存储的内容
IndexWriter 用来创建、维护一个索引 。它的API使用流程:
// 创建索引写对象
IndexWriter writer = new IndexWriter(directory, config);
// 创建document
Document doc = new Document();
// 将文档添加到索引
writer.addDocument(doc);
// 删除文档
//writer.deleteDocuments(terms);
//修改文档
//writer.updateDocument(term, doc);
// 刷新
writer.flush();
// 提交
writer.commit();
那Document是如何存储的呢?
Document
Document
Document即文档,要索引的数据记录、文档在lucene中的表示,是索引、搜索的基本单 元。一个Document由多个字段Field构成。就像数据库的记录-字段。
IndexWriter按加入的顺序为Document指定一个递增的id(从0开始),称为文档id。反向索引中存储的是这个id,文档存储中正向索引也是这个id。 业务数据的主键id只是文档的一个字段。
Filed
Filed即字段:由字段名name、字段值value(fieldsData)、字段类型 type 三 部分构成。 字段值可以是文本(String、Reader 或 预分析的 TokenStream)、二进制值(byte[])或数值。
IndexableFieldType
字段类型:描述该如何索引存储该字段
字段可选择性地保存在索引中,这样在搜索结果中,这些保存的字段值就可获得。 一个Document应该包含一个或多个存储字段来唯一标识一个文档。 未存储的字段,从索引中取得的document中是没有这些字段的。
Document 类关系:
IndexableFieldType:提供了是否分词,是否存储,是否标准化,如何索引等方法。
包括:stored,tokenized,indexOptions,storeTermVectors,omitNorms,
docValueType,point
IndexOptions
IndexOptions: 索引选项
NONE: Not indexed 不索引
DOCS:反向索引中只存储了包含该词的 文档id,没有词频、位置
DOCS_AND_FREQS:反向索引中会存储 文档id、词频
DOCS_AND_FREQS_AND_POSITIONS :反向索引中存储文档id、词频、位置
DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS:反向索引中存储 文档id、词频、位置、偏移量
例如在百度搜索Lucene
显示的信息:有一个标题,下面是正文的简单的描述,再下面是跳转的链接。搜索词Lucene的红色的高亮的显示。
当我们创建索引的时候,标题需要被索引,分词,存储。文章的链接,不是被索引,分词,只需要将它存储。文章的内容需要分词,建立索引,但不需要将部分描述存储。
有的情况下,如果要实现短语 查询、临近查询(跨度查询),例如:搜索包含“张三” “李四”,且两词之间跨度不超过5个字符 。
这时,需要存储分项的偏移量,位置等信息。
但是有的情况下某个字段不需要进行短语查询、临近查询,那么在反向索引中就 不需要保存位置、偏移数据。可以降低反向索引的数据量,提升效率。
为了提升反向索引的效率,这样的字段的位置、偏移数据是不应该保存到反向 索引中的。这也你前面看到 IndexOptions为什么有那些选项的原因。 在lucene4.0以前,反向索引中总会存储这些数据,4.0后改进为可选择的。
那对于做高亮显示,或者斜体,黑体(或得到搜索结果后需要使用这些信息)的字段怎么办?用IndexOptions可能不能满足需求,或者比较困难。
storeTermVectors
一个字段分词器分词后,每个词项会得到一系列属性信息,如出现频率、位 置、偏移量等,这些信息构成一个词项向量 termVectors。
对于不需要在搜索反向索引时用到,但在搜索结果处理时需要的位置、偏移 量、附加数据(payLoad) 的字段,我们可以单独为该字段存储(文档--->词项向量)的正向索引。
- boolean storeTermVectors() 是否存储词项向量
- boolean storeTermVectorPositions() 是否在词项向量中存储位置
- boolean storeTermVectorOffsets() 是否在词项向量中存储偏移量
- boolean storeTermVectorPayloads() 是否在词项向量中存储附加信息
FieldType实现类中有对应的set方法
什么是附加信息Payloads
粉色:代表一个文档Id,DocId
绿色:词频,出现几次
橙色:词的位置
黑色:附加信息
a这个词,?DocId为2,在文章出现一个,位置索引为2的位置
is的词,在DocId为1的文章中,出现一次,位置为1,附加信息为下划线。
附加信息非常有用,可用它来存储特殊信息,及减少词项数等。
我们往往需要对搜索的结果支持按不同的字段进行排序,如商品搜索 结果按价格排序、按销量排序等。以及对搜索结果进行按某字段分组统计,如 按品牌统计。
假如我们按关键字“娃娃”搜索后得到相关的文档id列表 {10,21,18,48,29,…..} 要对它们进行按价格排序 有的人想看销量排序 有时需要按品牌统计数量…
反向索引对排序有用吗? 需得到每个id对应的价格或销售是多少、品牌是什么,再进行排序、统计。 这个价格、销量、品牌数据在哪里? 如果搜到的文档列表量很大,排序会有什么问题没?
空间换时间
对这种需要排序、分组、聚合的字段,为其建立独立的文档->字段值的正向索引、列式存储。这样我们要加载搜中文档的这个字段的数据就快很多, 耗内存少。
docValuesType
IndexableFieldType 中的 docValuesType方法 就是让你来为需要排序、分组、 聚合的字段指定如何为该字段创建文档->字段值的正向索引的。
DocValuesType 选项说明:
- NONE 不开启docvalue
- NUMERIC 单值、数值字段,用这个
- BINARY 单值、字节数组字段用
- SORTED 单值、字符字段用, 会预先对值字节进行排序、去重存储
- SORTED_NUMERIC 单值、数值数组字段用,会预先对数值数组进行排序
- SORTED_SET 多值字段用,会预先对值字节进行排序、去重存储
DocValuesType是强类型要求的: 字段的值必须保证同类型。需要排序、分组、聚合、分类查询(面查询)的字段才创建docValues。
具体使用选择:
- 字符串+单值 会选择SORTED作为docvalue存储
- 字符串+多值 会选择SORTED_SET作为docvalue存储
- 数值或日期或枚举字段+单值 会选择NUMERIC 作为docvalue存储
- 数值或日期或枚举字段+多值 会选择SORTED_SET作为docvalue存储
Point
IndexableFieldType中最后定义的的pointDimensionCount(), pointNumBytes() 是做何用的? Lucene6以后引入了点的概念来表示数值字段,废除了原来的IntField等。在Point 字段类中提供了精确、范围查询的便捷方法。 注意:只是引入点的概念,并未改变数值字段的本质。 既然是点,就有空间概念:维度。一维:一个值,二维:两个值的;…… pointDimensionCount() 返回点的维数 pointNumBytes() 返回点中数值类型的字节数。
以下为一个代码示例:
// 创建使用的分词器
Analyzer analyzer = new IKAnalyzer4Lucene7(true);
// 索引配置对象
IndexWriterConfig config = new IndexWriterConfig(analyzer);
try (
// 索引存放目录
// 存放到文件系统中
Directory directory = FSDirectory
.open((new File("d:/test/indextest")).toPath());
// 存放到内存中
// Directory directory = new RAMDirectory();
// 创建索引写对象
IndexWriter writer = new IndexWriter(directory, config);) {
// 准备document
Document doc = new Document();
// 商品id:字符串,不索引、但存储
String prodId = "p0001";
FieldType onlyStoredType = new FieldType();
onlyStoredType.setTokenized(false);
onlyStoredType.setIndexOptions(IndexOptions.NONE);
onlyStoredType.setStored(true);
onlyStoredType.freeze();
doc.add(new Field("prodId", prodId, onlyStoredType));
// 等同下一行
// doc.add(new StoredField("prodId", prodId));
// 商品名称:字符串,分词索引(存储词频、位置、偏移量)、存储
String name = "ThinkPad X1 Carbon 20KH0009CD/25CD 超极本轻薄笔记本电脑联想";
FieldType indexedAllStoredType = new FieldType();
indexedAllStoredType.setStored(true);
indexedAllStoredType.setTokenized(true);
indexedAllStoredType.setIndexOptions(
IndexOptions.DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS);
indexedAllStoredType.freeze();
doc.add(new Field("name", name, indexedAllStoredType));
// 图片链接:仅存储
String imgUrl = "http://www.dongnao.com/aaa";
doc.add(new Field("imgUrl", imgUrl, onlyStoredType));
// 商品简介:文本,分词索引(不需要支持短语、临近查询)、存储,结果中支持高亮显示
String simpleIntro = "集成显卡 英特尔 酷睿 i5-8250U 14英寸";
FieldType indexedTermVectorsStoredType = new FieldType();
indexedTermVectorsStoredType.setStored(true);
indexedTermVectorsStoredType.setTokenized(true);
indexedTermVectorsStoredType
.setIndexOptions(IndexOptions.DOCS_AND_FREQS);
indexedTermVectorsStoredType.setStoreTermVectors(true);
indexedTermVectorsStoredType.setStoreTermVectorPositions(true);
indexedTermVectorsStoredType.setStoreTermVectorOffsets(true);
indexedTermVectorsStoredType.freeze();
doc.add(new Field("simpleIntro", simpleIntro,
indexedTermVectorsStoredType));
// 价格,整数,单位分,不索引、存储、要支持排序
int price = 999900;
FieldType numericDocValuesType = new FieldType();
numericDocValuesType.setTokenized(false);
numericDocValuesType.setIndexOptions(IndexOptions.NONE);
numericDocValuesType.setStored(true);
numericDocValuesType.setDocValuesType(DocValuesType.NUMERIC);
numericDocValuesType.setDimensions(1, Integer.BYTES);
numericDocValuesType.freeze();
doc.add(new MyIntField("price", price, numericDocValuesType));
// 与下两行等同
// doc.add(new StoredField("price", price));
// doc.add(new NumericDocValuesField("price", price));
// 类别:字符串,索引不分词,不存储、支持分类统计,多值
FieldType indexedDocValuesType = new FieldType();
indexedDocValuesType.setTokenized(false);
indexedDocValuesType.setIndexOptions(IndexOptions.DOCS);
indexedDocValuesType.setDocValuesType(DocValuesType.SORTED_SET);
indexedDocValuesType.freeze();
doc.add(new Field("type", "电脑", indexedDocValuesType) {
@Override
public BytesRef binaryValue() {
return new BytesRef((String) this.fieldsData);
}
});
doc.add(new Field("type", "笔记本电脑", indexedDocValuesType) {
@Override
public BytesRef binaryValue() {
return new BytesRef((String) this.fieldsData);
}
});
// 等同下四行
// doc.add(new StringField("type", "电脑", Store.NO));
// doc.add(new SortedSetDocValuesField("type", new BytesRef("电脑")));
// doc.add(new StringField("type", "笔记本电脑", Store.NO));
// doc.add(new SortedSetDocValuesField("type", new
// BytesRef("笔记本电脑")));
// 商家 索引(不分词),存储、按面(分类)查询
String fieldName = "shop";
String value = "联想官方旗舰店";
doc.add(new StringField(fieldName, value, Store.YES));
doc.add(new SortedDocValuesField(fieldName, new BytesRef(value)));
// 上架时间:数值,排序需要
long upShelfTime = System.currentTimeMillis();
doc.add(new NumericDocValuesField("upShelfTime", upShelfTime));
writer.addDocument(doc);
} catch (IOException e) {
e.printStackTrace();
}
}
public static class MyIntField extends Field {
public MyIntField(String fieldName, int value, FieldType type) {
super(fieldName, type);
this.fieldsData = Integer.valueOf(value);
}
@Override
public BytesRef binaryValue() {
byte[] bs = new byte[Integer.BYTES];
NumericUtils.intToSortableBytes((Integer) this.fieldsData, bs, 0);
return new BytesRef(bs);
}
}
猜你喜欢
- 2024-10-23 ES 基本知识(es基本介绍)
- 2024-10-23 maven打包jar包时如何打包本地jar文件
- 2024-10-23 记录自己搭建solr配置中文分词的过程供大家参考
- 2024-10-23 springboot整合websocket、solr(springboot整合rocketMQ)
- 2024-10-23 ElasticSearch安装ik分词插件(elasticsearch 安装ik分词器)
- 2024-10-23 12K的码农怎样蜕变为30k的架构师?找准方向,拒绝迷茫
- 2024-10-23 Lucene就是这么简单(好儿子今天妈妈就是你的女人了)
- 2024-10-23 5分钟带你了解Lucene全文索引(lucene索引原理)
- 2024-10-23 在.net core中进行中文分词方法(.net core hangfire)
- 2024-10-23 Elasticsearch-通过外网访问加入kibana,head「002」
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- jdk (81)
- putty (66)
- rufus (78)
- 内网穿透 (89)
- okhttp (70)
- powertoys (74)
- windowsterminal (81)
- netcat (65)
- ghostscript (65)
- veracrypt (65)
- asp.netcore (70)
- wrk (67)
- aspose.words (80)
- itk (80)
- ajaxfileupload.js (66)
- sqlhelper (67)
- express.js (67)
- phpmailer (67)
- xjar (70)
- redisclient (78)
- wakeonlan (66)
- tinygo (85)
- startbbs (72)
- webftp (82)
- vsvim (79)
本文暂时没有评论,来添加一个吧(●'◡'●)