1. 文档和域
1.1.文档和域的关系
文档是Lucene索引和搜索的原子单位。
文档为包含一个或多个域的容器,而域则依次包含“真正的”被搜索内容。
每个域都有一个标识名称,该名称为一个文本值或二进制值。
将一个文档加入到索引中时,可以通过一系列选项来控制Lucene的行为。
在对原始数据进行索引时,得先将数据转换成Lucene所能识别的文档和域。
在随后的搜索过程中,被搜索对象则为域值。
1.2. Lucene可以针对域进行3种操作
1) 域值可以被索引或不被索引。
要搜索一个域,则要先对该域进行索引。
被索引的域值必须是文本格式,二进制格式的域值只能被存储而不被索引。
域值 ==>分析后得到语汇单元 ==> 将语汇单元加入到索引中
2) 域被索引后,可以选择性地存储项向量
项向量可以看做是该域的一个小型反向索引集合,
通过该向量能够检索该域的所有语汇单元。
这个机制可以实现一些高级功能,如搜索与当前文档相似的文档。
3) 域值可以被单独存储
是指被分析前的域值备份可以写进索引中,以便后续的检索。
这机制将使你可以将原始域值展现给用户,如文档的标题或摘要。
有时,可能会需要使用杂项域,即包含所有文本的一个独立域以供搜索。
2. Lucene与数据库的区别
A. Lucene没有一个确定的全局模式
即:
加入索引的每个文档都是独立的,它与之前加入的文档完全没有关系;
文档可以包含任意的域,以及任意的索引、存储和项的向量操作选项;
文档也可以不必包含与其它文档相同的域,甚至跟其它文档可以只有相关操作选项有所区别。
这种特性可以随时对文档进行索引,而不必提前设计文档的数据结构表;
如果随后想向文档中添加域,则可以完成添加后重新索引该文档或重建索引即可;
Lucene这种灵活的架构意味着单一的索引可以包含表示不同实体的多个文档。
B. Lucene要求你在进行索引操作时简单化或反向规格化原始数据
反向规格化Donormalization: 解决有关文档真实结构和Lucene表示能力之间的“不匹配”问题
3. Lucene的索引过程
从原始文档 提取文本并建立文档 --> 分析文档 --> 文档索引(向索引添加文档)
A. 提取文本并建立文档
HTML ==> 【 Extract Text 】 ==> 文本
PDF ==> 【 Extract Text 】 ==> 文本
Word ==> 【 Extract Text 】 ==> 文本
提关提取文本信息的细节可结合Tika框架详述,使用该框架能使你很轻易地从各种格式的文件中提取文本信息。
一旦提取出预想的文本信息,并建立起对应的包含各个域的文档后,下一步就是对这些文本信息进行分析了。
B. 分析文档
分析文档是调用IndexWriter对象的addDocument方法将数据传递给Lucene进行索引操作。
索引操作时,Lucene首先分析文本,将文本数据分割成语汇单元,然后对它们执行一些可选操作。
在索引前,对语汇单元的操作:
1) 统一转换为小写,以使搜索不对大小写敏感;
2) 调用StopFilter停词过滤类,从输入中去掉一些使用很频繁即没有实际意义的词;
3) 去掉词干;
4) 然后调用一系列filter过滤类,以便修正语汇单元;
以上这些操作,构成了分析器。
此外,还可以通过链接Lucene的语汇单元和filter来搭建自己的分析器,或通过其它自定义方式来搭建分析器。
分析过程会产生大批的语汇单元,随后这些语汇单元将被写入索引文件中。
C. 文档索引(向索引添加文档)
对输入数据分析完毕后,就可以将分析结果写入索引文件中。
Lucene将输入数据以一种倒排索引(inverted index)的数据结构进行存储。
在进行关键字快速查找时,这种数据结构能够有效利用磁盘空间。
Lucene使用倒排数据结构的原因是:把文档中提取出的语汇单元作为查询关键字,而不是将文档作为中心实体。
倒排索引结构类似于图书的索引与页码的对应关系。
倒排索引不是回答【这个文档中包含哪些单词?】,而是经过优化后用来快速回答【哪些文档包含单词x?】”。
现在所有的Web搜索引擎核心都是采用倒排索引技术。
Lucene的索引文件目录有唯一一个段结构,即索引段(有待具体分析)。
可以将一个段看作一个子索引,尽管每个段都不是一个完全独立的索引。
Lucene的优势之一就是支持增量索引(Incremental indexing),而这个功能主要是靠索引分段实现。
4. 基本索引操作: 添加、删除、更新索引文档Document
4.1. 添加索引文档
Directory dir = null;
//取Writer
private IndexWriter getWriter() throws IOException{
return new IndexWriter(dir, new WhitespaceAnalyzer(), IndexWriter.MaxFieldLength.UNLIMITED);
//IndexWriter构造函数的三个参数:索引存放目录,分析器,域的最大长度
}
protected void setup() throws Exception{
dir = new RAMDirectory();
IndexWriter writer = getWriter();
for(...){
Document doc = new Document();
doc.add(new Field("id", "1", Field.Stores.YES, Field.Index.NOT_ANALYZED));
//Field构造函数的四个参数:域名,域值,域的存储状态(是否存储),域的索引状态(是否分析)
writer.addDocument(doc);
}
writer.commit();//或 writer.close();
}
protected int getHitCount(String fieldName, String searchString) throws Exception{
IndexSearcher searcher = IndexSearcher(dir);
Term t = new Term(fieldName, searchString);
Query query = new TermQuery(t);
int hitCount = searcher.search(query, 1).totalHits;
searcher.close();
return hitCount;
}
4.2. 删除索引中的文档
protected void DelDoc() throws Exception{
IndexWriter writer = getWriter();
writer.deleteDocument(new Term("id", "1"));//删除文档
//writer.hasDeletions(); 是否包含被标记为已删除的文档
//writer.maxDoc(); //返回索引中被删除和未被删除的文档总数
//writer.numDocs(); //返回索引中未被删除的文档总数
writer.optimize();//优化操作使用删除生效,并强制Lucene在删除一个文档后合并索引段
writer.commit();
//或 writer.close();
}
4.3. 更新索引中的文档
protected void ModDoc() throws Exception{
IndexWriter writer = getWriter();
Document doc = new Document()