1. 程式人生 > >lucene簡單的學習(一)

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("哈哈");
    }
}


lucenedoc.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就是這樣,可以設定某些欄位為不儲存,但是可以用來檢索。

終於在一篇文章裡看到這幾句話,突然間就明白了。

  1. //Store.YES 儲存 可以查詢 可以列印內容

  2. Field storeYes = new Field("storeyes","storeyes",Store.YES,Index.TOKENIZED);

  3. //Store.NO 不儲存 可以查詢 不可列印內容 由於不儲存內容所以節省空間

  4. Field storeNo = new Field("storeno","storeno",Store.NO,Index.TOKENIZED);

  5. //Store.COMPRESS 壓縮儲存 可以查詢 可以列印內容 可以節省生成索引檔案的空間,

  6. Field storeCompress = new Field("storecompress","storecompress",Store.COMPRESS,Index.TOKENIZED); 

至此,對於理解Store.YES,Store.NO 就是不儲存就不能直接獲取此欄位的內容,儲存了就可以。但是兩者都可以用於檢索。

欄位是否能被搜尋,還與Index有關。