在完成了文档预处理和索引的建立之后,就可以执行搜索操作了。本文将对Lucene搜索的二三事啰嗦一二。
3. Lucene搜索
3.1 IndexSearcher
Lucene中使用IndexSearcher类进行搜索。此类包含在org.apache.lucene.search包中。
//初始化一个IndexSearcher
IndexSearcher searcher = new IndexSearcher(INDEX_STORE_PATH);
//构建一个Term对象
Term t = new Term("bookname", "女");
//构建一个Query对象
Query q = new TermQuery(t);
//检索
Hits hits = searcher.search(q);
//显示查询结果
for(int i=0; i<hits.length(); i++){System.out.println(hits.doc(i));}
Hits对象表示查找结果,通过它可以访问检索到的Document。
3.2 Hits
Hits类在上文已有介绍,在此处,我们先来看一下它的几个公有接口
//取得当前结果集的数量
public final int length()
//取得当前结果集中第N个Document
public final Document doc(int n) throws IOException
//取得当前结果集中第N个Document的得分
public final float score(int n) throws IOException
//取得当前结果集中第N个Document的索引内部ID值
public final int id(int n) throws IOException
//取得对Hits集合的遍历对象
public Iterator iterator();
在Hits对象内部,保持了一个缓存。它用于存放一定数量的文档。每次用户要取出文档时,Hits都会先访问这个缓存。若缓存中存在该文档,则返回文档,否则执行查询。
3.3 搜索结果的评分
在前面,我们曾多次提到过评分。什么是评分呢?
实际上,通过Searcher检索出来的每个文档都有一个分值,该分值将作为自然排序的依据,即搜索结果输出的次序。
关于文档的分值是如何计算得出,请参考相关文章,这里不作详述。
3.3.1 激励因子
每个Field都会被设置一种激励因子,默认值为1。它增加了Field的重要性,也即增加了Document的重要性。重要性的增加就是指文档得分的增加。可通过Field类的方法setBoost(......) 人为设置激励因子的值。
3.4 Query
Lucene提供了大量Query的子类,可满足各式各样的查询需求,接下来我们分别介绍这些子类。
3.4.1 TermQuery词条搜索
通过对某个固定词条的指定,实现检索索引中存在该词条的所有文档。
IndexSearcher searcher = new IndexSearcher(INDEX_STORE_PATH);
Term t = new Term("bookname", "女");
Query q = new TermQuery(t);
Hits hits = searcher.search(q);
for(int i=0; i<hits.length(); i++){
System.out.println(hits.doc(i));
System.out.println(hits.score(i));
System.out.println(hits.id(i));
}
布尔型查询就是一个由多个子句和子句间的布尔逻辑所组成的查询。此种搜索方式的前提是处理多个查询子句。
BooleanQuery依靠BooleanClause.Occur类处理子句间的逻辑关系。
BooleanClause.Occur有三种表示:MUST、MUST_NOT、SHOULD
顾名思义,即是必须,不准,应当之意。两两组合,公有六种。
1. MUST和MUST
2. MUST和MUST_NOT
3. MUST_NOT和MUST_NOT
并没有实际意义,理解为两个子句的结果都不要,最终结果不会有任何值。
4. SHOULD和MUST
相当于MUST
5. SHOULD和MUST_NOT
相当于2
6. SHOULD和SHOULD
或关系,得并集
3.4.3 RangeQuery范围搜索
查找一定范围内的文档,这种范围可以是时间、日期、数字等。
IndexWriter writer = new IndexWriter(INDEX_STORE_PATH, new StandardAnalyzer(), true);
//不生成复合文件
writer.setUseCompoundFile(false);
Document doc1 = new Document();
Document doc2 = new Document();
Document doc3 = new Document();
Field bookNo1 = new Field("booknumber", "0001", Field.Store.YES, Field.Index.UN_TOKENIZED);
Field bookNo2 = new Field("booknumber", "0002", Field.Store.YES, Field.Index.UN_TOKENIZED);
Field bookNo3 = new Field("booknumber", "0003", Field.Store.YES, Field.Index.UN_TOKENIZED);
doc1.add(bookNo1);
doc2.add(bookNo2);
doc3.add(bookNo3);
writer.addDocument(doc1);
writer.addDocument(doc2);
writer.addDocument(doc3);
writer.close();
IndexSearcher searcher = new IndexSearcher(INDEX_STORE_PATH);
//RangeQuery的下界
Term begin = new Term("booknumber", "0002");
//RangeQuery的上界
Term end = new Term("booknumber", "0003");
//检索位于上、下界间的所有文档
RangeQuery q = new RangeQuery(begin, end, false);
Hits hits = searcher.search(q);
for(int i=0; i<hits.length(); i++){
System.out.println(hits.doc(i));
}
3.4.4 PrefixQuery前缀搜索
顾名思义,根据词条的前缀搜索。
IndexWriter writer = new IndexWriter(INDEX_STORE_PATH, new StandardAnalyzer(), true);
//不生成复合文件
writer.setUseCompoundFile(false);
Document doc1 = new Document();
Document doc2 = new Document();
Document doc3 = new Document();
Field bookNo1 = new Field("bookname", "三侠五义", Field.Store.YES, Field.Index.UN_TOKENIZED);
Field bookNo2 = new Field("bookname", "三国演义", Field.Store.YES, Field.Index.UN_TOKENIZED);
Field bookNo3 = new Field("bookname", "西游记", Field.Store.YES, Field.Index.UN_TOKENIZED);
doc1.add(bookNo1);
doc2.add(bookNo2);
doc3.add(bookNo3);
writer.addDocument(doc1);
writer.addDocument(doc2);
writer.addDocument(doc3);
writer.close();
IndexSearcher searcher = new IndexSearcher(INDEX_STORE_PATH);
//构建一个Term,用于表示要查找的前缀
Term prefix = new Term("bookname", "三");
//构建前缀查询
PrefixQuery q = new PrefixQuery(prefix);
Hits hits = searcher.search(q);
for(int i=0; i<hits.length(); i++){
System.out.println(hits.doc(i));
}
根据短语搜索
IndexWriter writer = new IndexWriter(INDEX_STORE_PATH, new StandardAnalyzer(), true);
//不生成复合文件
writer.setUseCompoundFile(false);
Document doc1 = new Document();
Document doc2 = new Document();
Document doc3 = new Document();
Field bookNo1 = new Field("bookname", "三侠五义", Field.Store.YES, Field.Index.UN_TOKENIZED);
Field bookNo2 = new Field("bookname", "三国演义", Field.Store.YES, Field.Index.UN_TOKENIZED);
Field bookNo3 = new Field("bookname", "西游记", Field.Store.YES, Field.Index.UN_TOKENIZED);
doc1.add(bookNo1);
doc2.add(bookNo2);
doc3.add(bookNo3);
writer.addDocument(doc1);
writer.addDocument(doc2);
writer.addDocument(doc3);
writer.close();
IndexSearcher searcher = new IndexSearcher(INDEX_STORE_PATH);
//构建一个Term,用于表示要查找的前缀
Term t1 = new Term("bookname", "三");
Term t2 = new Term("bookname", "国");
//构建短语查询
PhraseQuery q = new PhraseQuery();
query.add(t1);
query.add(t2);
Hits hits = searcher.search(q);
for(int i=0; i<hits.length(); i++){
System.out.println(hits.doc(i));
}
PhraseQuery提供了一种称为“坡度”的参数,用于表示词组的两个字间可以插入无关字数的个数。默认值为0.
通过public void setSlop(int s)方法即可设置坡度。
IndexSearcher searcher = new IndexSearcher(INDEX_STORE_PATH);
//构建一个Term,用于表示要查找的前缀
Term t1 = new Term("bookname", "三");
Term t2 = new Term("bookname", "义");
//构建短语查询
PhraseQuery q = new PhraseQuery();
query.add(t1);
query.add(t2);
query.setSlop(2);
Hits hits = searcher.search(q);
for(int i=0; i<hits.length(); i++){
System.out.println(hits.doc(i));
}
此处表示三和义之间可插入两个字。
3.4.6 MultiPhraseQuery多短语搜索
对多个短语同时进行检索
IndexWriter writer = new IndexWriter(INDEX_STORE_PATH, new StandardAnalyzer(), true);
//不生成复合文件
writer.setUseCompoundFile(false);
Document doc1 = new Document();
Document doc2 = new Document();
Document doc3 = new Document();
Field bookNo1 = new Field("bookname", "三侠五义", Field.Store.YES, Field.Index.UN_TOKENIZED);
Field bookNo2 = new Field("bookname", "三国演义", Field.Store.YES, Field.Index.UN_TOKENIZED);
Field bookNo3 = new Field("bookname", "西游记", Field.Store.YES, Field.Index.UN_TOKENIZED);
doc1.add(bookNo1);
doc2.add(bookNo2);
doc3.add(bookNo3);
writer.addDocument(doc1);
writer.addDocument(doc2);
writer.addDocument(doc3);
writer.close();
IndexSearcher searcher = new IndexSearcher(INDEX_STORE_PATH);
//构建短语查询
MultiPhraseQuery q = new MultiPhraseQuery();
//加入要查找的短语的前缀
query.add(new Term("bookname", "三"));
//构建两个Term,作为短语的后缀
Term t1 = new Term("bookname", "国");
Term t2 = new Term("bookname", "侠");
query.add(new Term[]{t1,t2});
Hits hits = searcher.search(q);
for(int i=0; i<hits.length(); i++){
System.out.println(hits.doc(i));
}
使用levenshtein作为匹配算法。这种算法在比较两个字符串时,将动作分为三种:
1. 加一个字母
2. 删一个字母
3. 改变一个字母
两个字符串进行比较时,实质就是执行将其中一个字符串转变为另一个。没执行上面一种动作,将扣除一定分数。比较完毕后,最终得分成为两者之间的距离,也叫模糊度。
分数越高,匹配度越高,默认最小相似度为0.5。该值可在实例化FuzzyQuery时作为参数加入。另一个参数prefixLength表示在进行模糊匹配时要有多少个前缀字母要完全匹配。
IndexSearcher searcher = new IndexSearcher(INDEX_STORE_PATH);
Term t = new Term("bookname", "三");
FuzzyQuery q = new FuzzyQuery(t, 0.1f, 1);
Hits hits = searcher.search(q);
for(int i=0; i<hits.length(); i++){
System.out.println(hits.doc(i));
}
3.4.8 WildcardQuery通配符搜索
通配符不用多做解释了,重新啰嗦几句它的基本符号
1. *:表示0到多个字符
2. ?:表示一个单一字符
IndexSearcher searcher = new IndexSearcher(INDEX_STORE_PATH);
Term t = new Term("bookname", "?国*");
WildcardQuery q = new WildcardQuery(t);
Hits hits = searcher.search(q);
for(int i=0; i<hits.length(); i++){
System.out.println(hits.doc(i));
}
3.4.9.1 多域搜索MultiFieldQueryParser
该类可完成三种搜索
1. 在不同的Field上进行不同的查找
2. 在不同的Field上进行同一个查找,指定它们之间的布尔关系
3. 在不同的Field上进行不同的查找,指定它们之间的布尔关系
3.4.9.2 MultiSearcher在多个索引上搜索
在不同的索引上执行不同的搜索(当然在同一索引执行多种搜索然后Boolean也是可以的)。MultiSearcher的原理是对一个IndexSearcher的数组进行循环遍历。分明进行查找,然后将结果进行合并,使用一个HitCollector收集后返回。
3.4.9.3 ParalellMultiSearcher多线程搜索
多线程版本的MultiSearcher
但是效率不一定比MultiSearcher高,和计算机性能有关