Lucene是一款高性能、可扩展的信息检索工具库,是用于全文检索和搜寻的Java开放源码程序库,最初是由Doug Cutting所撰写,2000年发行了第一个开源版本,2005年成为Apache顶级项目。虽然经过近20年,Lucene在全文检索领域还是独领风骚,蓬勃发展。
优秀的搜索引擎需要复杂的架构和算法,用来支撑对海量数据的存储和搜索,并同时保证搜索质量。搜索引擎最重要的一个数据结构:倒排索引(Inverted Index)
(实现单词->文档的存储形式),能高效实现全文搜索,并且索引数据是"一次检索,可多次搜索"。
Lucene的主要功能包括:
Lucene仅提供检索工具包,不提供额外的检索应用功能,在Lucene之上构建的应用项目主要有:
Lucene索引层级结构主要包括:Index(索引),Segment(索引段),Document(索引文档),Field(索引域),Term(索引项),它们之间关系如下:
Index(索引):原始文档经过Lucene的索引流程后,以Index形式存储在文件系统,支持对保存的数据进行快速随机访问。 一个Index由多个Segment(索引段)构成,每个Segment包含多个Document(索引文件)。Index是逻辑概念,是一个索引目录下,所有索引文件的总和,可表示Document文档数据的集合,不同的Document数据结构,建议使用不同的Index。
Segment(索引段):每个Lucene Index包括多个Segment,每个Segment都是一个独立的索引,是整个Index索引的子集,因此在搜索时支持对每个Segment进行单独访问,最后对结果进行汇总。Segment是逻辑概念,是一系列索引文件的集合,属于同个Segment的索引文件具有相同的文件前缀,各个独立的索引文件组成索引的不同部分(存储Field、词向量、倒排索引等)。
索引流程中基于Segment的主要功能包括:
Document(索引文档):包含多个Field对象的集合容器,是Lucene索引和搜索的原子单元。Lucene基于Document Numbers(文档编号)对每个Document编号,初始变化为0,后续其编号依次增长1。
Field(索引域):是Document的一部分,里面包含真正搜索的内容,每个Field包括三个部分:name(标识名称)、type(Field属性描述)、value。 Field被分词为Term对象进行索引保存。
Field常用的数据结构子类如下
类型 | 说明 |
---|---|
IntPoint | 以int索引,只索引不存储,支持精确/range查询,基于KD-tree 加速range查询速度 |
LongPoint | 以long索引,其他同上 |
FloatPoint | 以float索引,其他同上 |
DoublePoint | 以double索引,其他同上 |
TextField | 可以Reader或String索引,索引并分词,主要用于全文索引 |
StringField | 以String索引,只索引不分词,直接以整个字符串作为一个分词 |
SortedDocValuesField | 以String索引并存储,用于排序(sorting)操作 |
SortedSetDocValuesField | 以String索引并存储,用于聚合(faceting)、分组(grouping)、关联(joining)操作 |
NumericDocValuesField | 以long索引并存储,用于评分、排序和值检索 |
SortedNumericDocValuesField | 与NumericDocValuesField,常用于搜索结果排序 |
StoredField | 存储Field值 |
Field中以FieldType定义索引的属性描述,包括以下内容:
Term(词汇项):索引过程中,经过Analyzer分析器,将Field解析成Token,基于Token添加额外索引信息等操作后,得到Term对象进行索引。Term是最小搜索单元,是实现倒排表Posting的基本元素,由两部分组成:词汇的文本信息、词汇所属Field名称。
解析流程如下图:Field1、Field2基于分词操作,从Token转成Term,Field3设置不分词,其FiledValue直接转成独立的Term。
Analyzer(分词器):是由一组TokenStream串行组成的词汇分析链,定义将Field文本解析为搜索单元Term的策略,可通过IndexWriter构造方法指定Field的分词器。 用户可以很简单的对不同TokenStream组合和实现,得到自定义分词器。
TokenStream是分词处理流的抽象类,主要有两个子类:Tokenizer(定义分词逻辑),TokenFilter(定义分词后的转换操作)。Analyzer一般由一个Tokenizer和多个TokenFilter组成,其中Tokenizer是TokenFilter的前置。
Token(词汇单元):在词汇解析过程中,由Tokenizers和TokenFilters过程中产生的分词对象,包括一系列的Attribute属性信息,定义该分词对象的关注属性,如偏移量、位置、词性等。
Inverted Index(倒排索引):是搜索引擎的核心数据结构,对文档进行逆向排列,以文档Term为Key信息,关联包含该Term的文档信息,即文档原本的数据结构为:document -> terms,而倒排索引的数据结构为:term -> documents,使得基于term-based的全文检索更加高效。
Lucene的倒排索引主要有以下三部分构成:
Lucene功能主要包括两部分:索引,搜索
索引 和 搜索的逻辑架构图如下所示:
Lucene的核心功能索引和搜索都是在lucene.core子项目下实现,对应的源码包关系图如下:
Lucene核心包说明如下(官方文档):
@Test
public void create() throws Exception {
String content = "hello world index";
String indexPath = "test/index";
IndexWriterConfig iwc = new IndexWriterConfig(new StandardAnalyzer());
try (Directory dir = FSDirectory.open(Paths.get(indexPath));
IndexWriter writer = new IndexWriter(dir, iwc);
) {
Document doc = new Document();
doc.add(new StringField("path", indexPath, Field.Store.YES));
doc.add(new TextField("content", content, Field.Store.YES));
doc.add(new LongPoint("modified", Clock.systemUTC().millis()));
writer.addDocument(doc);
}
根据指定Term更新索引信息
@Test
public void update() throws Exception {
String content = "world index";
String indexPath = "test/index";
IndexWriterConfig iwc = new IndexWriterConfig(new StandardAnalyzer());
try (Directory dir = FSDirectory.open(Paths.get(indexPath));
IndexWriter writer = new IndexWriter(dir, iwc);
) {
Document doc = new Document();
doc.add(new StringField("path", indexPath + "_update", Field.Store.YES));
doc.add(new TextField("content", content, Field.Store.YES));
doc.add(new LongPoint("modified", Clock.systemUTC().millis()));
writer.updateDocument(new Term("path", indexPath), doc);
}
}
@Test
public void delete() throws Exception {
String fieldName = "content";
String indexPath = "test/index";
IndexWriterConfig iwc = new IndexWriterConfig(new StandardAnalyzer());
try (Directory dir = FSDirectory.open(Paths.get(indexPath));
IndexWriter writer = new IndexWriter(dir, iwc);
) {
QueryParser parser = new QueryParser(fieldName, new StandardAnalyzer());
Query query = parser.parse("hello world");
long deleteCounts = writer.deleteDocuments(query);
System.out.println("delete doc by query of count=" + deleteCounts);
}
}
@Test
public void read() throws Exception {
String indexPath = "test/index";
Directory dir = FSDirectory.open(Paths.get(indexPath));
DirectoryReader reader = DirectoryReader.open(dir);
IndexReader indexReader = new IndexSearcher(reader).getIndexReader();
//获取索引信息
System.out.println("索引leaves信息:" + indexReader.getContext().leaves());
System.out.println("索引中文档数量:" + indexReader.numDocs());
System.out.println("索引中文档的最大值:" + indexReader.maxDoc());
System.out.println("索引中被删除文档数量:" + indexReader.numDeletedDocs());
}
@Test
public void search() throws Exception {
String indexPath = "test/index";
String fieldName = "content";
Directory dir = FSDirectory.open(Paths.get(indexPath));
IndexSearcher searcher = new IndexSearcher(DirectoryReader.open(dir));
QueryParser parser = new QueryParser(fieldName, new StandardAnalyzer());
Query query = parser.parse("hello world");
TopDocs docs = searcher.search(query, 10);
System.out.println("匹配的搜索条数:" + docs.totalHits.value);
for (int i = 0; i < docs.scoreDocs.length; i++) {
ScoreDoc scoreDoc = docs.scoreDocs[i];
System.out.println("获取Document文档 docId=" + scoreDoc.doc + ",匹配打分=" + scoreDoc.score);
Document doc = searcher.doc(scoreDoc.doc);
Iterator<IndexableField> iterator = doc.iterator();
while (iterator.hasNext()) {
IndexableField field = iterator.next();
String fieldValue = doc.get(field.name());
System.out.println("[fieldName]=" + field.name() + ",[fieldValue]=" + fieldValue);
}
System.out.println();
}
}
多条件搜索基于BooleanQuery实现,示例如下:
@Test
public void booleanSearch() throws Exception {
String indexPath = "test/index";
Directory dir = FSDirectory.open(Paths.get(indexPath));
IndexSearcher searcher = new IndexSearcher(DirectoryReader.open(dir));
Query queryContent = new QueryParser("content", new StandardAnalyzer()).parse("hello world");
BoostQuery boost = new BoostQuery(queryContent, 100);
Query queryPath = new QueryParser("path", new SimpleAnalyzer()).parse("test\\/index");
BooleanQuery.Builder builder = new BooleanQuery.Builder();
builder.add(queryContent, BooleanClause.Occur.MUST);
builder.add(boost, BooleanClause.Occur.SHOULD);
builder.add(queryPath, BooleanClause.Occur.MUST);
BooleanQuery query = builder.build();
TopDocs docs = searcher.search(query, 10);
System.out.println("匹配的搜索条数:" + docs.totalHits.value);
for (int i = 0; i < docs.scoreDocs.length; i++) {
ScoreDoc scoreDoc = docs.scoreDocs[i];
System.out.println("获取Document文档 docId=" + scoreDoc.doc + ",匹配打分=" + scoreDoc.score);
Document doc = searcher.doc(scoreDoc.doc);
Iterator<IndexableField> iterator = doc.iterator();
while (iterator.hasNext()) {
IndexableField field = iterator.next();
String fieldValue = doc.get(field.name());
System.out.println("[fieldName]=" + field.name() + ",[fieldValue]=" + fieldValue);
}
System.out.println();
}
}
Lucene 建立了大数据检索的基础,其基于奥卡姆剃刀的原则,提供检索工具包而不提供更多应用功能。使得Lucene项目能够更专注于构建索引和搜索,也便于其他应用项目的集成与扩展。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。