本文是我们名为“ Apache Lucene基础知识 ”的学院课程的一部分。
在本课程中,您将了解Lucene。 您将了解为什么这样的库很重要,然后了解Lucene中搜索的工作方式。 此外,您将学习如何将Lucene Search集成到您自己的应用程序中,以提供强大的搜索功能。 在这里查看 !
目录
- 1.简介 2.查询类
- 2.1术语查询 2.2短语查询 2.3。 布尔查询 2.4。 通配符查询 2.5。 正则表达式查询 2.6。 TermRangeQuery 2.7。 NumberRangeQuery 2.8。 模糊查询
1.简介
在本课程的这一课中,我们将研究Lucene提供的基本查询机制。 您可能会在入门课程中记得,Lucene不会将要搜索的原始文本发送到索引。 它为此使用Query
对象。 在本课程中,我们将看到所有关键要素,它们将人类书面搜索短语转换为诸如Queries
类的代表性结构。
2.查询类
Query
类是一个公共抽象类,它代表对索引的查询。 在本节中,我们将看到最重要的Query子类,您可以使用它们来执行高度定制的查询。
2.1术语查询
这是您可以针对Lucene索引执行的最简单直接的查询。 您只需搜索在特定Field
包含单个单词的Documents
。
基本的TermQuery
构造函数定义如下: public TermQuery(Term t)
。 您从第一节课中记得, Term
由两部分组成:
- 该术语所驻留的
Field
的名称。 - 术语的实际值,在大多数情况下,是通过对某些纯文本的分析得出的单个单词。
因此,如果您想创建一个TermQuery来查找所有在"content"
Field
中包含"good"
字样的Documents
,则可以按照以下方法进行操作
TermQuery termQuery = new TermQuery(new Term("content","good"));
我们可以使用它在先前创建的索引中搜索单词“ static”:
String q = "static"Directory directory = FSDirectory.open(indexDir);IndexReader indexReader = DirectoryReader.open(directory);IndexSearcher searcher = new IndexSearcher(indexReader);Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_46);TermQuery termQuery = new TermQuery(new Term("content",q));TopDocs topDocs =searcher.search(termQuery, maxHits);ScoreDoc[] hits = topDocs.scoreDocs;for (ScoreDoc hit : hits) {int docId = hit.doc;Document d = searcher.doc(docId);System.out.println(d.get("fileName") + " Score :" + hit.score);
}System.out.println("Found " + hits.length);
输出为:
C:\\Users\\nikos\\Desktop\\LuceneFolders\\LuceneHelloWorld\\SourceFiles\\Product.java Score :0.29545835
C:\\Users\\nikos\\Desktop\\LuceneFolders\\LuceneHelloWorld\\SourceFiles\\SimpleSearcher.java Score :0.27245367
C:\\Users\\nikos\\Desktop\\LuceneFolders\\LuceneHelloWorld\\SourceFiles\\PropertyObject.java Score :0.24368995
C:\\Users\\nikos\\Desktop\\LuceneFolders\\LuceneHelloWorld\\SourceFiles\\SimpleIndexer.java Score :0.14772917
C:\\Users\\nikos\\Desktop\\LuceneFolders\\LuceneHelloWorld\\SourceFiles\\TestSerlvet.java Score :0.14621398
C:\\Users\\nikos\\Desktop\\LuceneFolders\\LuceneHelloWorld\\SourceFiles\\ShoppingCartServlet.java Score :0.13785185
C:\\Users\\nikos\\Desktop\\LuceneFolders\\LuceneHelloWorld\\SourceFiles\\MyServlet.java Score :0.12184498
Found 7
如您所见,我的七个源文件中包含"static"
关键字。 就是这样。 自然,如果您尝试在查询字符串中添加另一个单词,则搜索将返回0个结果。 例如,如果您将查询字符串设置为:
String q = "private static"
输出将是:
Found 0
现在,我知道我的许多源文件中都存在"private static"
。 但是,您可能还记得,我们在索引过程中使用了StandarAnalyzer
处理从文件中检索的纯文本。 StandardAnalyzer
将文本拆分为单个单词,因此每个Term
包含一个单词。 您可以选择不标记索引的Field
。 但是我建议您在包含元数据的Fields
中执行此操作,而不是在包含其内容的字段中,这些字段包含有关我们文档的信息(例如标题或作者)。 例如,如果您选择不对名称为'author'
且值为'James Wilslow'
的Field
进行标记化并编制索引,则Field
'author'
将仅包含一个整体值为'James Wilslow'
Term
。 如果您对Field
进行了标记化,则它将包含两个Terms
,一个的值为'James'
,另一个的值为'Wilslow'
。
2.2短语查询
使用PhraseQuery
您可以搜索包含特定单词序列(又名短语)的Documents
。
您可以这样创建一个PhraseQuery
:
PhraseQuery phraseQuery = new PhraseQuery();
然后,您可以向其添加Terms
。 例如,如果您要搜索在其“内容”字段中包含短语“ private static”的Documents
,则可以这样做:
PhraseQuery phraseQuery = new PhraseQuery();phraseQuery.add(new Term("content","private"));
phraseQuery.add(new Term("content","static"));TopDocs topDocs =searcher.search(phraseQuery, maxHits);ScoreDoc[] hits = topDocs.scoreDocs;for (ScoreDoc hit : hits) {int docId = hit.doc;Document d = searcher.doc(docId);System.out.println(d.get("fileName") + " Score :" + hit.score);
}System.out.println("Found " + hits.length);
输出将是:
C:\\Users\\nikos\\Desktop\\LuceneFolders\\LuceneHelloWorld\\SourceFiles\\Product.java Score :0.54864377
C:\\Users\\nikos\\Desktop\\LuceneFolders\\LuceneHelloWorld\\SourceFiles\\PropertyObject.java Score :0.45251375
C:\\Users\\nikos\\Desktop\\LuceneFolders\\LuceneHelloWorld\\SourceFiles\\SimpleSearcher.java Score :0.45251375
C:\\Users\\nikos\\Desktop\\LuceneFolders\\LuceneHelloWorld\\SourceFiles\\TestSerlvet.java Score :0.27150828
C:\\Users\\nikos\\Desktop\\LuceneFolders\\LuceneHelloWorld\\SourceFiles\\ShoppingCartServlet.java Score :0.25598043
C:\\Users\\nikos\\Desktop\\LuceneFolders\\LuceneHelloWorld\\SourceFiles\\MyServlet.java Score :0.22625688
C:\\Users\\nikos\\Desktop\\LuceneFolders\\LuceneHelloWorld\\SourceFiles\\SimpleIndexer.java Score :0.22398287
Found 7
仅当“ Field
"static"
连续且以该顺序准确包含单词"private"
和"static"
, Document
才进入结果。
因此,如果您以上述方式更改上述代码:
phraseQuery.add(new Term("content","private"));
phraseQuery.add(new Term("content","final"));
你会得到 :
Found 0
这是因为尽管我的源文件包含两个词,但它们不是连续的。 要改变这种行为一点点,你可以添加一个slop
到PhraseQuery
。 当您增加1的坡度时,您最多可以在词组中的词之间插入一个词。 添加坡度2时,短语中的单词之间最多允许2个单词。
有趣的是: “实际上,坡度是一个编辑距离,其单位对应于查询短语中词条移动的位置。 例如,要切换两个单词的顺序需要两个步骤(第一个步骤将单词彼此放在首位),因此要允许对短语进行重新排序,斜率必须至少为两个。
因此,如果我们这样做:
PhraseQuery phraseQuery = new PhraseQuery();phraseQuery.add(new Term("content","private"));
phraseQuery.add(new Term("content","final"));phraseQuery.setSlop(2);
我们的搜索输出将给出:
C:\\Users\\nikos\\Desktop\\LuceneFolders\\LuceneHelloWorld\\SourceFiles\\Product.java Score :0.38794976
C:\\Users\\nikos\\Desktop\\LuceneFolders\\LuceneHelloWorld\\SourceFiles\\PropertyObject.java Score :0.31997555
C:\\Users\\nikos\\Desktop\\LuceneFolders\\LuceneHelloWorld\\SourceFiles\\SimpleSearcher.java Score :0.31997555
C:\\Users\\nikos\\Desktop\\LuceneFolders\\LuceneHelloWorld\\SourceFiles\\TestSerlvet.java Score :0.19198532
C:\\Users\\nikos\\Desktop\\LuceneFolders\\LuceneHelloWorld\\SourceFiles\\ShoppingCartServlet.java Score :0.18100551
C:\\Users\\nikos\\Desktop\\LuceneFolders\\LuceneHelloWorld\\SourceFiles\\MyServlet.java Score :0.15998778
C:\\Users\\nikos\\Desktop\\LuceneFolders\\LuceneHelloWorld\\SourceFiles\\SimpleIndexer.java Score :0.15837982
重要的是要提到包含更接近查询确切短语的短语的文档将获得更高的分数。
2.3布尔查询
BooleanQuery
是一种更具表现力和功能的工具,因为您可以将多个查询与布尔子句结合在一起。 可以用BooleanClauses填充BoleanQuery。 BooleanClause包含一个Query
,以及Query
在布尔搜索中应具有的角色。
更具体地说,布尔子句可以在查询中扮演以下角色:
-
MUST
:这很自我explenatory
。 当且仅当Document
包含该子句时,它才会进入结果列表。 -
MUST NOT
:这是完全相反的情况。 对于文档而言,必须将其包含在结果列表中,而不包含该子句。 -
SHOULD
:这是针对Document
中可能出现的子句的,但是为了使结果成为结果,它们不必包含这些子句。
如果仅使用SHOULD子句进行布尔查询,则结果至少匹配其中一个子句。 这看起来像经典的OR布尔运算符,但正确使用它并不是那么简单。
现在让我们看一些例子。 让我们找到包含单词“ string”但不包含单词“ int”的源文件。
TermQuery termQuery = new TermQuery(new Term("content","string"));
TermQuery termQuery2 = new TermQuery(new Term("content","int"));BooleanClause booleanClause1 = new BooleanClause(termQuery, BooleanClause.Occur.MUST);
BooleanClause booleanClause2 = new BooleanClause(termQuery2, BooleanClause.Occur.MUST_NOT);BooleanQuery booleanQuery = new BooleanQuery();
booleanQuery.add(booleanClause1);
booleanQuery.add(booleanClause2);TopDocs topDocs =searcher.search(booleanQuery, maxHits);
结果如下:
C:\\Users\\nikos\\Desktop\\LuceneFolders\\LuceneHelloWorld\\SourceFiles\\SimpleEJB.java Score :0.45057273
C:\\Users\\nikos\\Desktop\\LuceneFolders\\LuceneHelloWorld\\SourceFiles\\PropertyObject.java Score :0.39020744
C:\\Users\\nikos\\Desktop\\LuceneFolders\\LuceneHelloWorld\\SourceFiles\\ShoppingCartServlet.java Score :0.20150226
C:\\Users\\nikos\\Desktop\\LuceneFolders\\LuceneHelloWorld\\SourceFiles\\TestSerlvet.java Score :0.13517183
Found 4
现在,让我们尝试查找包含单词“ nikos”和短语“ httpservletresponse response”的所有文档。 在以下代码片段中,您可以看到如何避免创建BooleanClause
实例,从而使您的代码更紧凑。
TermQuery termQuery = new TermQuery(new Term("content","nikos"));PhraseQuery phraseQuery = new PhraseQuery();
phraseQuery.add(new Term("content","httpservletresponse"));
phraseQuery.add(new Term("content","response"));BooleanQuery booleanQuery = new BooleanQuery();booleanQuery.add(phraseQuery,BooleanClause.Occur.MUST);
booleanQuery.add(termQuery,BooleanClause.Occur.MUST);TopDocs topDocs =searcher.search(booleanQuery, maxHits);
结果如下:
C:\\Users\\nikos\\Desktop\\LuceneFolders\\LuceneHelloWorld\\SourceFiles\\ShoppingCartServlet.java Score :0.3148332
Found 1
让我们找到所有包含单词“ int”或单词“ nikos”的文档。您可能会想到,必须以某种方式使用SHOULD规范:
TermQuery termQuery = new TermQuery(new Term("content","int"));
TermQuery termQuery2 = new TermQuery(new Term("content","nikos"));BooleanQuery booleanQuery = new BooleanQuery();booleanQuery.add(termQuery,BooleanClause.Occur.SHOULD);
booleanQuery.add(termQuery2,BooleanClause.Occur.SHOULD);TopDocs topDocs =searcher.search(booleanQuery, maxHits);
这虽然不太困难,但是创建更复杂的析取查询有点棘手。 如何正确使用它并不总是那么简单。
例如,让我们尝试查找所有包含单词“ nikos”和短语“ httpservletresponse response”或单词“ int”的文档。 可以这样写:
TermQuery termQuery = new TermQuery(new Term("content","nikos"));PhraseQuery phraseQuery = new PhraseQuery();
phraseQuery.add(new Term("content","httpservletresponse"));
phraseQuery.add(new Term("content","response"));BooleanQuery booleanQuery = new BooleanQuery();booleanQuery.add(phraseQuery,BooleanClause.Occur.MUST);
booleanQuery.add(termQuery,BooleanClause.Occur.MUST);
booleanQuery.add(new TermQuery(new Term("content","int")),BooleanClause.Occur.SHOULD);TopDocs topDocs =searcher.search(booleanQuery, maxHits);
但是查询将无法提供所需的结果。 请记住,如我们所构造的那样,该查询的结果必须同时包含单词"nikos"
并且必须同时包含短语"httpservletresponse response"
。 但这不是您想要的。 您需要包含nikos单词和短语"httpservletresponse response"
文档,但也想要独立包含单词"int"
文档,无论它们是否包含其他子句。 公平地说,上述布尔查询有点错误。 因为在直接的布尔语法中,您永远不会写类似的内容:A AND B ORC。您应该写(A AND B)ORC。或者A AND(B OR C)。 看到不同?
因此,您应该编写所需的查询,例如:(“ nikos”和“ httpservletresponse response”)或“ int”。
您可以将BooleanQueries
组合在一起。 使用上面严格的语法,很难想象这将如何进行:
TermQuery termQuery = new TermQuery(new Term("content","nikos"));PhraseQuery phraseQuery = new PhraseQuery();
phraseQuery.add(new Term("content","httpservletresponse"));
phraseQuery.add(new Term("content","response"));// (A AND B)
BooleanQuery conjunctiveQuery = new BooleanQuery();
conjunctiveQuery.add(termQuery,BooleanClause.Occur.MUST);
conjunctiveQuery.add(phraseQuery,BooleanClause.Occur.MUST);BooleanQuery disjunctiveQuery = new BooleanQuery();// (A AND B) OR C
disjunctiveQuery.add(conjunctiveQuery,BooleanClause.Occur.SHOULD);
disjunctiveQuery.add(new TermQuery(new Term("content","int")),BooleanClause.Occur.SHOULD);TopDocs topDocs =searcher.search(disjunctiveQuery, maxHits);
这是使用BooleanQuery
类构造布尔查询时可以遵循的快速指南:
- X和Y
BooleanQuery bool = new BooleanQuery();
bool.add(X,BooleanClause.Occur.MUST);
bool.add(Y,BooleanClause.Occur.MUST);
- X或Y
BooleanQuery bool = new BooleanQuery(); bool.add(X,BooleanClause.Occur.SHOULD); bool.add(Y,BooleanClause.Occur.SHOULD);
- X AND(不是Y)
BooleanQuery bool = new BooleanQuery(); bool.add(X,BooleanClause.Occur.MUST); bool.add(Y,BooleanClause.Occur.MUST_NOT);
- (X和Y)或Z
BooleanQuery conj = new BooleanQuery();conj.add(X,BooleanClause.Occur.MUST); conj.add(Y,BooleanClause.Occur.MUST);BooleanQuery disj = new BooleanQuery(); disj.add(conj,BooleanClause.Occur.SHOULD) disj.add(Z,BooleanClause.Occur.SHOULD)
- (X或Y)和Z
BooleanQuery conj = new BooleanQuery();conj.add(X,BooleanClause.Occur.SHOULD); conj.add(Y,BooleanClause.Occur.SHOULD);BooleanQuery disj = new BooleanQuery(); disj.add(conj,BooleanClause.Occur.MUST) disj.add(Z,BooleanClause.Occur.MUST)
- X或(非Z)
BooleanQuery neg = new BooleanQuery();neg.add(Z,BooleanClause.Occur.MUST_OT);BooleanQuery disj = new BooleanQuery(); disj.add(neg,BooleanClause.Occur.SHOULD) disj.add(X,BooleanClause.Occur.SHOULD)
上面的代码可用于创建越来越复杂的布尔查询。
2.4通配符查询
顾名思义,您可以使用WildcardQuery类使用“ *”或“?”执行通配符查询。 字符。 例如,如果您要o搜索包含以'ni'开头的词条,然后是其他任何字符序列的文档,则可以搜索'ni *'。 如果要搜索以“ jamie”开头(后跟(任意)一个字符)的术语,则可以搜索“ jamie?”。 就那么简单。 自然地,
WildcardQueries
效率低下,因为搜索可能要经过很多不同的术语才能找到匹配项。 通常,最好避免将通配符放在单词的开头,例如“ * abcde”。让我们来看一个例子:
Query wildcardQuery = new WildcardQuery(new Term("content","n*os")); TopDocs topDocs =searcher.search(wildcardQuery, maxHits);
和
Query wildcardQuery = new WildcardQuery(new Term("content","niko?")); TopDocs topDocs =searcher.search(wildcardQuery, maxHits);
2.5 RegexpQuery
使用
RegexpQuery
,您可以执行快速的正则表达式查询,并通过Lucene的快速自动机实现对其进行评估。 这是一个例子Query regexpQuery = new RegexpQuery(new Term("content","n[a-z]+"));TopDocs topDocs =searcher.search(regexpQuery, maxHits);
2.6 TermRangeQuery
当对字符串术语执行范围查询时,此查询子类很有用。 例如,您可以搜索“ abc”和“ xyz”两个词之间的字词。 字的比较是使用
Byte.compareTo(Byte)
执行的。 您可能会发现这对于在文档的元数据(例如标题甚至日期)中进行范围查询特别有用(如果使用日期,请小心使用DateTools
)。您可以在这里找到上周创建的所有文档:
Calendar c = Calendar.getInstance(); c.add(Calendar.DATE, -7); Date lastWeek = c.getTime();Query termRangeQuery = TermRangeQuery.newStringRange("date", DateTools.dateToString(new Date(), DateTools.Resolution.DAY),DateTools.dateToString(lastWeek, DateTools.Resolution.DAY),true,true);
当然,在为“日期”字段建立索引时必须小心。 您还必须将
DateTools.dateToString
应用于它,并指定该字段不进行分析(因此不会被标记化并拆分为单词)。2.7 NumberRangeQuery
这用于执行数字范围查询。 想象一下,您有一个“ wordcount”字段,用于存储该文档的单词数,并且您要检索的单词数在2000至10000之间:
Query numericRangeQuery = NumericRangeQuery.newIntRange("wordcount",2000,10000,true,true);
布尔参数规定范围内包括上限和下限。
2.8模糊查询
这是一个非常有趣的查询子类。 该查询根据邻近度量(例如众所周知的Damerau-Levenshtein距离)评估字词。 这将找到词典顺序接近的单词。 如果您想执行大量的词典应用程序,例如词典或“您要说的”单词建议功能,则可以使用
SpellChecker
API 。让我们看看如何执行不幸的“字符串”拼写错误的模糊查询搜索:
Query fuzzyQuery = new FuzzyQuery(new Term("content","srng"));
翻译自: https://www.javacodegeeks.com/2015/09/lucene-query-search-syntax-examples.html