编程开源技术交流,分享技术与知识

网站首页 > 开源技术 正文

Lucene的索引详解(loc索引)

wxchong 2024-10-23 15:50:34 开源技术 6 ℃ 0 评论

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);

  }

}

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表