基本概念
Token
Token在Lucene与在自然语言处理(NLP,Natural-language processing)中的概念相同,表示“词元”。词元即自然语言中的基本单位:在中文表现为一个独立的字或词,在英文中表现为一个单词。
将句子处理成Token的过程,称为分词,而完成分词工作的组件称为分词器(Analyzer)。分词器的选择根据语言、场景而不同:用户可以选择Lucene中内置的分词器,也可以自定义实现分词器。
总的来说,中文分词要比英文分词复杂。本篇介绍英文分词,有关中文分词的内容,后续单独介绍。
在Lucene中,分词通常包含以下步骤:
- 拆分单词(Split):像英文这种带有自然分隔(空格、标点符号)的语言,可以简单地按照分隔符进行拆分。
- 排除停用词(Stop Words):自然语言中含有大量没有实际含义的功能词,在自然语言处理中,遇到这类词,就会停止处理,所以称这些功能词为停用词。停用词通常表现为冠词(eg. a、the)、介词(eg. on、in)、副词(eg. how、when)或连词(eg. and)。在分词时排除停用词,既减少了索引量,又提高了检索效率和效果。关于停用词的段子:从品牌辨识度、图形独特性和美感角度,如何评价中国移动新品牌「and 和」的名称及其 logo 设计? - 傅渥成的回答 - 知乎
- 词干提取( Stemming):英文中含有名词复数、第三人称单数、过去时、进行时等一系列衍生词,一般而言,我们真正关心的是它们的词根。抽取单词词根的过程称为词干提取。主流的Stemming算法有Porter stemming algorithm、Lovins stemming algorithm、Lancaster(Paice/Husk) stemming algorithm。Lucene 使用了 Snowball 语言编写的词干提取算法,并将其封装在了lucene-analyzers-common包中。
- 单词小写化(Lower Case):针对搜索词大小写不敏感的情况,在分词时,会把单词统一转化为小写形式。
Field
Field(域)可以类比关系型数据库中的字段的概念。Field包含三个部分:名称、类型、值。
常用的Field有数值型、字符串型、文本型。
下面是常用的Field与关系型数据库字段的对照关系:
字段类型 | 关系型数据库(以MySQL为例) | Lucene | 说明 |
---|---|---|---|
数值型 | int、bigint、double | IntPoint、LongPoint、DoublePoint | - |
字符串型 | varchar | StringField | StringField中的值不会分词 |
文本型 | text | TextField | TextField中的值会做分词处理 |
日期型 | date、timestamp、datetime | - | Lucene没有提供日期型Field,但是可以根据需要将日期转为数值型或字符串型Field |
Document
Document(文档)是Field的集合。Document可以类比为关系型数据库中的记录,不同的是,Document并非结构化的,并没有schema的约束:
- 不同Document对象中包含的Field并不要求一致(半结构化)
- 同一Document对象可以包含多个同名的Field
举个例子:
一个Document可以描述一个人的信息,可能包含姓名、年龄等字段;
另外一个Document可以描述一本书的信息,可能包含书名、作者等字段,并且可以有多个名为作者的字段。
Directory
Directory是Document的集合,描述了Lucene索引的存放位置,可类比关系型数据库中的数据库:
- JDBC操作关系型数据库时,我们首先获取Connection对象;操作Lucene时,首先要获取Directory对象
- JDBC提供了Statement对象对数据库进行读写;而Lucene的读写操作分别由不同的类实现:IndexWriter负责写操作,IndexReader负责读操作。
数据库有MySQL、Oracle之分,Directory也有不同的实现:
Directory子类 | 描述 |
---|---|
SimpleFSDirectory | 最简单的Directory子类,使用java.io.* API将文件存入文件系统。不能很好支持多线程操作 |
NIOFSDirectory | 使用java.nio.* API将文件保存至文件系统。能很好支持除Windows之外的多线程操作,原因是Sun的JRE在Windows平台上长期存在问题 |
MMapDirectory | 使用内存映射I/O进行文件访问。对于64位JRE来说是一个很好的选择,对于32位JRE并且索引尺寸相对较小时也可以使用该类 |
RAMDirectory | 将所有文件都存入RAM |
FIleSwitchDirectory | 使用两个文件目录,根据文件扩展名在两个目录之间切换使用 |
表格中内容摘自《Lucene In Action》一书
如果选择将索引保存在文件系统中,可以显式指定使用哪个Directory子类,也可以使用Lucene提供的工厂方法,让Lucene为你做最优选择:
org.apache.lucene.store.FSDirectory#open(java.nio.file.Path)
Query
Query类似关系型数据库中的SQL语句。与关系型数据库类似,Lucene提供了以下的基本查询:
查询类型 | 关系型数据库 | Lucene |
---|---|---|
精确查询 | xxx = ? | TermQuery |
范围查询 | xxx BETWEEN? AND ? | PointRangeQuery |
模糊查询 | xxx LIKE '%?%' | PrefixQuery、RegexpQuery |
组合查询 | (...) AND (...) OR (...) | BooleanQuery |
倒排索引
倒排索引(Inverted Index,也叫反向索引)是相较正排索引(Forward Index,也叫正向索引)而言的。
简单来说,正排索引描述的是每个文档包含哪些词;倒排索引描述的是某个词出现在哪些文档中。
假设有如下编号的文档:
1. the fox jumps over the dog
2. god save dog
建正排索引如下:
文档编号 | 包含单词 |
---|---|
1 | {the, fox, jumps, over, dog} |
2 | {god, save, dog} |
建倒排索引如下:
单词 | 包含该单词的文档 |
---|---|
the | {1} |
fox | {1} |
... | ... |
dog | {1, 2} |
不难发现,如果要搜索哪些文档包含给定的单词,正排索引需要O(N)的时间,而倒排索引需要O(1)的时间。
Lucene中的倒排索引要比这复杂得多,不仅存储包含某单词的文档编号,而且会存储该单词在各文档中的出现频率以及位置信息等。
以上是Lucene的基本概念,下一篇开始,会介绍Lucene中的CRUD操作。