1.关于全文检索需要懂的
什么叫做全文检索呢?这要从我们生活中的数据说起。我们生活中的数据总体分为两种:
- 结构化数据:指具有固定格式或有限长度的数据,如数据库,元数据等。
- 非结构化数据(也叫全文数据) :指不定长或无固定格式的数据,如邮件,word文档等。
- 半结构化数据:如XML,HTML等,当根据需要可按结构化数据来处理,也可抽取出纯文本按非结构化数据来处理。
按照数据的分类,搜索也分为两种:
- 对结构化数据的搜索 :如对数据库的搜索,用SQL语句。再如对元数据的搜索,如利用windows搜索对文件名,类型,修改时间进行搜索等。
- 对非结构化数据的搜索 :如利用windows的搜索也可以搜索文件内容,Linux下的grep命令,再如用Google和百度可以搜索大量内容数据。
对非结构化数据也即对全文数据的搜索主要有两种方法:
- 顺序扫描法 (Serial Scanning)
所谓顺序扫描,比如要找内容包含某一个字符串的文件,就是一个文档一个文档的看,对于每一个文档,从头看到尾,如果此文档包含此字符串,则此文档为我们要找的文件,接着看下一个文件,直到扫描完所有的文件。如利用windows的搜索也可以搜索文件内容,只是相当的慢。如果你有一个80G硬盘,如果想在上面找到一个内容包含某字符串的文件,不花他几个小时,怕是做不到。Linux下的grep命令也是这一种方式。
这种方法比较原始,但对于小数据量的文件,这种方法还是最直接,最方便的。但是对于大量的文件,这种方法就很慢了。
2.全文检索
将非结构化数据中的一部分信息提取出来,重新组织,使其变得有一定结构,然后对此有一定结构的数据进行搜索,从而达到搜索相对较快的目的。
正排索引
根据文档找词!
为什么顺序扫描的速度慢:其实是由于我们想要搜索的信息和非结构化数据中所存储的信息不一致造成的。
倒排索引
非结构化数据中所存储的信息是每个文件包含哪些字符串,也即已知文件,欲求字符串相对容易,也即是从文件到字符串的映射。而我们想搜索的信息是哪些文件包含此字符串,也即已知字符串,欲求文件,也即从字符串到文件的映射。两者恰恰相反。于是如果索引总能够保存从字符串到文件的映射,则会大大提高搜索速度。
倒排索引一般表示为一个关键词,然后是它的频度(出现的次数),位置(出现在哪一篇文章或网页中,及有关的日期,作者等信息),它相当于为互联网上几千亿页网页做了一个索引,好比一本书的目录、标签一般。想看哪一个主题相关的章节,直接根据目录即可找到相关的页面。不必再从书的第一页到最后一页,一页一页的查找。
例如:
文章1 | 超级塞亚人 | 我是超级塞亚人我喜欢吃苹果,我不是天朝的人,也不是地球人 |
文章2 | 天朝大国 | 我大天朝威武,我大天朝13亿人,我大天朝 |
文章3 | 我喜欢游泳 | 游泳有很多好方法 |
文章4 | 动画片 | 喜欢看动画片,尤其是七龙珠,因为里面有塞亚人,而且塞亚人喜欢吃苹果,他们不是地球人 |
文章5 | 运动 | 喜欢运动,喜欢跑步,喜欢游泳,喜欢健身,喜欢打球 |
文章6 | 游戏 | 我喜欢打游戏,我最幸福的时光就是在天朝吃着苹果玩游戏 |
关键字:塞亚人 苹果 天朝 地球 七龙珠 游泳 喜欢
塞亚人 | 文章1[1],文章4[2], n n n |
苹果 | 文章1[1],文章4[1],文章6[1], n n n |
天朝 | 文章1[1],文章2[2],文章6[1], n n n n |
地球 | 文章1[1],文章4[1], n n |
游泳 | 文章3[1],文章5[1], n n |
七龙珠 | 文章4[1], n |
喜欢 | 文章1[1],文章4[2],文章5[4],文章6[1], n n n n n n n n |
倒排索引2个重要构成:
- 倒排文件(inverted file):存储倒排索引的物理文件
- 倒排索引组成:单词词典和倒排文件。
2.何为倒排索引
核心术语
Index
Lucene的索引(Index)是由多个文件组成,这些文件被存放在同一目录下。
Token
词元(Token)在Lucene与在自然语言处理(NLP)中的概念相同,表示“词元”。词元即自然语言中的基本单位:在中文表现为一个独立的字或词,在英文中表现为一个单词。
Term
经过分词和语言处理后得到的字符串,它是索引的最小单位。
Field
不同的域可以指定不同的索引方式,比如指定不同的分词方式,是否构建索引,是否存储等。
数值型 | int、bigint、double | IntPoint、LongPoint、DoublePoint | |
字符型 | varchar | StringField | 不做分词处理 |
文本型 | text | TextField | 会做分词处理 |
日期型 | date、timestamp、datetime | - | Lucene没有日期型,可将其转为数值型或字符型 |
Document
文档是索引的基本单位,代表一些域(Field)的集合。在Lucene中,Document是一种逻辑文件。可以近似地认为它表示的是计算机中的一个文件。这个Document是一种抽象的表示,它从各种维度来描述一个数据源中的每一条数据。
可以使用关系型数据库中的记录来理解Document,数据库的每一条记录可以表示为一个Document,而每一列可以用Field来表示。但不同的是,Document并非结构化的,并没有schema的约束。不同的文档保存在不同的段中的,一个段中可以包含多篇文档。新添加的文档被单独保存在一个新生成的段中,随着段的合并,不同的文档会合并到至相同的段中。
Segment
一个Lucene的索引(Index)由多个段组成,段与段之间是独立的。当添加索引时会生成新的Document,但并不是把新的Document 立即添加到同一个索引文件,而是把它们先被写入到不同的小文件(Segment),当小文件的个数达到阈值(段的个数,段中包含的文件数等)时,然后再合并成一个大索引文件(不同的段可以合并)。
Directory
Lucene索引的存放位置。
文档模型
文件名称 | 扩展名 | 说明 |
Segments File | segments_N | 保存提交点的信息 |
Lock File | write.lock | 写文件锁,用于防止多个IndexWriters同时写一个文件 |
Segment Info | .si | 保存段的元数据信息 |
Compound File | .cfs, .cfe | 采用混合格式下该文件包括其他所有格式的文件 |
Fields | .fnm | 保存域信息 |
Field Index | .fdx | 保存指向域数据的指针 |
Field Data | .fdt | 保存域数据 |
Term Dictionary | .tim | 保存词项信息 |
Term Index | .tip | Term Dictionary的索引信息 |
Frequencies | .doc | 记录文档信息,以及文档中词项对应的词频 |
Positions | .pos | 记录词项的位置信息 |
Payloads | .pay | 全文索引的字段,使用了一些像payloads的高级特性会有该文件,保存了term在doc中的一些高级特性 |
Norms | .nvd, .nvm | 文件保存索引字段加权数据 |
Per-Document Values | .dvd, .dvm | lucene的docvalues文件,即数据的列式存储,用作聚合和排序 |
Term Vector Index | .tvx | 记录文档数据记录中的偏移量 |
Term Vector Documents | .tvd | 记录有词项向量的文档的信息 |
Term Vector Fields | .tvf | 词项向量的域级别信息 |
Live Documents | .liv | 存活文件的信息 |
Point values | .dii, .dim | 记录被索引的点 |
Segment_N
Segments_N为段的元数据信息文件。保存了此索引包含多少个段,每个段包含多少篇文档等信息。
Lucene当前活跃的Segment都会存在一个Segment Info文件里,也就是segments_N。如果有多个segments_N, 那么序号最大的就是最新的。
- Format:索引文件格式的版本号。由于Lucene是在不断开发过程中的,不同版本的Lucene可能有不同索引文件格式,所以规定了文件格式的版本号。
- Version:索引的版本号
- NameCount:下一个新段的段名
- SegCount:段的个数
域文件格式
域元数据文件(fnm)
一个段(Segment)包含多个域,每个域都有一些元数据信息,保存在.fnm文件中,.fnm文件的格式如下:
域的数据信息存储在 .fdt 和 .fdx 文件中。其中,.fdx 文件中存放FieldValuesPositon,指向.fdt文件,也就是说域的具体数据是存放在fdt文件中的。
域数据文件(fdt)
真正保存域(stored field)信息的是fdt文件。
假如在一个Segment中包含N篇文档,那么fdt文档中一会有N个项,每一个项都保存对应文档的域信息。
- FieldCount,表示此文档包含的域数目
- 紧接着是FieldCount域信息项,每个项保存一个域的信息。对于每一个域的信息:
- FieldNum是域编号
- 接着是一个8bit的byte。根据填充值(0或1),代表不同的意义。最低一位表示此域是否分词;倒数第二位表示此域保存的是字符串数据还是二进制数据;倒数第三位表示此域是否被压缩。最后存储的是这个存储域的值。
域索引文件(fdx)
由域数据文件格式可知,每篇文档包含的域的个数、每个存储域的值都是不一样的。
因为segment中会包含N篇文档(Document),每篇文档占用的大小也是不一样的,那么如何快速在fdt文件中辨别每一篇文档的起始地址和终止地址?如何能够更快的找到第N篇文档的存储域的信息呢?
这就需要借助域索引文件。域索引文件也总共有N个项(如果segment中包含N篇Document),每篇文档都对应一个项,每一项中都存储一个Long型数值(大小固定),该数值代表文档在fdt中的起始地址偏移量。
词向量文件
词向量信息是从索引(Index)到文档(Document)到域(Field)到词(Term)的正向信息,有了词向量信息,就可以得到一篇文档包含哪些词的信息 。
词向量数据文件(tvd)
首先,是此文档包含域的个数:NumFields
- 最后,是(NumField - 1)大小的数组。每一篇文档的第一个域的偏移量信息存储在 tvx 文件中。而其他(NumFields - 1)域的偏移量就是第一个域的偏移量加上第(NumField - 1)个数组的值。
词向量索引文件(tvx)
一个段(segment)包含N篇文档,此文件就有N项,每一项代表一篇文档。每一项包含两部分信息:
- 第一部分是词向量文档文件(tvd)中此文档的偏移量
- 第二部分是词向量文件(tvf)中此文档的第一个域的偏移量
词向量域文件(tvf)
该文件包含了此段(Segment)中的所有域,并且不对文档做区分,到底第几个域到第几个域是属于那篇文件,是由tvx文件中的第一个域的偏移量以及tvd文件中的(NumField - 1)个域的偏移量来决定哪些域数据属于哪个文档。
对于每一个域,包含如下项:
- 首先,是说明此域中包含的多少(NumTerms)个词(Term)
- 之后,是8bit的byte(最后一位指定是否保存位置信息,倒数第二位表示是否保存偏移量信息)
- 最后,NumTerms个项的数组,每一项代表一个词(Term)。每一个词,由如下几部分构成
- 词的文本TermText
- 词频TermFreq(词在该文档中出现的次数)
- 词的位置
- 词的偏移量
3.创建倒排索引流程
全文检索的索引创建过程一般有以下几步:
第一步(原始素材)
为了方便说明索引创建过程,这里特意用两个文件为例:
- 文件一:Students should be allowed to go out with their friends, but not allowed to drink beer.
- 文件二:My friend Jerry went to school to see his students but found them drunk which is not allowed.
第二步(分词)
将原文档传给分词器(Tokenizer),分词器会做以下几件事情( 此过程称为Tokenize) :
- 将文档分成一个一个单独的单词。
- 去除标点符号。
- 去除停词(Stop word) 。
所谓停词(Stop word)就是一种语言中最普通的一些单词,由于没有特别的意义,因而大多数情况下不能成为搜索的关键词,因而创建索引时,这种词会被去掉而减少索引的大小。
每一种语言的分词组件(Tokenizer),都有一个停词(stop word)集合。
经过分词(Tokenizer) 后得到的结果称为词元(Token) ,如下:
“Students”,“allowed”,“go”,“their”,“friends”,“allowed”,“drink”,“beer”,“My”,“friend”,“Jerry”,“went”,“school”,“see”,“his”,“students”,“found”,“them”,“drunk”,“allowed”。 |
第三步(语言处理)
将得到的词元(Token)传给语言处理组件(Linguistic Processor),语言处理组件(linguistic processor)主要是对得到的词元(Token)做一些同语言相关的处理。
对于英语,语言处理组件(Linguistic Processor) 一般做以下几点:
- 变为小写(Lowercase) 。
- 将单词缩减为词根形式,如“cars ”到“car ”等。这种操作称为:stemming 。
- 将单词转变为词根形式,如“drove ”到“drive ”等。这种操作称为:lemmatization 。
经过语言处理,得到的词(Term)如下:
“student”,“allow”,“go”,“their”,“friend”,“allow”,“drink”,“beer”,“my”,“friend”,“jerry”,“go”,“school”,“see”,“his”,“student”,“find”,“them”,“drink”,“allow” |
第四步(构建索引)
将得到的词(Term)传给索引组件(Indexer),索引组件(Indexer)主要做以下几件事情:
- 利用得到的词(Term)创建一个字典。
Term | 文档编号 |
student | 1 |
allow | 1 |
go | 1 |
their | 1 |
friend | 1 |
allow | 1 |
drink | 1 |
beer | 1 |
my | 2 |
friend | 2 |
jerry | 2 |
go | 2 |
school | 2 |
see | 2 |
his | 2 |
student | 2 |
find | 2 |
them | 2 |
drink | 2 |
allow | 2 |
- 对字典按字母顺序进行排序。
- 合并相同的词(Term) 成为文档倒排(Posting List) 链表。
4.Lucene构建索引代码示例
版本需求
JDK 1.8
Lucene 7.7.0
具体代码
pom.xml
<properties>
<java.version>1.8</java.version>
<lucene.version>7.7.0</lucene.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
<version>${lucene.version}</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-common</artifactId>
<version>${lucene.version}</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-queries</artifactId>
<version>${lucene.version}</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-queryparser</artifactId>
<version>${lucene.version}</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-smartcn</artifactId>
<version>${lucene.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
创建索引代码示例:
import org.apache.commons.io.FileUtils;
import org.apache.lucene.analysis.cn.smart.SmartChineseAnalyzer;
import org.apache.lucene.document.*;
import org.apache.lucene.index.*;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.*;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
public class LuceneIndexDemo {
// 创建索引
public void createIndex() throws IOException {
// 原始文档的路径
File path = new File("D:/Lucene/data");
// 索引存放路径
Directory directory = FSDirectory.open(Paths.get("D:/Lucene/index"));
// 创建标准分析器
SmartChineseAnalyzer analyzer = new SmartChineseAnalyzer();
// 创建IndexWriterConfig
IndexWriterConfig iwc = new IndexWriterConfig(analyzer);
// 是否使用复合索引格式
iwc.setUseCompoundFile(true);
//创建IndexWriter
IndexWriter indexWriter = new IndexWriter(directory, iwc);
for (File file : path.listFiles()) {
// 创建Document对象
Document document = new Document();
// 文件名称
document.add(new TextField("filename", file.getName(), Field.Store.YES));
// 文件内容
document.add(new TextField("content", FileUtils.readFileToString(file, "GBK"),
Field.Store.YES));
// 文件路径
document.add(new StoredField("path", file.getPath()));
// 文件大小
document.add(new NumericDocValuesField("size", FileUtils.sizeOf(file)));
// 写入索引
indexWriter.addDocument(document);
}
// 写入完毕,清理工作
if (indexWriter != null) {
indexWriter.close();
indexWriter = null;
}
}
}
标签:文件,索引,认识,搜索引擎,lucene,全文检索,文档,org,Document
From: https://blog.51cto.com/u_13222507/6476609