Lucene搜索引擎-索引


如果对Lucene不熟悉的,请移步: Lucene搜索引擎-分词器


对输入的一串内容进行分词以后,如果需要在后续进行检索,则必须定义如何存储以及存储的方式、内容,则这就是索引需要做的事情。


直接上代码:

import java.io.File;
import java.io.IOException;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.document.FieldType;
import org.apache.lucene.document.NumericDocValuesField;
import org.apache.lucene.document.SortedDocValuesField;
import org.apache.lucene.document.StringField;
import org.apache.lucene.index.DocValuesType;
import org.apache.lucene.index.IndexOptions;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.BytesRef;
import com.dongnao.lucene.demo.analizer.ik.IKAnalyzer4Lucene7;

public class IndexWriteDemo {
 public static void main(String[] args) {
  // 创建使用的分词器,这里使用IKAnalyzer分词器
  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));

   	writer.addDocument(doc);
  } catch (IOException e) {
   	e.printStackTrace();
  }
 }
}

概念理解

先来理解下概念

  • Index:索引,类似传统数据库中的表的概念。Lucene的Index可以理解为一个文档收纳箱,你可以往内部塞入新的文档,或者从里面拿出文档,但如果你要修改里面的某个文档,则必须先拿出来修改后再塞回去。这个收纳箱可以塞入各种类型的文档,文档里的内容可以任意定义,Lucene都能对其进行索引。
  • Document:文档,类似传统数据库中的行记录的概念。一个Index内会包含多个Document。写入Index的Document会被分配一个唯一的ID,即Sequence Number。
  • Field:字段,类似于传统数据库中字段的概念。一个Document会由一个或多个Field组成,Field是Lucene中数据索引的最小定义单位。Lucene提供多种不同类型的Field,例如StringField、TextField、LongFiled或NumericDocValuesField等,Lucene根据Field的类型(FieldType)来判断该数据要采用哪种类型的索引方式(Invert Index、Store Field、DocValues或N-dimensional等。
  • Term和Term Dictionary:词项和词典,Lucene中索引和搜索的最小单位,一个Field会由一个或多个Term组成,Term是由Field经过Analyzer(分词)产生。Term Dictionary即Term词典,是根据条件查找Term的基本索引。

IndexWriter详解

IndexWriterConfig、Directory、Document作为IndexWriter的输入
在这里插入图片描述

  • IndexWriterConfig:索引配置。用这个配置对象创建好IndexWriter对象后,再修改这个配置对象的配置信息不会对IndexWriter对象起作用。涉及如下配置:

    使用的分词器
    如何打开索引(是新建、还是追加)
    还可配置缓冲大小、或缓冲多少个文档,再刷新到存储中
    还可配置合并、删除等策略

  • Directory:指定索引数据存放的位置,可以存放在内存、文件系统、数据库中。FSDirectory是存放于文件系统中,RAMDirectory是存放于内存中
    注意:IndexWriter是线程安全的。若业务代码中有其他的同步控制,请不要使用IndexWriter作为所对象,以免死锁。
  • IndexWriter的API使用流程:

    IndexWriter writer = new IndexWriter(directory, config);// 创建索引写对象
    writer.addDocument(doc);// 创建document,将文档添加到索引
    //writer.deleteDocuments(terms);// 删除文档
    //writer.updateDocument(term, doc);//修改文档
    writer.flush();// 刷新
    writer.commit();// 提交

Document详解

Docement存储

一个Document相当于数据库中的表,由多个字段Field构成,一个Field就像数据库表中的字段。
IndexWriter按加入的顺序为Document指定一个递增的id(从0开始),称为文档id。反向索引中存储的其实就是这个文档id,正向索引也是这个id,业务数据的主键id只是文档的一个字段。
怎么又出现了一个正向索引?来,继续看。
保存索引的时候其实不仅仅只保存反向索引,同时也会保存正向索引,那正向索引保存的又是什么呢?举个例子会比较清晰:

  • 百度搜索关键字:“新闻标题”
  • 搜索结果有很多内容:新闻标题、新闻摘要、图片、URL地址
  • 关键字"新闻标题"是存储在反向索引中的,而搜索结果中的其他内容"新闻摘要"、“图片”、"URL地址"正是存储在正向索引中的,先通过关键字找到对应的document id,在通过id找到具体的结果。

反向索引

词项 document id
苍老师 {1}
麻仓优 {2}

正向索引

document id 新闻id 新闻标题 新闻摘要 URL地址
1 001 苍老师是世界的 yyy http://yyy
2 002 麻仓优也是世界的 xxx http:/xxx

当搜索“苍老师”时,通过document id关联到新闻信息,而更具体的新闻信息则是存储在数据库或其他地方的。

上述的每个信息统称为字段“Field”,有字段名name、字段值value、字段类型type三部分构成。字段值可以是文本、二进制或数值。
看完上述例子后应该对以下的问题会非常清楚:

  • 新闻:新闻id,新闻标题、新闻内容、作者、所属分类、发表时间*
  • 网页搜索的结果:标题、内容、链接地址*
  • 商品:id、名称、图片链接、类别、价格、库存、商家、品牌、月销量、详情*
  • 我们收集数据创建document对象来为其创建索引,数据的所有属性是否都需要加入到document中?如数据库表中的数据记录的所有字段是否都需要放到document中?哪些字段应加入到document中?是不是所有加入的字段都需要进行索引?是不是所有加入的字段都要保存到索引库中?什么样的字段该被索引?什么样的字段该被存储?

Field索引类型

实际应用中会碰到要在搜索结果中做关键字高亮,实现短语
查询、临近查询(跨度查询)等等,那这些信息又是如何存储的呢?比如:要搜索包含“张三” “李四”,且两词之间跨度不超过5个字符。
这种时候就需要用到词项向量。

词项向量:一个字段分词器分词后,每个词项会得到一系列属性信息,如 出现频率、位置、偏移量等,这些信息构成一个词项向量 termVectors

可以在Field的索引类型中设置是否保存这些此项向量
IndexOptions索引选项:

NONE:不索引
DOCS:反向索引中只存储了包含该词的 文档id
DOCS_AND_FREQS:反向索引中会存储 文档id、词频
DOCS_AND_FREQS_AND_POSITIONS:反向索引中存储 文档id、词频、位置
DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS:反向索引中存储 文档id、词频、位置、偏移量

其实为了提升反向索引的效率,这样的字段的位置、偏移数据是不应该保存到反向索引中的,这也是为什么 IndexOptions为什么有那些选项的原因。在lucene4.0以前,反向索引中总会存储这些数据,4.0后改进为可选择的。

附加信息Payloads

对于不需要在搜索反向索引时用到,但在搜索结果处理时需要的位置、偏移
量等字段,我们可以单独为该字段存储(文档id–>词项
向量)的正向索引,即附加信息Payloads。

在这里插入图片描述
FieldType实现类中有对应的set方法:

boolean storeTermVectors() 是否存储词项向量
boolean storeTermVectorPositions() 是否在词项向量中存储位置
boolean storeTermVectorOffsets() 是否在词项向量中存储偏移量
boolean storeTermVectorPayloads() 是否在词项向量中存储附加信息

支持排序

我们往往需要对搜索的结果支持按不同的字段进行排序,如商品搜索
结果按价格排序、按销量排序等。以及对搜索结果进行按某字段分组统计,如
按品牌统计。
空间换时间:对这种需要排序、分组、聚合的字段,为其建立独立的文档->字段值的正向索引、列式存储。这样我们要加载搜中文档的这个字段的数据就快很多,耗内存少。


IndexableFieldType 中的 docValuesType方法 就是让你来为需要排序、分组、
聚合的字段指定如何为该字段创建文档->字段值的正向索引的。
在这里插入图片描述
DocValuesType选项如下:

  • NONE 不开启docvalue
  • NUMERIC 单值、数值字段,用这个
  • BINARY 单值、字节数组字段用
  • SORTED 单值、字符字段用, 会预先对值字节进行排序、去重存储
  • SORTED_NUMERIC 单值、数值数组字段用,会预先对数值数组进行排序
  • SORTED_SET 多值字段用,会预先对值字节进行排序、去重存储

具体使用选择:

  • 字符串+单值 会选择SORTED作为docvalue存储
  • 字符串+多值 会选择SORTED_SET作为docvalue存储
  • 数值或日期或枚举字段+单值 会选择NUMERIC 作为docvalue存储
  • 数值或日期或枚举字段+多值 会选择SORTED_SET作为docvalue存储

注意:

  • DocValuesType是强类型要求的,字段的值必须保证同类型。
  • 需要排序、分组、聚合、分类查询(面查询)的字段才创建docValues

Lucene所有字段子类

Lucene内部提供了很多Field的子类,可以根据实际情况进行灵活选择。
在这里插入图片描述

  • 如果单个Field无法满足需求,还可以进行多个Field组合。
  • 如果连组合都满足不了,还可以直接用Filed+FiledType的方式

Luke索引查看工具

下载地址:https://github.com/DmitryKey/luke/releases
当前最新版本7.3.1,可用于Lucene7.3.0版本
该工具可以用来查看我们创建索引后的结果。
在这里插入图片描述

索引更新

主要使用IndexWriter的API方法进行更新

  • 删除流程:根据Term(词项即字段)、Query找到相关的文档id、同时删除索引信息,再根据文档id删除对应的文档存储
  • 更新流程:先删除、再加入新的doc
  • 注意:只能根据有所有的Term才能进行删除和更新

猜你喜欢

转载自blog.csdn.net/supermao1013/article/details/83444544