【Lucene3.6.2入門系列】第03節_簡述Lucene中常見的搜索功能

  1. package com.jadyer.lucene;  
  2.   
  3. import java.io.File;  
  4. import java.io.IOException;  
  5. import java.text.SimpleDateFormat;  
  6. import java.util.Date;  
  7.   
  8. import org.apache.lucene.analysis.standard.StandardAnalyzer;  
  9. import org.apache.lucene.document.Document;  
  10. import org.apache.lucene.document.Field;  
  11. import org.apache.lucene.document.NumericField;  
  12. import org.apache.lucene.index.IndexReader;  
  13. import org.apache.lucene.index.IndexWriter;  
  14. import org.apache.lucene.index.IndexWriterConfig;  
  15. import org.apache.lucene.index.Term;  
  16. import org.apache.lucene.queryParser.ParseException;  
  17. import org.apache.lucene.queryParser.QueryParser;  
  18. import org.apache.lucene.search.BooleanQuery;  
  19. import org.apache.lucene.search.FuzzyQuery;  
  20. import org.apache.lucene.search.IndexSearcher;  
  21. import org.apache.lucene.search.NumericRangeQuery;  
  22. import org.apache.lucene.search.PhraseQuery;  
  23. import org.apache.lucene.search.PrefixQuery;  
  24. import org.apache.lucene.search.Query;  
  25. import org.apache.lucene.search.ScoreDoc;  
  26. import org.apache.lucene.search.TermQuery;  
  27. import org.apache.lucene.search.TermRangeQuery;  
  28. import org.apache.lucene.search.TopDocs;  
  29. import org.apache.lucene.search.WildcardQuery;  
  30. import org.apache.lucene.search.BooleanClause.Occur;  
  31. import org.apache.lucene.store.Directory;  
  32. import org.apache.lucene.store.FSDirectory;  
  33. import org.apache.lucene.util.Version;  
  34.   
  35. /** 
  36.  * 【Lucene3.6.2入門系列】第03節_簡述Lucene中常見的搜索功能 
  37.  * @create Aug 1, 2013 3:54:27 PM 
  38.  * @author 玄玉<http://blog.csdn.net/jadyer> 
  39.  */  
  40. public class HelloSearch {  
  41.     private Directory directory;  
  42.     private IndexReader reader;  
  43.     private String[] ids = {"1""2""3""4""5""6"};  
  44.     private String[] names = {"Michael""Scofield""Tbag""Jack""Jade""Jadyer"};  
  45.     private String[] emails = {"[email protected]""[email protected]""[email protected]""[email protected]""[email protected]""[email protected]"};  
  46.     private String[] contents = {"my java blog is http://blog.csdn.net/jadyer""my website is http://www.jadyer.cn""my name is jadyer""I am JavaDeveloper""I am from Haerbin""I like Lucene"};  
  47.     private int[] attachs = {9,3,5,4,1,2};  
  48.     private Date[] dates = new Date[ids.length];  
  49.       
  50.     public HelloSearch(){  
  51.         IndexWriter writer = null;  
  52.         Document doc = null;  
  53.         SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");  
  54.         try {  
  55.             dates[0] = sdf.parse("20120601");  
  56.             dates[1] = sdf.parse("20120603");  
  57.             dates[2] = sdf.parse("20120605");  
  58.             dates[3] = sdf.parse("20120607");  
  59.             dates[4] = sdf.parse("20120609");  
  60.             dates[5] = sdf.parse("20120611");  
  61.             directory = FSDirectory.open(new File("myExample/03_index/"));  
  62.             writer = new IndexWriter(directory, new IndexWriterConfig(Version.LUCENE_36, new StandardAnalyzer(Version.LUCENE_36)));  
  63.             writer.deleteAll();              //創建索引之前,先把文檔清空掉  
  64.             for(int i=0; i<ids.length; i++){ //遍歷ID來創建文檔  
  65.                 doc = new Document();  
  66.                 doc.add(new Field("id", ids[i], Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS));  
  67.                 doc.add(new Field("name", names[i], Field.Store.YES, Field.Index.ANALYZED_NO_NORMS));  
  68.                 doc.add(new Field("email", emails[i], Field.Store.YES, Field.Index.NOT_ANALYZED));  
  69.                 doc.add(new Field("email""test"+i+""+i+"@jadyer.com", Field.Store.YES, Field.Index.NOT_ANALYZED));  
  70.                 doc.add(new Field("content", contents[i], Field.Store.NO, Field.Index.ANALYZED));  
  71.                 doc.add(new NumericField("attach", Field.Store.YES, true).setIntValue(attachs[i]));        //爲數字加索引(第三個參數指定是否索引)  
  72.                 doc.add(new NumericField("attach", Field.Store.YES, true).setIntValue((i+1)*100));         //假設有多個附件  
  73.                 doc.add(new NumericField("date", Field.Store.YES, true).setLongValue(dates[i].getTime())); //爲日期加索引  
  74.                 writer.addDocument(doc);  
  75.             }  
  76.         } catch (Exception e) {  
  77.             e.printStackTrace();  
  78.         } finally {  
  79.             if(null != writer){  
  80.                 try {  
  81.                     writer.close();  
  82.                 } catch (IOException ce) {  
  83.                     ce.printStackTrace();  
  84.                 }  
  85.             }  
  86.         }  
  87.     }  
  88.       
  89.       
  90.     /** 
  91.      * 針對分頁搜索創建索引 
  92.      */  
  93.     public HelloSearch(boolean pageFlag){  
  94.         String[] myNames = new String[50];  
  95.         String[] myContents = new String[50];  
  96.         for(int i=0; i<50; i++){  
  97.             myNames[i] = "file(" + i + ")";  
  98.             myContents[i] = "I love JavaSE, also love Lucene(" + i + ")";  
  99.         }  
  100.         IndexWriter writer = null;  
  101.         Document doc = null;  
  102.         try {  
  103.             directory = FSDirectory.open(new File("myExample/03_index/"));  
  104.             writer = new IndexWriter(directory, new IndexWriterConfig(Version.LUCENE_36, new StandardAnalyzer(Version.LUCENE_36)));  
  105.             writer.deleteAll();  
  106.             for(int i=0; i<myNames.length; i++){  
  107.                 doc = new Document();  
  108.                 doc.add(new Field("myname", myNames[i], Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS));  
  109.                 doc.add(new Field("mycontent", myContents[i], Field.Store.YES, Field.Index.ANALYZED));  
  110.                 writer.addDocument(doc);  
  111.             }  
  112.         } catch (IOException e) {  
  113.             e.printStackTrace();  
  114.         } finally {  
  115.             if(null != writer){  
  116.                 try {  
  117.                     writer.close();  
  118.                 } catch (IOException ce) {  
  119.                     ce.printStackTrace();  
  120.                 }  
  121.             }  
  122.         }  
  123.     }  
  124.       
  125.       
  126.     /** 
  127.      * 獲取IndexSearcher實例 
  128.      */  
  129.     private IndexSearcher getIndexSearcher(){  
  130.         try {  
  131.             if(reader == null){  
  132.                 reader = IndexReader.open(directory);  
  133.             }else{  
  134.                 //if the index was changed since the provided reader was opened, open and return a new reader; else,return null  
  135.                 //如果當前reader在打開期間index發生改變,則打開並返回一個新的IndexReader,否則返回null  
  136.                 IndexReader ir = IndexReader.openIfChanged(reader);  
  137.                 if(ir != null){  
  138.                     reader.close(); //關閉原reader  
  139.                     reader = ir;    //賦予新reader  
  140.                 }  
  141.             }  
  142.             return new IndexSearcher(reader);  
  143.         }catch(Exception e) {  
  144.             e.printStackTrace();  
  145.         }  
  146.         return null//發生異常則返回null  
  147.     }  
  148.       
  149.       
  150.     /** 
  151.      * 執行搜索操作 
  152.      * @param query 搜索的Query對象 
  153.      */  
  154.     private void doSearch(Query query){  
  155.         IndexSearcher searcher = this.getIndexSearcher();  
  156.         try {  
  157.             //第二個參數指定搜索後顯示的最多的記錄數,其與tds.totalHits沒有聯繫  
  158.             TopDocs tds = searcher.search(query, 10);  
  159.             System.out.println("本次搜索到[" + tds.totalHits + "]條記錄");  
  160.             for(ScoreDoc sd : tds.scoreDocs){  
  161.                 Document doc = searcher.doc(sd.doc);  
  162.                 System.out.print("文檔編號=" + sd.doc + "  文檔權值=" + doc.getBoost() + "  文檔評分=" + sd.score + "    ");  
  163.                 System.out.print("id=" + doc.get("id") + "  email=" + doc.get("email") + "  name=" + doc.get("name") + "  ");  
  164.                 //獲取多個同名域的方式  
  165.                 String[] attachValues = doc.getValues("attach");  
  166.                 for(String attach : attachValues){  
  167.                     System.out.print("attach=" + attach + "  ");  
  168.                 }  
  169.                 System.out.println();  
  170.             }  
  171.         } catch (IOException e) {  
  172.             e.printStackTrace();  
  173.         } finally {  
  174.             if(null != searcher){  
  175.                 try {  
  176.                     searcher.close(); //記得關閉IndexSearcher  
  177.                 } catch (IOException e) {  
  178.                     e.printStackTrace();  
  179.                 }  
  180.             }  
  181.         }  
  182.     }  
  183.       
  184.       
  185.     /** 
  186.      * 精確匹配搜索 
  187.      * @param fieldName 域名(相當於表的字段名) 
  188.      * @param keyWords  搜索的關鍵字 
  189.      */  
  190.     public void searchByTerm(String fieldName, String keyWords){  
  191.         Query query = new TermQuery(new Term(fieldName, keyWords));  
  192.         this.doSearch(query);  
  193.     }  
  194.       
  195.       
  196.     /** 
  197.      * 基於範圍的搜索 
  198.      * @param fieldName 域名(相當於表的字段名) 
  199.      * @param start     開始字符 
  200.      * @param end       結束字符 
  201.      */  
  202.     public void searchByTermRange(String fieldName, String start, String end){  
  203.         Query query = new TermRangeQuery(fieldName, start, end, truetrue); //後面兩個參數用於指定開區間或閉區間  
  204.         this.doSearch(query);  
  205.     }  
  206.       
  207.       
  208.     /** 
  209.      * 針對數字的搜索 
  210.      */  
  211.     public void searchByNumericRange(String fieldName, int min, int max){  
  212.         Query query = NumericRangeQuery.newIntRange(fieldName, min, max, truetrue);  
  213.         this.doSearch(query);  
  214.     }  
  215.       
  216.       
  217.     /** 
  218.      * 基於前綴的搜索 
  219.      * @see 它是對Field分詞後的結果進行前綴查找的結果 
  220.      */  
  221.     public void searchByPrefix(String fieldName, String prefix){  
  222.         Query query = new PrefixQuery(new Term(fieldName, prefix));  
  223.         this.doSearch(query);  
  224.     }  
  225.       
  226.       
  227.     /** 
  228.      * 基於通配符的搜索 
  229.      * @see *-->任意多個字符 
  230.      * @see ?-->一個字符 
  231.      */  
  232.     public void searchByWildcard(String fieldName, String wildcard){  
  233.         Query query = new WildcardQuery(new Term(fieldName, wildcard));  
  234.         this.doSearch(query);  
  235.     }  
  236.       
  237.       
  238.     /** 
  239.      * 模糊搜索 
  240.      * @see 與通配符搜索不同 
  241.      */  
  242.     public void searchByFuzzy(String fieldName, String fuzzy){  
  243.         Query query = new FuzzyQuery(new Term(fieldName, fuzzy));  
  244.         this.doSearch(query);  
  245.     }  
  246.       
  247.       
  248.     /** 
  249.      * 多條件搜索 
  250.      * @see 本例中搜索name值中以Ja開頭,且content中包含am的內容 
  251.      * @see Occur.MUST------表示此條件必須爲true 
  252.      * @see Occur.MUST_NOT--表示此條件必須爲false 
  253.      * @see Occur.SHOULD----表示此條件非必須 
  254.      */  
  255.     public void searchByBoolean(){  
  256.         BooleanQuery query = new BooleanQuery();  
  257.         query.add(new WildcardQuery(new Term("name""Ja*")), Occur.MUST);  
  258.         query.add(new TermQuery(new Term("content""am")), Occur.MUST);  
  259.         this.doSearch(query);  
  260.     }  
  261.       
  262.       
  263.     /** 
  264.      * 短語搜索 
  265.      * @see 很遺憾的是短語查詢對中文搜索沒有太大的作用,但對英文搜索是很好用的,但它的開銷比較大,儘量少用 
  266.      */  
  267.     public void searchByPhrase(){  
  268.         PhraseQuery query = new PhraseQuery();  
  269.         query.setSlop(1);                          //設置跳數  
  270.         query.add(new Term("content""am"));      //第一個Term  
  271.         query.add(new Term("content""Haerbin")); //產生距離之後的第二個Term  
  272.         this.doSearch(query);  
  273.     }  
  274.       
  275.       
  276.     /** 
  277.      * 基於QueryParser的搜索 
  278.      */  
  279.     public void searchByQueryParse(){  
  280.         QueryParser parser = new QueryParser(Version.LUCENE_36, "content"new StandardAnalyzer(Version.LUCENE_36));  
  281.         Query query = null;  
  282.         try {  
  283. //          query = parser.parse("Haerbin");           //搜索content中包含[Haerbin]的記錄  
  284. //          query = parser.parse("I AND Haerbin");     //搜索content中包含[I]和[Haerbin]的記錄  
  285. //          query = parser.parse("Lucene OR Haerbin"); //搜索content中包含[Lucene]或者[Haerbin]的記錄  
  286. //          query = parser.parse("Lucene Haerbin");    //搜索content中包含[Lucene]或者[Haerbin]的記錄  
  287. //          parser.setDefaultOperator(Operator.AND);   //將空格的默認操作OR修改爲AND  
  288. //          //1)如果name域在索引時,不進行分詞,那麼無論這裏寫成[name:Jadyer]還是[name:jadyer],最後得到的都是0條記錄  
  289. //          //2)由於name原值爲大寫[J],若索引時不對name分詞,除非修改name原值爲小寫[j],並且搜索[name:jadyer]才能得到記錄  
  290. //          query = parser.parse("name:Jadyer");       //修改搜索域爲name=Jadyer的記錄  
  291. //          query = parser.parse("name:Ja*");          //支持通配符  
  292. //          query = parser.parse("\"I am\"");          //搜索content中包含[I am]的記錄(注意不能使用parse("content:'I am'"))  
  293. //          parser.setAllowLeadingWildcard(true);      //設置允許[*]或[?]出現在查詢字符的第一位,即[name:*de],否則[name:*de]會報異常  
  294. //          query = parser.parse("name:*de");          //Lucene默認的第一個字符不允許爲通配符,因爲這樣效率比較低  
  295. //          //parse("+am +name:Jade")--------------搜索content中包括[am]的,並且name=Jade的記錄  
  296. //          //parse("am AND NOT name:Jade")--------搜索content中包括[am]的,並且nam不是Jade的記錄  
  297. //          //parse("(blog OR am) AND name:Jade")--搜索content中包括[blog]或者[am]的,並且name=Jade的記錄  
  298. //          query = parser.parse("-name:Jack +I");     //搜索content中包括[I]的,並且name不是Jack的記錄(加減號要放到域說明的前面)  
  299. //          query = parser.parse("id:[1 TO 3]");       //搜索id值從1到3的記錄(TO必須大寫,且這種方式沒有辦法匹配數字)  
  300. //          query = parser.parse("id:{1 TO 3}");       //搜索id=2的記錄  
  301.             query = parser.parse("name:Jadk~");        //模糊搜索  
  302.         } catch (ParseException e) {  
  303.             e.printStackTrace();  
  304.         }  
  305.         this.doSearch(query);  
  306.     }  
  307.       
  308.       
  309.     /** 
  310.      * 普通的分頁搜索 
  311.      * @see 適用於lucene3.5之前 
  312.      * @param expr      搜索表達式 
  313.      * @param pageIndex 頁碼 
  314.      * @param pageSize  分頁大小 
  315.      */  
  316.     public void searchPage(String expr, int pageIndex, int pageSize){  
  317.         IndexSearcher searcher = this.getIndexSearcher();  
  318.         QueryParser parser = new QueryParser(Version.LUCENE_36, "mycontent"new StandardAnalyzer(Version.LUCENE_36));  
  319.         try {  
  320.             Query query = parser.parse(expr);  
  321.             TopDocs tds = searcher.search(query, pageIndex*pageSize);  
  322.             ScoreDoc[] sds = tds.scoreDocs;  
  323.             for(int i=(pageIndex-1)*pageSize; i<pageIndex*pageSize; i++){  
  324.                 Document doc = searcher.doc(sds[i].doc);  
  325.                 System.out.println("文檔編號:" + sds[i].doc + "-->" + doc.get("myname") + "-->" + doc.get("mycontent"));  
  326.             }  
  327.         } catch (Exception e) {  
  328.             e.printStackTrace();  
  329.         } finally {  
  330.             if(null != searcher){  
  331.                 try {  
  332.                     searcher.close();  
  333.                 } catch (IOException e) {  
  334.                     e.printStackTrace();  
  335.                 }  
  336.             }  
  337.         }  
  338.     }  
  339.       
  340.       
  341.     /** 
  342.      * 基於searchAfter的分頁搜索 
  343.      * @see 適用於Lucene3.5 
  344.      * @param expr      搜索表達式 
  345.      * @param pageIndex 頁碼 
  346.      * @param pageSize  分頁大小 
  347.      */  
  348.     public void searchPageByAfter(String expr, int pageIndex, int pageSize){  
  349.         IndexSearcher searcher = this.getIndexSearcher();  
  350.         QueryParser parser = new QueryParser(Version.LUCENE_36, "mycontent"new StandardAnalyzer(Version.LUCENE_36));  
  351.         try {  
  352.             Query query = parser.parse(expr);  
  353.             TopDocs tds = searcher.search(query, (pageIndex-1)*pageSize);  
  354.             //使用IndexSearcher.searchAfter()搜索,該方法第一個參數爲上一頁記錄中的最後一條記錄  
  355.             if(pageIndex > 1){  
  356.                 tds = searcher.searchAfter(tds.scoreDocs[(pageIndex-1)*pageSize-1], query, pageSize);  
  357.             }else{  
  358.                 tds = searcher.searchAfter(null, query, pageSize);  
  359.             }  
  360.             for(ScoreDoc sd : tds.scoreDocs){  
  361.                 Document doc = searcher.doc(sd.doc);  
  362.                 System.out.println("文檔編號:" + sd.doc + "-->" + doc.get("myname") + "-->" + doc.get("mycontent"));  
  363.             }  
  364.         } catch (Exception e) {  
  365.             e.printStackTrace();  
  366.         } finally {  
  367.             if(null != searcher){  
  368.                 try {  
  369.                     searcher.close();  
  370.                 } catch (IOException e) {  
  371.                     e.printStackTrace();  
  372.                 }  
  373.             }  
  374.         }  
  375.     }  
  376. }  


下面是JUnit4.x編寫的測試

  1. package com.jadyer.test;  
  2.   
  3. import java.io.File;  
  4.   
  5. import org.junit.Before;  
  6. import org.junit.Test;  
  7.   
  8. import com.jadyer.lucene.HelloSearch;  
  9.   
  10. public class HelloSearchTest {  
  11.     private HelloSearch hello;  
  12.       
  13.     @Before  
  14.     public void init(){  
  15.         hello = new HelloSearch();  
  16.     }  
  17.       
  18.       
  19.     @Test  
  20.     public void searchByTerm(){  
  21.         hello.searchByTerm("content""my");  
  22.     }  
  23.       
  24.       
  25.     @Test  
  26.     public void searchByTermRange(){  
  27.         hello.searchByTermRange("name""M""o");  
  28.     }  
  29.       
  30.       
  31.     @Test  
  32.     public void searchByNumericRange(){  
  33.         hello.searchByNumericRange("attach"25);  
  34.     }  
  35.       
  36.       
  37.     @Test  
  38.     public void searchByPrefix(){  
  39.         hello.searchByPrefix("content""b");  
  40.     }  
  41.       
  42.       
  43.     @Test  
  44.     public void searchByWildcard(){  
  45.         hello.searchByWildcard("name""Ja??er");  
  46.     }  
  47.       
  48.       
  49.     @Test  
  50.     public void searchByFuzzy(){  
  51.         hello.searchByFuzzy("name""Jadk");  
  52.     }  
  53.       
  54.       
  55.     @Test  
  56.     public void searchByBoolean(){  
  57.         hello.searchByBoolean();  
  58.     }  
  59.       
  60.       
  61.     @Test  
  62.     public void searchByPhrase(){  
  63.         hello.searchByPhrase();  
  64.     }  
  65.       
  66.       
  67.     @Test  
  68.     public void searchByQueryParse(){  
  69.         hello.searchByQueryParse();  
  70.     }  
  71.       
  72.       
  73.     @Test  
  74.     public void searchPage(){  
  75.         for(File file : new File("myExample/03_index/").listFiles()){  
  76.             file.delete();  
  77.         }  
  78.         hello = new HelloSearch(true);  
  79.         hello.searchPage("mycontent:javase"210);  
  80.     }  
  81.       
  82.       
  83.     @Test  
  84.     public void searchPageByAfter(){  
  85.         for(File file : new File("myExample/03_index/").listFiles()){  
  86.             file.delete();  
  87.         }  
  88.         hello = new HelloSearch(true);  
  89.         hello.searchPageByAfter("mycontent:javase"310);  
  90.     }  
  91. }  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章