编程开源技术交流,分享技术与知识

网站首页 > 开源技术 正文

Lucene搜索详解(lucence索引)

wxchong 2024-10-23 15:50:20 开源技术 7 ℃ 0 评论

上篇说了Lucene索引,建好了索引,后面就是需要查询搜索了。


Lucene搜索代码示例

public class SearchBaseFlow {

    public static void main(String[] args) throws IOException, ParseException {

    // 使用的分词器

    Analyzer analyzer = new IKAnalyzer4Lucene7(true);

    // 索引存储目录

    Directory directory = FSDirectory.open(Paths.get("f:/test/indextest"));

    // 索引读取器

    IndexReader indexReader = DirectoryReader.open(directory);

    // 索引搜索器

    IndexSearcher indexSearcher = new IndexSearcher(indexReader);

    // 要搜索的字段

    String filedName = "name";

    // 查询生成器(解析输入生成Query查询对象)

    QueryParser parser = new QueryParser(filedName, analyzer);

    // 通过parse解析输入(分词),生成query对象

    Query query = parser.parse("Thinkpad");

    // 搜索,得到TopN的结果(结果中有命中总数,topN的scoreDocs(评分文档(文档id,评分)))

    TopDocs topDocs = indexSearcher.search(query, 10);   //前10条

    //获得总命中数

    System.out.println(topDocs.totalHits);

    // 遍历topN结果的scoreDocs,取出文档id对应的文档信息

    for (ScoreDoc sdoc : topDocs.scoreDocs) {

        // 根据文档id取存储的文档

        Document hitDoc = indexSearcher.doc(sdoc.doc);

        // 取文档的字段

        System.out.println(hitDoc.get(filedName));

    }

    // 使用完毕,关闭、释放资源

    indexReader.close();

    directory.close();

    }

}

核心API图示:


IndexReader 索引读取器

Open一个读取器,读取的是该时刻点的索引视图。如果后续索引发生改变,需重新open一个读取器。获得索引读取器的方式:

  • DirectoryReader.open(IndexWriter indexWriter);优先使用
  • DirectoryReader.open(Directory)
  • DirectoryReader.openIfChanged(DirectoryReader);共享当前reader资源重新打开一个(当索引变化时)

IndexReader分为两类:



  • 叶子读取器:支持获取stored fields, doc values, terms(词项), and postings (词项对应的文档)
  • 复合读取器,多个读取器的复合。只可直接用它获取stored fields 。在内部通过CompositeReader.getSequentialSubReaders 得到里面的叶子读取器来获取其他数据。

DirectoryReader 是 复合读取器,IndexReader是线程安全的。

IndexSearcher 索引搜索器

应用通过调用它的search(Query,int)重载方法在一个IndexReader上实现搜索。出于性能的考虑,请使用一个IndexSearcher实例,除非索引发生变化。如索引更新了则通过DirectoryReader.openIfChanged(DirectoryReader) 取得新的读取器,再创建新的搜索器。IndexSearcher是线程安全的。

TopDocs 搜索命中的结果集 (Top-N)

TopFieldDocs 按字段排序的搜索命中结果集


ScoreDoc


Query 查询的表示。

它的可实例化子类有:

  • TermQuery
  • BooleanQuery
  • WildcardQuery
  • PhraseQuery
  • PrefixQuery
  • MultiPhraseQuery
  • FuzzyQuery
  • RegexpQuery
  • TermRangeQuery
  • PointRangeQuery
  • ConstantScoreQuery
  • DisjunctionMaxQuery
  • MatchAllDocsQuery

它们是lucene中的基本查询。我们可以用它们来创建查询。

TermQuery 词项查询

TermQuery tq = new TermQuery(new Term("fieldName", "term"));

词项查询,最基本、最常用的查询。用来查询指定字段包含指定词项的文档。

TermQuery tq = new TermQuery(new Term(“name", “thinkpad"));


BooleanQuery 布尔查询

搜索的条件往往是多个的,如要查询名称包含“电脑” 或 “thinkpad”的商品,就需要两个词项查询做或合并。布尔查询就是用来组合多个子查询的。每个子查询称为布尔字句 BooleanClause,布尔字句自身也可以是组合的。

组合关系支持如下四种:

  • Occur.SHOULD 或
  • Occur.MUST 且
  • Occur.MUST_NOT 且非
  • Occur.FILTER 同 MUST,但该字句不参与评分

布尔查询默认的最大字句数为1024,在将通配符查询这样的查询rewriter为布尔查询时,往往会产生很多的字句,可能抛出TooManyClauses 异常。可通过BooleanQuery.setMaxClauseCount(int)设置最大字句数。

BooleanQuery 布尔查询示例

// 布尔查询

Query query1 = new TermQuery(new Term(filedName, "thinkpad"));

Query query2 = new TermQuery(new Term("simpleIntro", "英特尔"));

BooleanQuery.Builder booleanQueryBuilder = new BooleanQuery.Builder();

booleanQueryBuilder.add(query1, Occur.SHOULD);

booleanQueryBuilder.add(query2, Occur.MUST);

BooleanQuery booleanQuery = booleanQueryBuilder.build();

// 可像下一行这样写

// BooleanQuery booleanQuery = new BooleanQuery.Builder()

//     .add(query1, Occur.SHOULD).add(query2, Occur.MUST).build();

PhraseQuery 短语查询

最常用的查询,匹配特点序列的多个词项。PhraserQuery使用一个位置移动因子(slop)来决定任意两个词项的位置可最大移动多少个位置来进行匹配,默认为0。有两种方式来构建对象:

所有加入的词项都匹配才算匹配(即使是你在同一位置加入多个词项)。如果需要在同一位置匹配多个同义词中的一个,适合用MultiPhraseQuery。

PhraseQuery 短语查询示例

PhraseQuery phraseQuery1 = new PhraseQuery("name", "thinkpad",

    "carbon");

PhraseQuery phraseQuery2 = new PhraseQuery(1, "name", "thinkpad",

    "carbon");

PhraseQuery phraseQuery3 = new PhraseQuery("name", "笔记本电脑", "联想");

PhraseQuery phraseQuery4 = new PhraseQuery.Builder()

    .add(new Term("name", "笔记本电脑"), 4)

    .add(new Term("name", "联想"), 5).build();

// 这两句等同

PhraseQuery phraseQuery5 = new PhraseQuery.Builder()

    .add(new Term("name", "笔记本电脑"), 0)

    .add(new Term("name", "联想"), 1).build();

PhraseQuery slop 移动因子说明

String name = "ThinkPad X1 Carbon 20KH0009CD/25CD 超极本轻薄笔记本电脑联想";

1、如果想用 “thinkpad carbon” 来匹配 name。因中间有 x1,则需要将thinkpad 向右移动1个位置。

2、如果想用 “carbon thinkpad” 来匹配 name。因中间有 x1,则需要将carbon 向右移动3个位置。

// String name = "ThinkPad X1 Carbon 20KH0009CD/25CD 超极本轻薄笔记本电脑联想";


// PhraseQuery 短语查询

PhraseQuery phraseQuery2 = new PhraseQuery(1, "name", "thinkpad","carbon");

// slop示例

PhraseQuery phraseQuery2Slop = new PhraseQuery(3, "name", "carbon", "thinkpad");

PhraseQuery phraseQuery3 = new PhraseQuery("name", "笔记本电脑", "联想");

// slop示例

PhraseQuery phraseQuery3Slop = new PhraseQuery(2, "name", "联想","笔记本电脑");

MultiPhraseQuery 多重短语查询

短语查询的一种更通用的用法,支持同位置多个词的OR匹配。通过里面的Builder来构建MultiPhraseQuery:

MultiPhraseQuery 多重短语查询示例

// 4 MultiPhraseQuery 多重短语查询

Term[] terms = new Term[2];

terms[0] = new Term("name", "笔记本");

terms[1] = new Term("name", "笔记本电脑");

Term t = new Term("name", "联想");

MultiPhraseQuery multiPhraseQuery = new MultiPhraseQuery.Builder()

    .add(terms).add(t).build();

// 对比 PhraseQuery在同位置加入多个词 ,同位置的多个词都需匹配,所以查不出。

PhraseQuery pquery = new PhraseQuery.Builder().add(terms[0], 0)

    .add(terms[1], 0).add(t, 1).build();

SpanNearQuery 临近查询(跨度查询)

用于更复杂的短语查询,可以指定词间位置的最大间隔跨度。通过组合一系列的SpanQuery 实例来进行查询,可以指定是否按顺序匹配、slop、gap。


SpanNearQuery 临近查询示例

// SpanNearQuery 临近查询

SpanTermQuery tq1 = new SpanTermQuery(new Term("name", "thinkpad"));

SpanTermQuery tq2 = new SpanTermQuery(new Term("name", "carbon"));

SpanNearQuery spanNearQuery = new SpanNearQuery(

    new SpanQuery[] { tq1, tq2 }, 1, true);

// SpanNearQuery 临近查询 gap slop 使用

SpanNearQuery.Builder spanNearQueryBuilder = SpanNearQuery

    .newOrderedNearQuery("name");

spanNearQueryBuilder.addClause(tq1).addGap(0).setSlop(1)

    .addClause(tq2);

SpanNearQuery spanNearQuery5 = spanNearQueryBuilder.build();

TermRangeQuery 词项范围查询

用于查询包含某个范围内的词项的文档,如以字母开头a到c的词项。词项在反向索引中是排序的,只需指定的开始词项、结束词项,就可以查询该范围的词项。

如果是做数值的范围查询则用 PointRangeQuery 。


参数说明:

field – 字段

lowerTerm – 下边界词

upperTerm – 上边界词

includeLower –是否包含下边界.

includeUpper – 是否包含上边界.

// TermRangeQuery 词项范围查询

TermRangeQuery termRangeQuery 
                = TermRangeQuery.newStringRange("name",  "carbon", "张三", false, true);

PrefixQuery, WildcardQuery, RegexpQuery

  • PrefixQuery 前缀查询

查询包含以xxx为前缀的词项的文档,是通配符查询,如 app,实际是 app*

  • WildcardQuery 通配符查询*表示0个或多个字符,?表示1个字符,\是转义符。通配符查询可能会比较慢,不可以通配符开头(那样就是所有词项了)
  • RegexpQuery 正则表达式查询词项。符合某正则表达式

PrefixQuery, WildcardQuery, RegexpQuery 示例

// PrefixQuery 前缀查询

PrefixQuery prefixQuery = new PrefixQuery(new Term("name", "think"));

// WildcardQuery 通配符查询

WildcardQuery wildcardQuery = new WildcardQuery(

    new Term("name", "think*"));

// WildcardQuery 通配符查询

WildcardQuery wildcardQuery2 = new WildcardQuery(

    new Term("name", "厉害了???"));

// RegexpQuery 正则表达式查询

RegexpQuery regexpQuery = new RegexpQuery(new Term("name", "厉害.{4}"));

FuzzyQuery 模糊查询

简单地与索引词项进行相近匹配,允许最大2个不同字符。常用于拼写错误的容错:如把 “thinkpad” 拼成 “thinkppd”或 “thinkd”,使用FuzzyQuery 仍可搜索到正确的结果。

// FuzzyQuery 模糊查询

FuzzyQuery fuzzyQuery = new FuzzyQuery(new Term("name", "thind"));

FuzzyQuery fuzzyQuery2 = new FuzzyQuery(new Term("name", "thinkd"), 2);

FuzzyQuery fuzzyQuery3 = new FuzzyQuery(new Term("name", "thinkpaddd"));

FuzzyQuery fuzzyQuery4 = new FuzzyQuery(new Term("name", "thinkdaddd"));

数值查询

前提:查询的数值字段必须索引。通过 IntPoint, LongPoint, FloatPoint, or DoublePoint 中的方法构建对应的查询。以IntPoint为例:

// 精确值查询

Query exactQuery = IntPoint.newExactQuery("price", 1999900);

// 数值范围查询

Query pointRangeQuery = IntPoint.newRangeQuery("price", 499900,1000000);

// 集合查询

Query setQuery = IntPoint.newSetQuery("price", 1999900, 1000000,2000000);

QueryParser 查询解析生成器

Lucene QueryPaser包中提供了两类查询解析器

1.传统的解析器?

QueryParser

MultiFieldQueryParser

2.基于新的 flexible 框架的解析器

StandardQueryParser?

传统解析器-单默认字段 QueryParser:

QueryParser parser = new QueryParser("defaultFiled", analyzer);

//parser.setPhraseSlop(2);

Query query = parser.parse("query String");

示例:

// 使用的分词器

Analyzer analyzer = new IKAnalyzer4Lucene7(true);

// 要搜索的默认字段

String defaultFiledName = "name";

// 查询生成器(解析输入生成Query查询对象)

QueryParser parser = new QueryParser(defaultFiledName, analyzer);

// 通过parse解析输入,生成query对象

Query query1 = parser.parse(

        "(name:\"联想笔记本电脑\" OR simpleIntro:英特尔) AND type:电脑 AND price:999900");

传统解析器-多默认字段 MultiFieldQueryParser:

// 传统查询解析器-多默认字段

String[] multiDefaultFields = { "name", "type", "simpleIntro" };

MultiFieldQueryParser multiFieldQueryParser = new MultiFieldQueryParser(

        multiDefaultFields, analyzer);

// 设置默认的组合操作,默认是 OR

multiFieldQueryParser.setDefaultOperator(Operator.OR);

Query query4 = multiFieldQueryParser.parse("笔记本电脑 AND price:1999900");

新解析框架的标准解析器:StandardQueryParser:

StandardQueryParser queryParserHelper = new StandardQueryParser(analyzer);

// 设置默认字段

// queryParserHelper.setMultiFields(CharSequence[] fields);

// queryParserHelper.setPhraseSlop(8);

// Query query = queryParserHelper.parse("a AND b", "defaultField");

Query query5 = queryParserHelper.parse(

    "(\"联想笔记本电脑\" OR simpleIntro:英特尔) AND type:电脑 AND price:1999900","name");

使用查询解析器前需考虑三点:

  1. 查询字符串应是由人输入的,而不应是你编程产生。如果你为了用查询解析器,而在你的应用中编程产生查询字符串,不可取,更应该直接使用基本查询API;
  2. 未分词的字段,应直接使用基本查询API加入到查询中,而不应使用查询解析器;
  3. 对于普通文本字段,使用查询解析器,而其他值字段:如 时间、数值,则应使用基本查询API

查询描述规则语法(查询解析语法):

Term 词项:

单个词项的表示: 电脑

短语的表示: "联想笔记本电脑"

Field 字段:

字段名:

示例: name:“联想笔记本电脑” AND type:电脑

如果name是默认字段,则可写成: “联想笔记本电脑” AND type:电脑

如果查询串是:type:电脑 计算机 手机

注意:只有第一个是type的值,后两个则是使用默认字段。

查询描述规则语法(查询解析语法):

Term Modifiers 词项修饰符:

统配符:

? 单个字符

* 0个或多个字符

示例:te?t test* te*t

注意:通配符不可用在开头。

模糊查询,词后加 ~

示例: roam~

模糊查询最大支持两个不同字符。

示例: roam~1

正则表达式: /xxxx/

示例: /[mb]oat/

临近查询,短语后加 ~移动值

示例: "jakarta apache"~10

查询描述规则语法(查询解析语法):

Term Modifiers 词项修饰符:

范围查询:

mod_date:[20020101 TO 20030101] 包含边界值

title:{Aida TO Carmen} 不包含边界值

词项加权,使该词项的相关性更高,通过 ^数值来指定加权因子,默认加权因子值是1

示例:如要搜索包含 jakarta apache 的文章,jakarta更相关,则:

jakarta^4 apache

短语也可以: "jakarta apache"^4 "Apache Lucene"

查询描述规则语法(查询解析语法):

Boolean 操作符

Lucene支持的布尔操作: AND, “+”, OR, NOT ,"-"

OR

"jakarta apache" jakarta

=

"jakarta apache" OR jakarta

NOT 非

"jakarta apache" NOT "Apache Lucene“


注意:NOT不可单项使用:

NOT “Apache Lucene“ 不可

AND

"jakarta apache" AND "Apache Lucene"


+ 必须包含

+jakarta lucene

- 同NOT

"jakarta apache" -"Apache Lucene“

查询描述规则语法(查询解析语法):

组合 ()

字句组合

(jakarta OR apache) AND website

字段组合

title:(+return +"pink panther")

转义 \

对语法字符: + - && || ! ( ) { } [ ] ^ “ ~ * ? : \ / 进行转义。

如要查询包含 (1+1):2

\(1\+1\)\:2

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表