關于SpanQuery(跨度搜索),它是Query的子類,但是SpanQuery仍然是一個抽象類,它有6個直接子類實現(xiàn)類。繼承關系如圖所示:
其中SpanTermQuery是一個最基礎的跨度搜索實現(xiàn)類,SpanTermQuery與SpanQuery的關系,就如同TermQuery與Query的關系:SpanTermQuery是為SpanQuery其它的具體實現(xiàn)子類服務的,其實TermQuery也是為Query的具體子類實現(xiàn)類服務的,例如構造一個BooleanQuery查詢,可以向其中添加多個TermQuery查詢子句。
SpanTermQuery跨度搜索
SpanTermQuery的應用與TermQuery的用法是一樣的,獲取到的檢索結果集也是相同的。
這里,SpanTermQuery是SpanQuery的子實現(xiàn)類,所有從跨度搜索的角度來說,他的跨度值就是使用SpanTermQuery的唯一的一個構造方法所用的一個Term的跨度值。也就是說,它的起始位置和結束位置都是一個固定的值(其實就是一個固定跨度)。
SpanFirstQuery跨度搜索
SpanFirstQuery搜索是基于SpanTermQuery的,在實例化一個SpanFirstQuery的時候,是通過一個SpanTermQuery的實例作為參數(shù)來構造的。
該SpanFirstQuery只有唯一的一個構造方法:
public SpanFirstQuery(SpanQuery match, int end) {
??? this.match = match;
??? this.end = end;
}
上面的end指定了在查詢時,從起始位置開始(起始位置為0,這點可以在后面的測試中得知。因為名稱中First已經(jīng)表達了這層含義),在小于end的位置之前的文本中,與match進行匹配。
先使用ThesaurusAnalyzer分析器來實現(xiàn)分詞,為指定的數(shù)據(jù)建立索引,如下所示:
package org.apache.lucene.shirdrn.main;
import java.io.IOException;
import net.teamhot.lucene.ThesaurusAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.store.LockObtainFailedException;
public class MySearchEngineUsingThesaurusAnalyzer {
public static void main(String[] args){
?? String indexPath = "E:\\Lucene\\index";
?? IndexWriter writer;
?? try {
??? writer = new IndexWriter(indexPath,new ThesaurusAnalyzer(),true);
??? Field fieldA = new Field("contents","今天是我們地球的生日,對于我們每個人,在我們的宇宙中,一場空前關于我們熟悉的宇宙論的辯論激烈地展開了。",Field.Store.YES,Field.Index.TOKENIZED);
??? Document docA = new Document();
??? docA.add(fieldA);
???
??? Field fieldB1 = new Field("contents","誰知道宇宙空間的奧秘,在我們這些人當中?",Field.Store.YES,Field.Index.TOKENIZED);
??? Field fieldB2 = new Field("contents","宇宙飛船。",Field.Store.YES,Field.Index.TOKENIZED);
??? Field fieldB3 = new Field("contents","我們的太空宇宙。",Field.Store.YES,Field.Index.TOKENIZED);
??? Document docB = new Document();
??? docB.add(fieldB1);
??? docB.add(fieldB2);
??? docB.add(fieldB3);
???
??? Field fieldC = new Field("contents","我們宇宙學家對地球的重要性。",Field.Store.YES,Field.Index.TOKENIZED);
??? Document docC = new Document();
??? docC.add(fieldC);
???
??? writer.addDocument(docA);
??? writer.addDocument(docB);
??? writer.addDocument(docC);
??? writer.close();
?? } catch (CorruptIndexException e) {
??? e.printStackTrace();
?? } catch (LockObtainFailedException e) {
??? e.printStackTrace();
?? } catch (IOException e) {
??? e.printStackTrace();
?? }
}
首先要把ThesaurusAnalyzer分析器的jar包加入到CLASSPATH中,然后運行上面的主函數(shù),建立索引。
建立的索引文件在本地磁盤指定的索引目錄E:\Lucene\index下生成,這時候可以測試SpanFirstQuery的使用了。
因為ThesaurusAnalyzer分析器自帶了一個詞庫,該詞庫中有詞條“我們”,我們就通過“我們”來構造SpanQuery,進行查詢。
編寫一個SpanFirstQuerySearcher測試類,帶主函數(shù),如下所示:
package org.apache.lucene.shirdrn.main;
import java.io.IOException;
import java.util.Date;
import java.util.List;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.TermDocs;
import org.apache.lucene.search.Hits;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.spans.SpanFirstQuery;
import org.apache.lucene.search.spans.SpanQuery;
import org.apache.lucene.search.spans.SpanTermQuery;
public class SpanFirstQuerySearcher {
public static void main(String[] args) {
?? String indexPath = "E:\\Lucene\\index";
?? try {
??? IndexSearcher searcher = new IndexSearcher(indexPath);
??? String keyword = "我們";
??? Term term = new Term("contents",keyword);
??? SpanTermQuery spanTermQuery = new SpanTermQuery(term);
??
int end = 1;
??? SpanQuery spanFirstQuery = new SpanFirstQuery(spanTermQuery,end);
??? System.out.println("####################################################################");
??? System.out.println("SpanFirstQuery中end指定值為 : "+end);
??? System.out.println("####################################################################");
??? Date startTime = new Date();
??? Hits hits = searcher.search(spanFirstQuery);
??? for(int i=0;i<hits.length();i++){
???? TermDocs termDocs = searcher.getIndexReader().termDocs(term);
???? while(termDocs.next()){
????? if(termDocs.doc() == hits.id(i)){
?????? System.out.println("Document的內部編號為 : "+hits.id(i));
?????? Document doc = hits.doc(i);
?????? System.out.println("Document的得分為 : "+hits.score(i));
?????? List fieldList = doc.getFields();
?????? System.out.println("Document(編號) "+hits.id(i)+" 的Field的信息: ");
?????? for(int j=0;j<fieldList.size();j++){
??????? Field field = (Field)fieldList.get(j);
??????? System.out.println("??? Field的name : "+field.name());
??????? System.out.println("??? Field的stringValue : "+field.stringValue());
??????? System.out.println("??? ------------------------------------");
?????? }
?????? System.out.println("搜索的該關鍵字【"+keyword+"】在Document(編號) "+hits.id(i)+" 中,出現(xiàn)過 "+termDocs.freq()+" 次");
????? }
???? }
??? }
??? System.out.println("********************************************************************");
??? Date finishTime = new Date();
??? long timeOfSearch = finishTime.getTime() - startTime.getTime();
??? System.out.println("本次搜索所用的時間為 "+timeOfSearch+" ms");
???
?? } catch (CorruptIndexException e) {
??? e.printStackTrace();
?? } catch (IOException e) {
??? e.printStackTrace();
?? }
}
}
當end=1時,也就是從具有一個詞條的跨度,運行結果如下所示:
####################################################################
SpanFirstQuery中end指定值為 : 1
####################################################################
Document的內部編號為 : 2
Document的得分為 : 0.18888181
Document(編號) 2 的Field的信息:
??? Field的name : contents
??? Field的stringValue : 我們宇宙學家對地球的重要性。
??? ------------------------------------
搜索的該關鍵字【我們】在Document(編號) 2 中,出現(xiàn)過 1 次
********************************************************************
本次搜索所用的時間為 78 ms
這里docB沒有被檢索出來。
當end=5時,增大了跨度,執(zhí)行結果如下所示:
####################################################################
SpanFirstQuery中end指定值為 : 5
####################################################################
Document的內部編號為 : 2
Document的得分為 : 0.18888181
Document(編號) 2 的Field的信息:
??? Field的name : contents
??? Field的stringValue : 我們宇宙學家對地球的重要性。
??? ------------------------------------
搜索的該關鍵字【我們】在Document(編號) 2 中,出現(xiàn)過 1 次
Document的內部編號為 : 0
Document的得分為 : 0.09444091
Document(編號) 0 的Field的信息:
??? Field的name : contents
??? Field的stringValue : 今天是我們地球的生日,對于我們每個人,在我們的宇宙中,一場空前關于我們熟悉的宇宙論的辯論激烈地展開了。
??? ------------------------------------
搜索的該關鍵字【我們】在Document(編號) 0 中,出現(xiàn)過 4 次
********************************************************************
本次搜索所用的時間為 62 ms
當end=10的時候,可以看到3個Document都被檢索到,如下所示:
####################################################################
SpanFirstQuery中end指定值為 : 10
####################################################################
Document的內部編號為 : 2
Document的得分為 : 0.18888181
Document(編號) 2 的Field的信息:
??? Field的name : contents
??? Field的stringValue : 我們宇宙學家對地球的重要性。
??? ------------------------------------
搜索的該關鍵字【我們】在Document(編號) 2 中,出現(xiàn)過 1 次
Document的內部編號為 : 0
Document的得分為 : 0.13355961
Document(編號) 0 的Field的信息:
??? Field的name : contents
??? Field的stringValue : 今天是我們地球的生日,對于我們每個人,在我們的宇宙中,一場空前關于我們熟悉的宇宙論的辯論激烈地展開了。
??? ------------------------------------
搜索的該關鍵字【我們】在Document(編號) 0 中,出現(xiàn)過 4 次
Document的內部編號為 : 1
Document的得分為 : 0.1259212
Document(編號) 1 的Field的信息:
??? Field的name : contents
??? Field的stringValue : 誰知道宇宙空間的奧秘,在我們這些人當中?
??? ------------------------------------
??? Field的name : contents
??? Field的stringValue : 宇宙飛船。
??? ------------------------------------
??? Field的name : contents
??? Field的stringValue : 我們的太空宇宙。
??? ------------------------------------
搜索的該關鍵字【我們】在Document(編號) 1 中,出現(xiàn)過 2 次
********************************************************************
本次搜索所用的時間為 234 ms
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
SpanNearQuery跨度搜索
SpanNearQuery只有一個構造方法,可以從SpanNearQuery的構造方法來看:
public SpanNearQuery(SpanQuery[] clauses, int slop, boolean inOrder) {
??? this.clauses = new ArrayList(clauses.length);
??? for (int i = 0; i < clauses.length; i++) {
????? SpanQuery clause = clauses[i];
????? if (i == 0) {?????????????????????????????
??????? field = clause.getField();
????? } else if (!clause.getField().equals(field)) {
??????? throw new IllegalArgumentException("Clauses must have same field.");
????? }
????? this.clauses.add(clause);
??? }
??? this.slop = slop;
??? this.inOrder = inOrder;
}
從方法的聲明來看,各個參數(shù)如下:
clauses是指:一個SpanQuery的子句的數(shù)組;
slop是指:對于每個SpanQuery都由一個Term構造而成,在一段文本中,可能在出現(xiàn)的這兩個詞條之間由若干個其它不相關的詞條,slop指定了一個整數(shù)值,從而可以忽略這些不相關的詞條(忽略的個數(shù)<=slop),如果slop=0,則說明clauses中的SpanQuery查詢的詞條必須是相連著的;
inOrder是指:是否clauses子句們按照有序的方式實現(xiàn)搜索,當inOrder為true時,必須按照各個子句中檢索的詞條的前后順序進行匹配,逆序的就被淘汰。
依然用上面建立的索引文件測試。
測試通過先構造一個SpanTermQuery(詞條內容為“我們”)和一個SpanFirstQuery(詞條內容為“宇宙”),再構造一個SpanNearQuery,測試代碼如下所示:
package org.apache.lucene.shirdrn.main;
import java.io.IOException;
import java.util.Date;
import java.util.List;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.TermDocs;
import org.apache.lucene.search.Hits;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.spans.SpanFirstQuery;
import org.apache.lucene.search.spans.SpanNearQuery;
import org.apache.lucene.search.spans.SpanQuery;
import org.apache.lucene.search.spans.SpanTermQuery;
public class SpanNearQuerySearcher {
public static void main(String[] args) {
?? String indexPath = "E:\\Lucene\\index";
?? try {
??? IndexSearcher searcher = new IndexSearcher(indexPath);
??? String keywordA = "我們";
??? Term termA = new Term("contents",keywordA);
??? SpanTermQuery spanTermQueryA = new SpanTermQuery(termA);???
??? int end = 10;
??? System.out.println("####################################################################");
??? System.out.println("SpanFirstQuery中end指定值為 : "+end);
??? System.out.println("####################################################################");
??? SpanQuery spanFirstQuery = new SpanFirstQuery(spanTermQueryA,end);
??? String keywordB = "宇宙";
??? Term termB = new Term("contents",keywordA);
??? SpanTermQuery spanTermQueryB = new SpanTermQuery(termB);
??? int slop = 2;
??? System.out.println("####################################################################");
??? System.out.println("SpanNearQuery中slop指定值為 : "+slop);
??? System.out.println("####################################################################");
??
SpanNearQuery spanNearQuery = new SpanNearQuery(new SpanQuery[]{spanFirstQuery,spanTermQueryB},slop,true);
??? Date startTime = new Date();
??? Hits hits = searcher.search(spanNearQuery);
??? for(int i=0;i<hits.length();i++){
???? System.out.println("Document的內部編號為 : "+hits.id(i));
???? Document doc = hits.doc(i);
???? System.out.println("Document的得分為 : "+hits.score(i));
???? List fieldList = doc.getFields();
???? System.out.println("Document(編號) "+hits.id(i)+" 的Field的信息: ");
???? for(int j=0;j<fieldList.size();j++){
????? Field field = (Field)fieldList.get(j);
????? System.out.println("??? Field的name : "+field.name());
????? System.out.println("??? Field的stringValue : "+field.stringValue());
????? System.out.println("??? ------------------------------------");
???? }
??? }
??? System.out.println("********************************************************************");
??? Date finishTime = new Date();
??? long timeOfSearch = finishTime.getTime() - startTime.getTime();
??? System.out.println("本次搜索所用的時間為 "+timeOfSearch+" ms");
???
?? } catch (CorruptIndexException e) {
??? e.printStackTrace();
?? } catch (IOException e) {
??? e.printStackTrace();
?? }
}
}
這里,指定了slop=2,inOrder=true,即:“我們”和“宇宙”是按先后順序在Document中進行匹配的。
運行測試程序,結果如下:
####################################################################
SpanFirstQuery中end指定值為 : 10
####################################################################
####################################################################
SpanNearQuery中slop指定值為 : 2
####################################################################
Document的內部編號為 : 0
Document的得分為 : 0.059729677
Document(編號) 0 的Field的信息:
??? Field的name : contents
??? Field的stringValue : 今天是我們地球的生日,對于我們每個人,在我們的宇宙中,一場空前關于我們熟悉的宇宙論的辯論激烈地展開了。
??? ------------------------------------
********************************************************************
本次搜索所用的時間為 93 ms
其實,我們指定了SpanFirstQuery足夠大的跨度,但是在SpanNearQuery中指定的slop的值很小,在進行匹配的時候,只是允許兩個詞條之間可以有2個無關的其它詞條,再加上指定了inOrder為true,嚴格有序,所以只檢索到了編號為0的Document。
現(xiàn)在,將slop改為10,因為slop比較關鍵,決定了兩個檢索詞條之間的間隙大小,這時可以看到檢索結果如下所示:
####################################################################
SpanFirstQuery中end指定值為 : 10
####################################################################
####################################################################
SpanNearQuery中slop指定值為 : 10
####################################################################
Document的內部編號為 : 0
Document的得分為 : 0.078204505
Document(編號) 0 的Field的信息:
??? Field的name : contents
??? Field的stringValue : 今天是我們地球的生日,對于我們每個人,在我們的宇宙中,一場空前關于我們熟悉的宇宙論的辯論激烈地展開了。
??? ------------------------------------
Document的內部編號為 : 1
Document的得分為 : 0.06730772
Document(編號) 1 的Field的信息:
??? Field的name : contents
??? Field的stringValue : 誰知道宇宙空間的奧秘,在我們這些人當中?
??? ------------------------------------
??? Field的name : contents
??? Field的stringValue : 宇宙飛船。
??? ------------------------------------
??? Field的name : contents
??? Field的stringValue : 我們的太空宇宙。
??? ------------------------------------
********************************************************************
本次搜索所用的時間為 125 ms
SpanNearQuery的構造方法給了一個SpanQuery[] clauses子句數(shù)組,可以使用任何繼承了SpanQuery的具體實現(xiàn)類,當然也包括SpanNearQuery,將它們添加到子句的數(shù)組中,實現(xiàn)復雜的搜索。
SpanNotQuery跨度搜索
依然從構造方法看:
public SpanNotQuery(SpanQuery include, SpanQuery exclude) {
??? this.include = include;
??? this.exclude = exclude;
??? if (!include.getField().equals(exclude.getField()))
????? throw new IllegalArgumentException("Clauses must have same field.");
}
該SpanNotQuery指定了一個SpanQuery include,該include子句查詢會得到一個結果的集合,設為集合A;SpanQuery exclude也可以得到一個結果的集合,設為集合B,則SpanNotQuery檢索結果的集合表示為:
A-B
很好理解,就是集合的差運算。
SpanOrQuery跨度搜索
這個也很好理解,就是集合的并運算,它的構造方法如下所示:
public SpanOrQuery(SpanQuery[] clauses) {
??? this.clauses = new ArrayList(clauses.length);
??? for (int i = 0; i < clauses.length; i++) {
????? SpanQuery clause = clauses[i];
????? if (i == 0) {???????????????????????????????
??????? field = clause.getField();
????? } else if (!clause.getField().equals(field)) {
??????? throw new IllegalArgumentException("Clauses must have same field.");
????? }
????? this.clauses.add(clause);
??? }
}
只要把你想要檢索的SpanQuery子句構造好以后,添加到SpanQuery[] clauses數(shù)組中,談后執(zhí)行SpanOrQuery跨度搜索的時候,會把每個子句得到的結果合并起來,得到一個很龐大的檢索結果集。
SpanRegexQuery跨度搜索
構造該SpanQuery也很容易:
public SpanRegexQuery(Term term) {
??? this.term = term;
}
只需要一個Term作為參數(shù)即可。從該SpanRegexQuery的名稱來看,就知道它和正則表達式有一定的聯(lián)系。其實在構造好一個SpanRegexQuery以后,可以為其設置一個正則表達式,這要看你對正則表達式的運用的熟練程度如何了。
在SpanRegexQuery中定義了兩個成員變量:
private RegexCapabilities regexImpl = new JavaUtilRegexCapabilities();
private Term term;
而且SpanRegexQuery實現(xiàn)了RegexQueryCapable接口:
public class SpanRegexQuery extends SpanQuery implements RegexQueryCapable
如果你想使用SpanRegexQuery實現(xiàn)跨度搜索,可以研究一下與SpanRegexQuery相關的JavaUtilRegexCapabilities類,在JavaUtilRegexCapabilities中涉及到了java.util.regex.Pattern,它可不是Lucene定義的,是第三方提供的。
更多文章、技術交流、商務合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
