-
Lucene主要的API
类 目的 IndexSearcher 搜索索引的入口,调用重载的search方法 Query及其子类 封装某种查询类型的具体子类,作为search方法的参数 QueryParser 将用户输入的查询表达式处理成具体的Query的对象 TopDocs 保持由IndexSearcher.search()方法返回的具有较高评分的顶部文档 ScoreDoc TopDocs中每条搜索结果的访问接口 -
对特定项的搜索 —— 使用 TermQuery
public void testTerm() throws Exception { // 1. 打开索引文件所在的目录,委托给 Directory 对象 Directory directory = FSDirectory.open(new File("./index")); // 2. 创建 IndexSearcher 对象 IndexSearcher indexSearcher = new IndexSearcher(directory); // 3. 创建 TermQuery Term term = new Term("subject", "ant"); Query query = new TermQuery(term); // 4. 返回 TopDocs 结果 TopDocs docs = indexSearcher.search(query, 10); ... // 这里可以使用 docs.scoreDocs 获取细节 // 5. 关闭 IndexSearcher 对象和 Directory 对象 indexSearcher.close(); directory.close(); }
在一个程序中,最好让 IndexSearcher 对象和 Directory 对象保持打开状态,最后结束时再关闭。因为重新打开要花费代价。
-
解析用户输入的查询表达式 —— QueryParser
public void testQueryParser() throws Exception { // 1. 打开索引文件所在的目录,委托给 Directory 对象 Directory directory = FSDirectory.open(new File("./index")); // 2. 创建 IndexSearcher 对象 IndexSearcher indexSearcher = new IndexSearcher(directory); // 3. 创建 QueryParser 对象, QueryParser parser = new QueryParser( Version.LUCENE_30, "contents", new SimpleAnalyzer()); // Example 1 // 4. 解析 "+JUNIT +ANT -MOCK" 类型的查询 Query query = parser.parse("+JUNIT +ANT -MOCK"); / TopDocs docs = indexSearcher.search(query, 10); assertEquals(1, docs.totalHits); Document d = indexSearcher.doc(docs.scoreDocs[0].doc); assertEquals("Ant in Action", d.get("title")); // Example 2 // 4. 解析 "mock OR junit" 类型的查询 query = parser.parse("mock OR junit"); docs = indexSearcher.search(query, 10); assertEquals("Ant in Action, " + "JUnit in Action, Second Edition", 2, docs.totalHits); // 关闭 IndexSearcher 对象和 Directory 对象 indexSearcher.close(); directory.close(); }
QueryParser将用户输入转换为可以处理的Query
QueryParser parser = new QueryParser( Version matchVersion, String defaultField, Analyzer analyzer);
QueryParser处理的表达式范例 P75
查询表达式 匹配Doc java 默认Field包含java的文档 java junit java OR junit 默认Field包含java或junit的文档 +java +junit java AND junit 默认Field同时包含java和junit的文档 title:ant title Field包含ant的文档 title:extreme -subject:sports title Field包含extreme且subject Field不包含sports的文档 (agile OR extreme) AND methodology 默认Field包含methodology且包含agile和extreme其中之一的文档 title:“junit in action” title Field恰好为junit in action的文档 title:“junit action” ~5 title Field恰好为junit和action距离小于5的文档 java* 包含由java*开头的文档 java~ 包含与单词java相近的项的文档 lastModified:[1/1/09 TO 12/31/09] lastModified项在1/1/09和12/31/09之间的文档 -
使用IndexSearcher类
-
示例
Directory dir = FSDirectory.open(new File("/path/to/index")); IndexReader reader = IndexReader.open(dir); IndexSearcher searcher = new IndexSearcher(reader);
-
上个示例和之前的区别是多了一个 IndexReader 中间层,事实上以前直接使用
IndexSearcher searcher = new IndexSearcher(dir);
的时候,系统后台也会创建私有的IndexReader
值得注意的是,IndexReader和IndexSearcher对象在关闭的时候关闭一个就行,因为IndexSearcher的源码是这样的
public class IndexSearcher extends Searcher { ... public void close() throws IOException { if(closeReader) reader.close(); } ... }
-
几个类的关系
Query ----> IndexSearcher ----> TopDocs | | IndexReader | | Directory | | 索引文件
-
打开IndexReader需要较大的系统开销,因此最好使用同一个IndexReader对象
-
当底层索引更新时,原有的IndexReader看不到更新,必须使用新的IndexReader对象
-
为了同时解决4和5的问题,建议使用reopen方法
IndexReader newReader = reader.reopen(); if (reader != newReader) { reader.close(); reader = newReader; searcher = new IndexSearcher(reader); }
更新IndexReader后别忘了同时更新IndexSearcher
-
search API
TopDocs topDocs = indexSearcher.search(Query query, int n);
返回评分最高的n个Docs
-
TopDocs
public class TopDocs implements java.io.Serializable { /** The total number of hits for the query.*/ public int totalHits; /** The top hits for the query. */ public ScoreDoc[] scoreDocs; /** Stores the maximum score value encountered, needed for normalizing. */ private float maxScore; ... }
totalHits:匹配搜索条件的总Docs数量
maxScore:所有匹配Doc的最大评分
scoreDocs:一个数组,包含top n个匹配Doc的信息,这个数组按照匹配程度进行排序(第一个文档最匹配)
public class ScoreDoc implements java.io.Serializable { /** Expert: The score of this document for the query. */ public float score; /** Expert: A hit document's number. * @see Searcher#doc(int) */ public int doc; ... }
其中,score代表该文档的相关性评分,doc代表文档ID
-
搜索结果分页
(1) 两种方案
1° 首次搜索时多搜一些,然后缓存在ScoreDocs实例中
2° 每次用户换页时重新搜索
(2) 一般方案2°更合理,因为Lucene可以快速处理,这样不需要缓存用户状态
-
近实时搜索
(1) 含义:IndexWriter未commit或close的结果,就会被IndexSearcher看到
(2) 主要API
IndexReader indexReader = indexWriter.getReader();
这种方式可以实现近实时搜索,语义上等价于先commit再open一个IndexReader,但是大大减少commit开销
但是要注意的是这种方法只是不用commit就能看到结果而已,还是要显式IndexReader.reopen()以及更新IndexSearcher对象
-
-
Lucene的评分机制
-
一个没看懂的评分公式 P82
-
使用 explain() 理解搜索结果评分
indexSearcher.explain(Query query, int doc)
-
一个 explain 结果示例
Query: junit ---------- JUnit in Action, Second Edition 0.7629841 = (MATCH) fieldWeight(contents:junit in 8), product of: 1.4142135 = tf(termFreq(contents:junit)=2) 2.466337 = idf(docFreq=2, maxDocs=13) 0.21875 = fieldNorm(field=contents, doc=8) ---------- Ant in Action 0.61658424 = (MATCH) fieldWeight(contents:junit in 6), product of: 1.0 = tf(termFreq(contents:junit)=1) 2.466337 = idf(docFreq=2, maxDocs=13) 0.25 = fieldNorm(field=contents, doc=6)
其中,
0.7629841 = 1.4142135 * 2.466337 * 0.21875
且
0.61658424 = 1.0 * 2.466337 * 0.25
-
-
Lucene的多样化查询
-
创建Query的办法有两种
(1) 直接实例化Query的子类
(2) 通过 QueryParser.query(String userInput) 返回Query实例对象
-
通过项进行搜索——TermQuery
(1) 示例
Directory directory = FSDirectory.open(new File("./index")); IndexSearcher indexSearcher = new IndexSearcher(directory); Term t = new Term("isbn", "9781935182023"); Query query = new TermQuery(t); TopDocs docs = indexSearcher.search(query, 10);
(2) TermQuery在根据关键字查询文档时特别实用,如果文档是通过 Field.Index.NOT_ANALYZED进行索引的
-
在指定的项范围内搜索——TermRangeQuery
(1) 示例
TermRangeQuery query = new TermRangeQuery( "title2", "d", "j", true, true);
创建时的域
doc.add(new Field( "title2", title.toLowerCase(), Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS, Field.TermVector.WITH_POSITIONS_OFFSETS));
(2) TermRangeQuery用于文本范围查询,上面示例的意思是,在title2这个Field里,找[d, j]范围内的Doc
(3) Lucene通常按照字典编排顺序(String.compareTo)来存储Term,也就是说可以很快速的找到某个区间范围
-
在指定的数字范围内搜索——NumericRangeQuery
(1) 如果使用 NumericField 对象来索引域,那么就可以有效使用 NumericRangeQuery 类来在某个特定范围内搜索该域
NumericRangeQuery query = NumericRangeQuery.newIntRange( "pubmonth", 200605, 200609, true, true);
创建域时使用NumericField
doc.add(new NumericField( "pubmonth", Field.Store.YES, true).setIntValue(Integer.parseInt(pubmonth)));
-
通过字符串搜索——PrefixQuery
(1) PrefixQuery搜索包含指定字符串开头的项的文档
Term term = new Term("category", "/technology/computers/programming"); PrefixQuery query = new PrefixQuery(term);
-
组合查询——BooleanQuery
(1) BooleanQuery 可以作为多个查询子句的组合,依靠add API
public void add(Query query, BooleanClause.Occur occur)
其中,Occur是枚举类型,包含三种取值
MUST --- 代表 AND SHOULD --- 代表 OR MUST_NOT --- 代表 NOT
(2) 示例
TermQuery searchingBooks = new TermQuery(new Term("subject", "search")); // 1 Query books2010 = NumericRangeQuery.newIntRange("pubmonth", 201001, 201012, true, true); // 2 BooleanQuery searchingBooks2010 = new BooleanQuery(); // AND two sub-queries searchingBooks2010.add(searchingBooks, BooleanClause.Occur.MUST); searchingBooks2010.add(books2010, BooleanClause.Occur.MUST);
-
通过短语搜索——PhraseQuery
(1) 两个term之间的最大间隔距离slop:
term若要按顺序组成给定的短语所需要的移动位置的次数
示例
原Doc:the quick brown fox jumped over the lazy dog 短语 "quick fox": slop = 1,让fox向右移动1次和原Doc匹配 短语 "fox quick": slop = 3,让fox向右移动3次和原Doc匹配
(2) 多个term同样支持
示例
原Doc:the quick brown fox jumped over the lazy dog 短语 "lazy jumped quick": slop = 8(4+4)
(3) 代码示例
PhraseQuery query = new PhraseQuery(); query.setSlop(slop); // 设置Query的slop值 String[] phrase = new String[]{"lazy", "jumped", "quick"}; for (String word : phrase) { query.add(new Term("field", word)); } TopDocs matches = searcher.search(query, 10); AssertTrue(matches.totalHits > 0);
注:在setSlop之前,slop默认值为0
(4) 短语查询评分
1/(distance + 1)
-
通配符查询——WildcardQuery
(1) Lucene使用两个标准的通配符
1° * 代表0个或多个字母
2° ? 代表0个或1个字母
(2) 示例
Query query = new WildcardQuery(new Term("contents", "?ild*")); indexSingleFieldDocs( new Field[]{ new Field("contents", "wild", Field.Store.YES, Field.Index.ANALYZED), new Field("contents", "child", Field.Store.YES, Field.Index.ANALYZED), new Field("contents", "mild", Field.Store.YES, Field.Index.ANALYZED), new Field("contents", "mildew", Field.Store.YES, Field.Index.ANALYZED)});
(3) 通配符匹配查询对评分没有任何影响
-
搜索类似项——FuzzyQuery
(1) 用于判断一个term内部字母移动的次数,如果
1 - distance / min(textLen, targetLen)
小于阈值则hit
(2) 示例
Query query = new FuzzyQuery(new Term("contents", "wuzza")); indexSingleFieldDocs(new Field[]{ new Field("contents", "fuzzy", Field.Store.YES, Field.Index.ANALYZED), new Field("contents", "wuzzy", Field.Store.YES, Field.Index.ANALYZED) });
-
匹配所有文档——MatchAllDocsQuery
匹配索引中的所有文档
-
-
解析查询表达式——QueryParser
-
通过QueryParser可以将自然语言的查询表达式转换为某个Query子类实例,转换过程由后台完成
转换后的Query对象调用toString()方法可以看到转换后的形式
-
TermQuery
单个词在默认情况下,如果不被识别为更长的其他查询表达式的一部分,就会被QueryParser解析为TermQuery对象
Query query = new QueryParser( Version.LUCENE_30, "subject", new WhitespaceAnalyzer() ).parse("computers);
-
TermRangeQuery
(1) 用TO连接,必须大写
(2) 中括号[]代表inclusive,大括号{}代表exclusive,不支持半开半闭
Query query = new QueryParser( Version.LUCENE_30, "subject", new WhitespaceAnalyzer() ).parse("title2:[Q TO V]"); Query query = new QueryParser( Version.LUCENE_30, "subject", new WhitespaceAnalyzer() ).parse("title2:{Q TO \"Tapestry in Action\"}");
(3) 3.0.2及其以前版本的Lucene不支持自动转换为 NumericRangeQuery
-
前缀查询和通配符查询
(1) 如果某个项中包含了一个?或*,该项被视为通配符查询WildcardQuery;如果某个项只在末尾有一个?,该项被视为前缀查询PrefixQuery
(2) 示例
Query query1 = new QueryParser( Version.LUCENE_30, "field", new WhitespaceAnalyzer() ).parse("PrefixQuery*"); assertTrue(query1 instanceof PrefixQuery); Query query2 = new QueryParser( Version.LUCENE_30, "field", new WhitespaceAnalyzer() ).parse("Prefix?Query*"); assertTrue(query2 instanceof WildcardQuery);
-
布尔查询
(1) 布尔操作符必须是大写形式
AND OR NOT
(2) AND 可以用 + 代替;
OR 可以用 空格代替(默认)
NOT 可以用 - 代替
(3) 示例
Query query = new QueryParser( Version.LUCENE_30, "subject", new WhitespaceAnalyzer() ).parse("(agile OR extreme) AND methodology"); assertTrue(query instanceof BooleanQuery);
-
短语查询
(1) 双引号""括起来的term会被创建一个PhraseQuery,如果内部是多个单词的话,但是分析之后的结果会随着Analyzer的不同而不同,一些the/a之类的可能会被去掉
Query query1 = new QueryParser( Version.LUCENE_30, "field", new StandardAnalyzer(Version.LUCENE_30)) .parse("\"This is Some Phrase*\""); assertTrue(query1 instanceof PhraseQuery); // 短语查询 Query query2 = new QueryParser(Version.LUCENE_30, "field", new StandardAnalyzer(Version.LUCENE_30)) .parse("This is Some Phrase"); assertTrue(query instanceof BooleanQuery); // 布尔查询
注意,没有"",就会被解析为布尔查询,因为空格默认代表OR
-
MatchAllDocsQuery
输入
*:*
-
为子查询加权
(1) 浮点数前面加上一个^符号代表对查询处理进行加权因子的设置,默认为1.0
Query q = new QueryParser(Version.LUCENE_30, "field", analyzer).parse("term^2 junit");
-
chapter03_为应用程序添加搜索功能
猜你喜欢
转载自blog.csdn.net/captxb/article/details/103299925
今日推荐
周排行