本文的核心内容:Lucene索引加权和默认加权方式,Query查询,高亮查询,分页查询。
一:Lucene的默认打分机制
当一个文档出现在了搜索结果中,这就意味着该文档与用户给定的查询语句是相匹配的。Lucene会对匹配成功的文档给定一个分数。至少从Lucene这个层面,从打分公式的结果来看,分数值越高,代表文档相关性越高。
为了计算出一个文档的得分,我们必须考虑如下的因素:
.文档权重(Document boost):在索引时给某个文档设置的权重值。
· 域权重(Field boost):在查询的时候给某个域设置的权重值。
· 调整因子(Coord):基于文档中包含查询关键词个数计算出来的调整因子。一般而言,如果一个文档中相比其它的文档出现了更多的查询关键词,那么其值越大。
· 逆文档频率(Inerse document frequency):基于Term的一个因子,存在的意义是告诉打分公式一个词的稀有程度。其值越低,词越稀有(这里的值是指单纯的频率,即多少个文档中出现了该词;而非指Lucene中idf的计算公式)。打分公式利用这个因子提升包含稀有词文档的权重。
· 长度归一化(Length norm):基于域的一个归一化因子。其值由给定域中Term的个数决定(在索引文档的时候已经计算出来了,并且存储到了索引中)。域越的文本越长,因子的权重越低。这表明Lucene打分公式偏向于域包含Term少的文档。
· 词频(Term frequency):基于Term的一个因子。用来描述给定Term在一个文档中出现的次数,词频越大,文档的得分越大。
· 查询归一化因子(Query norm):基于查询语句的归一化因子。其值为查询语句中每一个查询词权重的平方和。查询归一化因子使得比较不同查询语句的得分变得可行,当然比较不同查询语句得分并不总是那么易于实现和可行的。
Lucene的打分方式:
我们只需要知道在进行文档打分的时候,哪些因素是起决定作用的就可以了。基本上,从前面的公式中可以提炼出以下的几个规则:
· 匹配到的关键词越稀有,文档的得分就越高。
· 文档的域越小(包含比较少的Term),文档的得分就越高。
· 设置的权重(索引和搜索时设置的都可以)越大,文档得分越高。
Lucene加权时机【加权->创建索引时加权】
// StringField 无法加权
// 加权检索不能通配符查询或者模糊查询
// 排名靠前 权值变动 检索结果也会发生变化 权值高的文档排名靠前
TextField name = new TextField("content", contents[i], TextField.Store.YES);
// 加权方法 默认加权权值 1.0f
name.setBoost(3.0f); // 上限
二:Lucene查询的Query
常用 Query 查询
- TermQuery 词元检索
// 基于词元的检索
query = new TermQuery(new Term("name","aa"));
- TermRangeQuery 词元范围检索
// 基于词元范围的检索 字符或者字母范围
// 参一:检索域 参二:起始字符 参三: 结束字符 参四:是不是包含起始 参五:是不是包含结束
query = new TermRangeQuery("name",new BytesRef("aa"),new BytesRef("ae"),false,false);
- NumericRangeQuery 数字范围检索
// 基于数字范围的检索 id 数字 1-5
// 参数一:检索域名 参数二:最小 参数三:最大 参数四:是不是包含最小 参数五:是不是包含最大
query = NumericRangeQuery.newIntRange("id",3,5,true,false);
- PrefixQuery 前缀检索
// 基于前缀的检索 检索参数为 term term 中参数为检索域名 和 前缀
query = new PrefixQuery(new Term("name","a"));
- WildcardQuery 通配查询
// 基于通配符的检索 ? 占位符一个字符 * 匹配零个或者多个字符
query = new WildcardQuery(new Term("name","?b"));
query = new WildcardQuery(new Term("name","*c"));
- FuzzyQuery 模糊查询
// 基于模糊的检索 只要在一定误差范围内都可以检索到 符合条件内容和相似内容
query = new FuzzyQuery(new Term("name","az"));
query = new FuzzyQuery(new Term("content"," 一 "));
- BooleanQuery 布尔查询
// 多条件检索 检索 id 为 1 的文档 并且 content 内容有 “ 海 ” 文档
// BooleanClause.Occur.MUST 并
// BooleanClause.Occur.MUST_NOT 非
// BooleanClause.Occur.SHOULD 或
BooleanQuery booleanQuery = new BooleanQuery();
// name 域中必须有 aa
booleanQuery.add(new TermQuery(new Term("name","aa")), BooleanClause.Occur.MUST);
- PhraseQuery 短语查询
// 基于短语的检索 应用于 英文短语查询 i am gaozhy,welcome to bejing
// 词与词的偏移量
query = new PhraseQuery(2,"content"," 一 "," 三 ");
基于 QueryParser 的高级搜索 ( 可以替换 Query 查询 )
语法: [] {} * ? ~ ^ "" + - AND OR NOT
基于词元的检索 : parse("content: 河 ");
基于词元范围的检索 : parse("name:[aa TO ae}"); 不支持数字
基于前缀和通配符检索 : parse("name:a?"); parse("name:a*");
基于模糊检索 : parse("name:aa~"); “~” 模糊检索
基于 boolean 检索 : AND NOT OR + - 基于短语检索 parse("name:"hello "~1");
三:Lucene的高亮、分页查询
/**
* 分页检索 高亮效果
* limit 0,10
* 1. 第一种方式:将所有的结果检索,对结果直接截取分页 lucene
* 2. 第二种方式: searchAfter(ScoreDoc,query,pageSize);
*/
@Test
public void searchByHighLighter(){
int nowPage = 1;
int pageSize = 2;
try {
FSDirectory fsDirectory =
FSDirectory.open(Paths.get("D:\\lucene\\index\\secondIndex"));
DirectoryReader directoryReader = DirectoryReader.open(fsDirectory);
// 所有文档数
directoryReader.maxDoc();
// 有索引的文档数
directoryReader.numDocs();
IndexSearcher indexSearcher = new IndexSearcher(directoryReader);
QueryParser queryParser = new QueryParser("content",new StandardAnalyzer());
Query query = queryParser.parse(" 爱 ");
TopDocs topDocs = null;
// 第一页直接检索
if(nowPage==1){
topDocs = indexSearcher.search(query,pageSize);
}else{
// 计算 start
int start = (nowPage-1)*pageSize;
topDocs = indexSearcher.search(query,start);
// 最后一个 ScoreDoc 对象
// 参数一:当前页的上一页的最后一个 ScoreDoc 对象 参数二:检索条件 参数:查几条
// searchAfter :从参数一的 ScoreDoc 对象后 进行检索,并检索参数三 条文档
topDocs = indexSearcher.searchAfter(topDocs.scoreDocs[topDocs.scoreDocs.length-1],query,pageSize);
}
// 获取命中数 符合条件总条数
// 总页数:
System.out.println(" 总命中数: "+topDocs.totalHits);
// 指定高亮格式
SimpleHTMLFormatter formatter = new SimpleHTMLFormatter("<span style='color:red'>", "</span>");
// 创建 Scorer
QueryScorer queryScorer = new QueryScorer(query);
// 创建高亮器
Highlighter highlighter = new Highlighter(formatter,queryScorer);
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
for (ScoreDoc scoreDoc : scoreDocs) {
System.out.println(" 文档得分: " + scoreDoc.score);
int docId = scoreDoc.doc;
Document document = indexSearcher.doc(docId);
String highlighterBestFragment =highlighter.getBestFragment(new StandardAnalyzer(),"content",document.get("content"));
System.out.println(document.get("id") + " " + document.get("name") + " " + highlighterBestFragment + " " + document.get("birthday"));
}
} catch (Exception e) {
e.printStackTrace();
}
}