Lucene詳解

一.lucene原理

    Lucene 是apache軟件基金會一個開放源代碼的全文檢索引擎工具包,是一個全文檢索引擎的架構,提供了完整的查詢引擎和索引引擎,部分文本分析引擎。它不是一個完整的搜索應用程序,而是爲你的應用程序提供索引和搜索功能。lucene 可以爲文本類型的數據創建索引,因此你只要能把你要索引的數據格式轉化的文本的,Lucene 就能對你的文檔進行索引和搜索。好比你要對一些 HTML 文檔,PDF 文檔進行索引的話你就首先須要把 HTML 文檔和 PDF 文檔轉化成文本格式的,而後將轉化後的內容交給 Lucene 進行索引,而後把建立好的索引文件保存到磁盤或者內存中,最後根據用戶輸入的查詢條件在索引文件上進行查詢。前端

搜索應用程序和 Lucene 之間的關係,也反映了利用 Lucene 構建搜索應用程序的流程:java

二. 索引和搜索

索引是現代搜索引擎的核心,創建索引的過程就是把源數據處理成很是方便查詢的索引文件的過程。爲何索引這麼重要呢,試想你如今要在大量的文檔中搜索含有某個關鍵詞的文檔,那麼若是不創建索引的話你就須要把這些文檔順序的讀入內存,而後檢查這個文章中是否是含有要查找的關鍵詞,這樣的話就會耗費很是多的時間,想一想搜索引擎但是在毫秒級的時間內查找出要搜索的結果的。這就是因爲創建了索引的緣由,你能夠把索引想象成這樣一種數據結構,他可以使你快速的隨機訪問存儲在索引中的關鍵詞,進而找到該關鍵詞所關聯的文檔。Lucene 採用的是一種稱爲反向索引(inverted index)的機制。反向索引就是說咱們維護了一個詞 / 短語表,對於這個表中的每一個詞 / 短語,都有一個鏈表描述了有哪些文檔包含了這個詞 / 短語。這樣在用戶輸入查詢條件的時候,就能很是快的獲得搜索結果。搜索引擎首先會對搜索的關鍵詞進行解析,而後再在創建好的索引上面進行查找,最終返回和用戶輸入的關鍵詞相關聯的文檔。對於中文用戶來講,最關心的問題是其是否支持中文的全文檢索。因爲Lucene良好架構設計,對中文的支持只需對其語言詞法分析接口進行擴展就能實現對中文檢索的支持。web

三. 索引步驟ajax

  1. 獲取內容: Lucene自己沒有提供獲取內容的工具或者組件,內容是要開發者本身提供相應的程序。這一步包括使用網絡爬蟲或蜘蛛程序來搜索和界定須要索引的內容。固然,數據來源可能包括數據庫、分佈式文件系統、本地xml等等。lucene做爲一款核心搜索庫,不提供任何功能來實現內容獲取。目前有大量的開源爬蟲軟件能夠實現這個功能,例如:Solrlucene的子項;Nutchapache項目,包含大規模的爬蟲工具,抓取和分辨web站點數據;Grub,比較流行的開源web爬蟲工具;Heritrix,一款開源的Internet文檔搜索程序;Aperture,支持從web站點、文件系統和郵箱中抓取,並解析和索引其中的文本數據。
  2. 創建文檔:獲取原始內容後,須要對這些內容進行索引,必須將這些內容轉換成部件(文檔)。文檔主要包括幾個帶值的域,好比標題,正文,摘要,做者和連接。若是文檔和域比較重要的話,還能夠添加權值。設計完方案後,須要將原始內容中的文本提取出來寫入各個文檔,這一步可使用文檔過濾器,開源項目如Tika,實現很好的文檔過濾。若是要獲取的原始內容存儲於數據庫中,有一些項目經過無縫連接內容獲取步驟和文檔創建步驟就能輕易地對數據庫表進行航因此操做和搜索操做,例如DBSightHibernate SearchLuSQLCompassOracle/Lucene集成項目。
  3. 文檔分析: 搜索引擎不能直接對文本進行索引:必須將文本分割成一系列被稱爲語彙單元的獨立的原子元素。每個語彙單元能大體與語言中的單詞對應起來,這個步驟決定文檔中的文本域如何分割成語彙單元系列。lucene提供了大量內嵌的分析器能夠輕鬆控制這步操做。
  4. 文檔索引: 將文檔加入到索引列表中。Lucene在這一步驟中提供了強檔的API,只需簡單調用提供的幾個方法就能夠實現出文檔索引的創建。爲了提供好的用戶體驗,索引是必需要處理好的一環:在設計和定製索引程序時必須圍繞如何提升用戶的搜索體驗來進行。

四. 搜索組件數據庫

  搜索組件即爲輸入搜索短語,而後進行分詞,然從索引中查找單詞,從而找到包含該單詞的文檔。搜索質量由查準率和查全率來衡量。搜索組件主要包括如下內容:
apache

  1. 用戶搜索界面:主要是和用戶進行交互的頁面,也就是呈如今瀏覽器中能看到的東西,這裏主要考慮的就是頁面UI設計了。一個良好的UI設計是吸引用戶的重要組成部分。
  2. 創建查詢:創建查詢主要是指用戶輸入所要查詢的短語,以普通HTML表單或者ajax的方式提交到後臺服務器端。而後把詞語傳遞給後臺搜索引擎。這就是一個簡單創建查詢的過程。
  3. 搜索查詢:即爲查詢檢索索引而後返回與查詢詞語匹配的文檔。而後把返回來的結構按照查詢請求來排序。搜索查詢組件覆蓋了搜索引擎中大部分的複雜內容。
  4. 展示結果:所謂展示結果,和第一個搜索界面相似。都是一個與用戶交互的前端展現頁面,做爲一個搜索引擎,用戶體驗永遠是第一位。其中前端展現在用戶體現上佔據了重要地位。

五. 官網實例解析瀏覽器

Lucene的使用主要體如今兩個步驟:
服務器

  1. 建立索引,經過IndexWriter對不一樣的文件進行索引的建立,並將其保存在索引相關文件存儲的位置中。
  2. 經過索引查尋關鍵字相關文檔。

下面針對官網上面給出的一個例子,進行分析:網絡

 

    Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_CURRENT);  
      
        // Store the index in memory:  
        Directory directory = new RAMDirectory();  
        // To store an index on disk, use this instead:  
        //Directory directory = FSDirectory.open("/tmp/testindex");  
        IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_CURRENT, analyzer);  
        IndexWriter iwriter = new IndexWriter(directory, config);  
        Document doc = new Document();  
        String text = "This is the text to be indexed.";  
        doc.add(new Field("fieldname", text, TextField.TYPE_STORED));  
        iwriter.addDocument(doc);  
        iwriter.close();  
          
        // Now search the index:  
        DirectoryReader ireader = DirectoryReader.open(directory);  
        IndexSearcher isearcher = new IndexSearcher(ireader);  
        // Parse a simple query that searches for "text":  
        QueryParser parser = new QueryParser(Version.LUCENE_CURRENT, "fieldname", analyzer);  
        Query query = parser.parse("text");  
        ScoreDoc[] hits = isearcher.search(query, null, 1000).scoreDocs;  
        assertEquals(1, hits.length);  
        // Iterate through the results:  
        for (int i = 0; i < hits.length; i++) {  
          Document hitDoc = isearcher.doc(hits[i].doc);  
          assertEquals("This is the text to be indexed.", hitDoc.get("fieldname"));  
        }  
        ireader.close();  
        directory.close();  

 

索引的建立

首先,咱們須要定義一個詞法分析器。數據結構

  好比一句話,「我愛咱們的中國!」,如何對他拆分,扣掉停頓詞「的」,提取關鍵字「我」「咱們」「中國」等等。這就要藉助的詞法分析器Analyzer來實現。這裏面使用的是標準的詞法分析器,若是專門針對漢語,還能夠搭配paoding,進行使用。

1 Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_CURRENT);

  參數中的Version.LUCENE_CURRENT,表明使用當前的Lucene版本,本文環境中也能夠寫成Version.LUCENE_40。

  第二步,肯定索引文件存儲的位置,Lucene提供給咱們兩種方式:

  1 本地文件存儲 

Directory directory = FSDirectory.open("/tmp/testindex");

  2 內存存儲

Directory directory = new RAMDirectory();

  能夠根據本身的須要進行設定。

  第三步,建立IndexWriter,進行索引文件的寫入。

IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_CURRENT, analyzer); IndexWriter iwriter = new IndexWriter(directory, config);

  這裏的IndexWriterConfig,據官方文檔介紹,是對indexWriter的配置,其中包含了兩個參數,第一個是目前的版本,第二個是詞法分析器Analyzer。 

  第四步,內容提取,進行索引的存儲。

Document doc = new Document(); String text = "This is the text to be indexed."; doc.add(new Field("fieldname", text, TextField.TYPE_STORED)); iwriter.addDocument(doc); iwriter.close();

  第一行,申請了一個document對象,這個相似於數據庫中的表中的一行。

  第二行,是咱們即將索引的字符串。

  第三行,把字符串存儲起來(由於設置了TextField.TYPE_STORED,若是不想存儲,可使用其餘參數,詳情參考官方文檔),並存儲「代表」爲"fieldname".

  第四行,把doc對象加入到索引建立中。

  第五行,關閉IndexWriter,提交建立內容。

  這就是索引建立的過程。

關鍵字查詢:

  第一步,打開存儲位置

DirectoryReader ireader = DirectoryReader.open(directory);

  第二步,建立搜索器

IndexSearcher isearcher = new IndexSearcher(ireader);

  第三步,相似SQL,進行關鍵字查詢

複製代碼
QueryParser parser = new QueryParser(Version.LUCENE_CURRENT, "fieldname", analyzer); Query query = parser.parse("text"); ScoreDoc[] hits = isearcher.search(query, null, 1000).scoreDocs; assertEquals(1, hits.length); for (int i = 0; i < hits.length; i++) { Document hitDoc = isearcher.doc(hits[i].doc); assertEquals("This is the text to be indexed.",hitDoc.get("fieldname")); }
複製代碼

  這裏,咱們建立了一個查詢器,並設置其詞法分析器,以及查詢的「表名「爲」fieldname「。查詢結果會返回一個集合,相似SQL的ResultSet,咱們能夠提取其中存儲的內容。

  關於各類不一樣的查詢方式,能夠參考官方手冊,或者推薦的PPT

  第四步,關閉查詢器等。

ireader.close();
directory.close();

本身實現的一個小實例:對一個文件夾內的內容進行索引的建立,並根據關鍵字篩選文件,並讀取其中的內容

    package cn.lnu.edu.yxk;  
    import java.io.BufferedReader;  
    import java.io.File;  
    import java.io.FileInputStream;  
    import java.io.FileNotFoundException;  
    import java.io.FileReader;  
    import java.io.IOException;  
    import java.util.ArrayList;  
    import java.util.Date;  
    import java.util.List;  
      
    import jxl.Cell;  
    import jxl.Sheet;  
    import jxl.Workbook;  
    import jxl.read.biff.BiffException;  
      
    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.index.DirectoryReader;  
    import org.apache.lucene.index.IndexWriter;  
    import org.apache.lucene.index.IndexWriterConfig;  
    import org.apache.lucene.queryparser.classic.ParseException;  
    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.poi.hwpf.HWPFDocument;  
    import org.apache.poi.hwpf.usermodel.Range;  
    /** 
     * 對一個文件夾內的內容進行索引的建立,並根據關鍵字篩選文件,讀取其中的內容。 
     * @author yxk 
     * 
     */  
    public class IndexManager {  
      
        private static String content = "";//文件裏面的內容  
        private static String INDEX_DIR = "D:\\test\\luceneIndex";//索引建立的存儲目錄  
        private static String DATA_DIR = "D:\\test\\luceneData";//文件夾的目錄  
      
        private static Analyzer analyzer = null;//詞法分析器  
        private static Directory directory = null;//索引文件存儲的位置  
        private static IndexWriter indexWriter = null;//建立索引器,索引文件的寫入  
      
        /** 
         * 建立當前文件目錄的索引 
         * @param path當前目錄的文件 
         * @return 返回是否建立成功 
         */  
        public static Boolean createIndex(String path) {  
            Date date1 = new Date();//建立須要的時間  
      
            List<File> files = listFile(path);// 獲取指定目錄下得全部符合條件的文件  
      
            // 獲取文件的內容  
            for (File file : files) {  
                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(file.getName().getBytes().toString());  
                System.out.println();  
      
                try {  
                    analyzer = new StandardAnalyzer();//詞法分析器  
                      
                    directory = FSDirectory.open(new File(INDEX_DIR).toPath());//索引建立存儲的位置  
                    // System.out.println("ssss"  
                    // + new File(INDEX_DIR).toPath().toString());  
                      
                    //自動建立索引目錄  
                    File indexFile = new File(INDEX_DIR);  
                    if (!indexFile.exists()) {  
                        indexFile.mkdirs();  
                    }  
                      
                    //索引文件的寫入  
                    IndexWriterConfig config = new IndexWriterConfig(analyzer);  
                    indexWriter = new IndexWriter(directory, config);  
                      
                    /* 
                     * 內容提取,進行索引的存儲 
                     */  
                    //申請了一個document對象,這個相似於數據庫中的表中的一行。  
                    Document document = new Document();  
                      
                    //把字符串存儲起來(由於設置了TextField.TYPE_STORED,若是不想存儲,可使用其餘參數,詳情參考官方文檔),並存儲「代表」爲"fieldname".  
                    document.add(new org.apache.lucene.document.TextField(  
                            "filename", file.getName(), Field.Store.YES));//文件名索引建立  
                    document.add(new org.apache.lucene.document.TextField(  
                            "content", content, Field.Store.YES));//文件內容索引建立  
                    document.add(new org.apache.lucene.document.TextField("path",  
                            file.getPath(), Field.Store.YES));//文件路徑索引的建立  
                      
                    //把document對象加入到索引建立中  
                    indexWriter.addDocument(document);  
                      
                    //關閉IndexWriter,提交建立內容。  
                    indexWriter.commit();  
                    closeWriter();  
                      
                } catch (IOException e) {  
                    e.printStackTrace();  
                }  
                content = "";  
            }  
            Date date2 = new Date();  
            System.out.println("建立索引-----耗時:" + (date2.getTime() - date1.getTime())  
                    + "ms\n");  
            return true;  
        }  
      
        /** 
         * 查詢索引,返回符合條件的文件 
         *  
         * @param 查詢的字符串 
         * @return 符合條件的結果 
         * @throws IOException 
         */  
        public static void serarchIndex(String text) {  
            Date date1 = new Date();  
            try {  
                //打開存儲位置  
                directory = FSDirectory.open(new File(INDEX_DIR).toPath());  
                analyzer = new StandardAnalyzer();  
                DirectoryReader ireader = DirectoryReader.open(directory);  
                  
                //建立搜索器  
                IndexSearcher isearcher = new IndexSearcher(ireader);  
      
                /* 
                 * 相似SQL,進行關鍵字查詢 
                 */  
                QueryParser parser = new QueryParser("content", analyzer);  
                Query query = parser.parse(text);  
                //建立了一個查詢器,並設置其詞法分析器,以及查詢的「表名「爲」fieldname「。查詢結果會返回一個集合,相似SQL的ResultSet,咱們能夠提取其中存儲的內容。  
                ScoreDoc[] hits = isearcher.search(query, 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(hitDoc.get("content"));  
                    System.out.println(hitDoc.get("path"));  
                    System.out.println("------------");  
                }  
                  
                //關閉查詢器  
                ireader.close();  
                directory.close();  
                  
            } catch (IOException e) {  
                e.printStackTrace();  
            } catch (ParseException e) {  
                e.printStackTrace();  
            }  
            Date date2 = new Date();  
            System.out.println("關鍵字查詢-----耗時:" + (date2.getTime() - date1.getTime())  
                    + "ms\n");  
        }  
      
        /** 
         *  
         * @throws IOException 
         */  
        private static void closeWriter() throws IOException {  
            if (indexWriter != null)  
                indexWriter.close();  
      
        }  
      
        /** 
         * 讀取xls文件內容,引入jxl.jar類型的包 
         * @param file 
         * @return 返回內容 
         */  
        private 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; i < 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 (FileNotFoundException e) {  
                e.printStackTrace();  
            } catch (BiffException e) {  
                e.printStackTrace();  
            } catch (IOException e) {  
                e.printStackTrace();  
            }  
            return result;  
        }  
      
        /** 
         * 讀取doc類型文件的內容,經過poi.jar 
         * @param file的類型 
         * @return 返回文件的內容 
         */  
        private static String doc2String(File file) {  
            String result = "";  
            try {  
                FileInputStream fis = new FileInputStream(file);//文件輸入流  
                  
                HWPFDocument document = new HWPFDocument(fis);  
                Range range = document.getRange();  
                result += range.text();  
                  
                fis.close();  
                  
            } catch (FileNotFoundException e) {  
                e.printStackTrace();  
            } catch (IOException e) {  
                e.printStackTrace();  
            }  
            return result;  
        }  
      
        /** 
         * 讀取txt文件的內容 
         *  
         * @param file想要讀取的文件類型 
         * @return 返回文件內容 
         */  
        private static String txt2String(File file) {  
            String result = "";  
              
            try {  
                BufferedReader reader = new BufferedReader(new FileReader(file));  
                String s = "";  
                while ((s = reader.readLine()) != null) {  
                    result += result + "\n" + s;  
                }  
                  
                reader.close();  
                  
            } catch (FileNotFoundException e) {  
                e.printStackTrace();  
            } catch (IOException e) {  
                e.printStackTrace();  
            }  
            return result;  
        }  
      
        /** 
         * 過濾當前目錄下得文件 
         * @param path 當前目錄下得文件 
         * @return 返回符合條件的文件 
         */  
        private static List<File> listFile(String path) {  
            File[] files = new File(path).listFiles();  
            List<File> fileList = new ArrayList<File>();  
            for (File file : files) {  
                if (isTxtFile(file.getName())) {  
                    fileList.add(file);  
                }  
            }  
            return fileList;  
        }  
      
        /** 
         * 判斷是否爲目標文件,支持的格式爲.txt,.doc,.xls文件格式 若是是文件類型知足過濾條件,返回true;不然返回false 
         * @param name 根據文件名的後綴 
         * @return 是否符合格式規範 
         */  
        private static boolean isTxtFile(String name) {  
            if (name.lastIndexOf(".txt") > 0)  
                return true;  
            else if (name.lastIndexOf(".doc") > 0)  
                return true;  
            else if (name.lastIndexOf(".xls") > 0)  
                return true;  
            return false;  
        }  
      
        public static void main(String[] args) {  
            //建立索引目錄,運行一次,從新建立一次  
            File fileIndex = new File(INDEX_DIR);  
            if (deleteIndex(fileIndex)) {  
                fileIndex.mkdir();  
            } else {  
                fileIndex.mkdir();  
            }  
              
            //建立索引文件  
            createIndex(DATA_DIR);  
              
            //經過關鍵字查詢  
            serarchIndex("中華");  
      
        }  
      
        /** 
         * 刪除文件目錄下得全部文件 
         *  
         * @param fileIndex 當前索引目錄下得文件 
         * @return 返回是否刪除從新建立 
         */  
        private static boolean deleteIndex(File fileIndex) {  
            if (fileIndex.isDirectory()) {  
                File[] files = fileIndex.listFiles();  
                for (int i = 0; i < files.length; i++) {  
                    deleteIndex(files[i]);  
                }  
            }  
            fileIndex.delete();  
            return true;  
        }  
      
    }  
相關文章
相關標籤/搜索