lucene簡單的學習(一)
今天小白剛學lucene,記錄下自己的學習。先上程式碼,然後再分析
package com.wm.util; import java.io.*; import java.util.ArrayList; import java.util.Date; import java.util.List; import jxl.Cell; import jxl.Sheet; import jxl.Workbook; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.document.LongField; import org.apache.lucene.document.TextField; import org.apache.lucene.document.Field.Store; import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriterConfig; import org.apache.lucene.queryparser.classic.QueryParser; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.Query; import org.apache.lucene.search.ScoreDoc; import org.apache.lucene.store.Directory; import org.apache.lucene.store.FSDirectory; import org.apache.lucene.util.Version; import org.apache.poi.hwpf.HWPFDocument; import org.apache.poi.hwpf.usermodel.Range; /** * @author xinghl * */ public class IndexManager{ private static IndexManager indexManager; private static String content=""; private static String INDEX_DIR = "/Users/wangmiao/WebstormProjects/luceneIndex"; private static String DATA_DIR = "/Users/wangmiao/WebstormProjects/luceneData"; private static Analyzer analyzer = null; private static Directory directory = null; private static IndexWriter indexWriter = null; /** * 建立索引管理器 * @return 返回索引管理器物件 */ public IndexManager getManager(){ if(indexManager == null){ this.indexManager = new IndexManager(); } return indexManager; } /** * 建立當前檔案目錄的索引 * @param path 當前檔案目錄 * @return 是否成功 */ public static boolean createIndex(String path) throws Exception{ Date date1 = new Date(); List<File> fileList = getFileList(path); //建立分詞的器物 analyzer = new StandardAnalyzer(Version.LUCENE_CURRENT); //建立索引目錄(FSDirectory 索引庫的存放位置,在硬碟上)/(RAMDirectory 索引庫在記憶體上) directory = FSDirectory.open(new File(INDEX_DIR)); //索引的寫入操作 IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_CURRENT, analyzer); indexWriter = new IndexWriter(directory, config); int id=0; for (File file : fileList) { content = ""; //獲取檔案字尾 String type = file.getName().substring(file.getName().lastIndexOf(".")+1); if("txt".equalsIgnoreCase(type)){ content += txt2String(file); }else if("doc".equalsIgnoreCase(type)){ content += doc2String(file); }else if("xls".equalsIgnoreCase(type)){ content += xls2String(file); } System.out.println("name :"+file.getName()); System.out.println("path :"+file.getPath()); System.out.println("content :"+content); System.out.println(); try{ File indexFile = new File(INDEX_DIR); if (!indexFile.exists()) { indexFile.mkdirs(); } //繼承field的集合,然後新增到IndexWrite中,有點蕾絲表中的行的概念 Document document = new Document(); // 提供了對要分析的欄位進行何種處理,以KV形式呈現。 // ①:Field.Store.YES, Field.Index.NOT_ANALYZED 表示對索引欄位採取:原樣儲存並且不被StandardAnalyzer進行切分。 // ②: Field.Store.NO, Field.Index.ANALYZED 不儲存但是要被StandardAnalyzer切分。 //Store.YES存在記憶體中,可以被搜尋出來,Store.NO不存在記憶體中,但可以被搜尋,但結果不會被搜尋出來 document.add(new TextField("filename", file.getName(), Store.YES)); document.add(new Field("content", content, Store.YES,Field.Index.ANALYZED)); document.add(new TextField("path", file.getPath(), Store.YES)); document.add(new Field("id",id+"",Store.YES,Field.Index.NO)); indexWriter.addDocument(document); indexWriter.commit(); id++; }catch(Exception e){ e.printStackTrace(); } content = ""; } closeWriter(); Date date2 = new Date(); System.out.println("建立索引-----耗時:" + (date2.getTime() - date1.getTime()) + "ms\n"); return true; } /** * 讀取txt檔案的內容 * @param file 想要讀取的檔案物件 * @return 返回檔案內容 */ public static String txt2String(File file){ String result = ""; try{ InputStreamReader in = new InputStreamReader(new FileInputStream(file),"UTF-8"); BufferedReader br = new BufferedReader(in);//構造一個BufferedReader類來讀取檔案 String s = null; while((s = br.readLine())!=null){//使用readLine方法,一次讀一行 result = result + "\n" +s; } br.close(); }catch(Exception e){ e.printStackTrace(); } return result; } /** * 讀取doc檔案內容 * @param file 想要讀取的檔案物件 * @return 返回檔案內容 */ public static String doc2String(File file){ String result = ""; try{ FileInputStream fis = new FileInputStream(file); HWPFDocument doc = new HWPFDocument(fis); Range rang = doc.getRange(); result += rang.text(); fis.close(); }catch(Exception e){ e.printStackTrace(); } return result; } /** * 讀取xls檔案內容 * @param file 想要讀取的檔案物件 * @return 返回檔案內容 */ public static String xls2String(File file){ String result = ""; try{ FileInputStream fis = new FileInputStream(file); StringBuilder sb = new StringBuilder(); jxl.Workbook rwb = Workbook.getWorkbook(fis); Sheet[] sheet = rwb.getSheets(); for (int i = 0; i < sheet.length; i++) { Sheet rs = rwb.getSheet(i); for (int j = 0; j < rs.getRows(); j++) { Cell[] cells = rs.getRow(j); for(int k=0;k<cells.length;k++) sb.append(cells[k].getContents()); } } fis.close(); result += sb.toString(); }catch(Exception e){ e.printStackTrace(); } return result; } /** * 查詢索引,返回符合條件的檔案 * @param text 查詢的字串 * @return 符合條件的檔案List */ public static void searchIndex(String text){ Date date1 = new Date(); try{ directory = FSDirectory.open(new File(INDEX_DIR)); analyzer = new StandardAnalyzer(Version.LUCENE_CURRENT); DirectoryReader ireader = DirectoryReader.open(directory); //IndexSearcher:這個我們可以理解成以只讀的形式開啟由IndexWriter建立的索引庫,search給QueryParser提供了查詢的橋樑。 IndexSearcher isearcher = new IndexSearcher(ireader); //QueryParser:這玩意提供了一個parse方法能夠將我們要查詢的詞轉化為lucene能夠理解了查詢表示式。 QueryParser parser = new QueryParser(Version.LUCENE_CURRENT, "content", analyzer); Query query = parser.parse(text); //Hits:這個就是獲取匹配結果的一個指標,優點類似C#中的延遲載入,目的都是一樣,提高效能。 ScoreDoc[] hits = isearcher.search(query, null, 1000).scoreDocs; for (int i = 0; i < hits.length; i++) { Document hitDoc = isearcher.doc(hits[i].doc); System.out.println("____________________________"); System.out.println(hitDoc.get("filename")); System.out.println("content>>"+hitDoc.get("content")); System.out.println(hitDoc.get("path")); System.out.println(hitDoc.get("id")); System.out.println("____________________________"); } ireader.close(); directory.close(); }catch(Exception e){ e.printStackTrace(); } Date date2 = new Date(); System.out.println("檢視索引-----耗時:" + (date2.getTime() - date1.getTime()) + "ms\n"); } /** * 過濾目錄下的檔案 * @param dirPath 想要獲取檔案的目錄 * @return 返回檔案list */ public static List<File> getFileList(String dirPath) { File[] files = new File(dirPath).listFiles(); List<File> fileList = new ArrayList<File>(); for (File file : files) { if (isTxtFile(file.getName())) { fileList.add(file); } } return fileList; } /** * 判斷是否為目標檔案,目前支援txt xls doc格式 * @param fileName 檔名稱 * @return 如果是檔案型別滿足過濾條件,返回true;否則返回false */ public static boolean isTxtFile(String fileName) { if (fileName.lastIndexOf(".txt") > 0) { return true; }else if (fileName.lastIndexOf(".xls") > 0) { return true; }else if (fileName.lastIndexOf(".doc") > 0) { return true; } return false; } public static void closeWriter() throws Exception { if (indexWriter != null) { indexWriter.close(); } } /** * 刪除檔案目錄下的所有檔案 * @param file 要刪除的檔案目錄 * @return 如果成功,返回true. */ public static boolean deleteDir(File file){ if(file.isDirectory()){ File[] files = file.listFiles(); for(int i=0; i<files.length; i++){ deleteDir(files[i]); } } file.delete(); return true; } public static void main(String[] args) throws Exception{ // File fileIndex = new File(INDEX_DIR); // if(deleteDir(fileIndex)){ // fileIndex.mkdir(); // }else{ // fileIndex.mkdir(); // } createIndex(DATA_DIR); searchIndex("哈哈"); } }
lucene在doc.add(new Field("content",curArt.getContent(),Field.Store.NO,Field.Index.TOKENIZED));
Field有兩個屬性可選:儲存和索引。
通過儲存屬性你可以控制是否對這個Field進行儲存;
通過索引屬性你可以控制是否對該Field進行索引。
事實上對這兩個屬性的正確組合很重要。
Field.Index |
Field.Store |
說明 |
TOKENIZED(分詞) |
YES | 被分詞索引且儲存 |
TOKENIZED |
NO |
被分詞索引但不儲存 |
NO |
YES |
這是不能被搜尋的,它只是被搜尋內容的附屬物。如URL等 |
UN_TOKENIZED |
YES/NO |
不被分詞,它作為一個整體被搜尋,搜一部分是搜不出來的 |
NO |
NO |
沒有這種用法 |
我們以文章表為例.articleinfo.有ID,title(標題),sumary(摘要),content(內容),userName(使用者名稱)
其中title(標題),sumary(摘要)屬於第一種情況,既要索引也要分詞,也要儲存.
content(內容)要分詞,索引,但不儲存.由於他太大了,而且介面也不用顯示整個內容.
ID要儲存,不用索引.因為沒人用他來查詢.但拼URL卻很需要他.索引要儲存.
userName(使用者名稱)索引,但不分詞.可用儲存.為什麼不分詞?比如"成吉思汗",我不想被"成漢"搜尋到.我希望要麼"成吉思汗"或者"*吉思*"萬用字元搜到.
Field.Store.YES:儲存欄位值(未分詞前的欄位值)
Field.Store.NO:不儲存,儲存與索引沒有關係
Field.Store.COMPRESS:壓縮儲存,用於長文字或二進位制,但效能受損
Field.Index.ANALYZED:分詞建索引
Field.Index.ANALYZED_NO_NORMS:分詞建索引,但是Field的值不像通常那樣被儲存,而是隻取一個byte,這樣節約儲存空間
Field.Index.NOT_ANALYZED:不分詞且索引
Field.Index.NOT_ANALYZED_NO_NORMS:不分詞建索引,Field的值去一個byte儲存
TermVector表示文件的條目(由一個Document和Field定位)和它們在當前文件中所出現的次數
Field.TermVector.YES:為每個文件(Document)儲存該欄位的TermVector
Field.TermVector.NO:不儲存TermVector
Field.TermVector.WITH_POSITIONS:儲存位置
Field.TermVector.WITH_OFFSETS:儲存偏移量
Field.TermVector.WITH_POSITIONS_OFFSETS:儲存位置和偏移量
一:索引:
相信大家對索引還是比較熟悉的,lucene能夠將我們內容切分成很多詞,然後將詞作為key,建立“倒排索引”,然後放到索引庫中,在上面
的例子中,我們看到了索引過程中使用到了IndexWriter,FSDirectory,StandardAnalyzer,Document和Field這些類,下面簡要分析下。
1:IndexWriter
我們看到該類有一個AddDocument方法,所以我們認為該類實現了索引的寫入操作。
2:FSDirectory
這個就更簡單了,提供了索引庫的存放位置,比如我們這裡的D:\Sample,或許有人問,能不能存放在記憶體中,在強大的lucene面前當然
可以做到,lucene中的RAMDirectory就可以實現,當然我們的記憶體足夠大的話,還是可以用記憶體承載索引庫,進而提高搜尋的效率。
3:StandardAnalyzer
這個算是索引過程中最最關鍵的一步,也是我們使用lucene非常慎重考慮的東西,之所以我們能搜尋秒殺,關鍵在於我們如何將輸入的內容
進行何種形式的切分,當然不同的切分形式誕生了不同的分析器,StandardAnalyzer就是一個按照單字分詞的一種分析器,詳細的介紹後續文
章分享。
4:Document
在上面的例子可以看到,他是承載field的集合,然後新增到IndexWriter中,有點類似表中的行的概念。
5: Field
提供了對要分析的欄位進行何種處理,以KV形式呈現。
①:Field.Store.YES, Field.Index.NOT_ANALYZED 表示對索引欄位採取:原樣儲存並且不被StandardAnalyzer進行切分。
②: Field.Store.NO, Field.Index.ANALYZED 不儲存但是要被StandardAnalyzer切分。
二:搜尋
這個比較容易,根據我們輸入的詞lucene能夠在索引庫中快速定位到我們要找的詞,同樣我們可以看到IndexSearcher,QueryParser,Hits。
1:IndexSearcher
這個我們可以理解成以只讀的形式開啟由IndexWriter建立的索引庫,search給QueryParser提供了查詢的橋樑。
2:QueryParser
這玩意提供了一個parse方法能夠將我們要查詢的詞轉化為lucene能夠理解了查詢表示式。
3:Hits
這個就是獲取匹配結果的一個指標,優點類似C#中的延遲載入,目的都是一樣,提高效能。
連線補充
http://blog.csdn.net/chenyi0834/article/details/7846868
http://www.cnblogs.com/ShaYeBlog/archive/2012/09/04/2670432.html
http://www.codeweblog.com/%E6%80%BB%E7%AE%97%E6%89%BE%E5%88%B0lucene-%E5%85%B3%E4%BA%8Estore-yes%E7%9A%84%E8%A7%A3%E9%87%8A%E4%BA%86/
最後ps:
一直對Lucene Store.YES不太理解,網上多數的說法是儲存欄位,NO為不儲存。
這樣的解釋有點鬱悶:字面意思一看就明白,但是不解。
之前我的理解是:如果欄位可以不儲存,那要怎麼搜尋這個不儲存的欄位呢?
原來Lucene就是這樣,可以設定某些欄位為不儲存,但是可以用來檢索。
終於在一篇文章裡看到這幾句話,突然間就明白了。
- //Store.YES 儲存 可以查詢 可以列印內容
Field storeYes = new Field("storeyes","storeyes",Store.YES,Index.TOKENIZED);
- //Store.NO 不儲存 可以查詢 不可列印內容 由於不儲存內容所以節省空間
Field storeNo = new Field("storeno","storeno",Store.NO,Index.TOKENIZED);
- //Store.COMPRESS 壓縮儲存 可以查詢 可以列印內容 可以節省生成索引檔案的空間,
Field storeCompress = new Field("storecompress","storecompress",Store.COMPRESS,Index.TOKENIZED);
至此,對於理解Store.YES,Store.NO 就是不儲存就不能直接獲取此欄位的內容,儲存了就可以。但是兩者都可以用於檢索。
欄位是否能被搜尋,還與Index有關。