開源搜索引擎 - Lucene入門

Lucene是一個開源的搜索引擎, 或者叫全文索引工具, 用於快速查找, 而且可以獲得一個匹配度得分;

比如我們直接使用sql進行搜索的時候, 可能會使用Like關鍵詞

但是這樣有兩個不便之處, 一是當數據太多時, 會比較慢, 二是這樣無法得到一些基於關鍵詞的匹配相似度得分;

用lucene可以實現上面兩點;

那麼直接開始做一個簡單的搜索的demo吧~

一.創建索引

假設現在已經有了一些數據, 放在了一個list裏面:

List<String> productNames = new ArrayList<>();
productNames.add("飛利浦led燈泡e27螺口暖白球泡燈家用照明超亮節能燈泡轉色溫燈泡");
productNames.add("飛利浦led燈泡e14螺口蠟燭燈泡3W尖泡拉尾節能燈泡暖黃光源Lamp");
productNames.add("雷士照明 LED燈泡 e27大螺口節能燈3W球泡燈 Lamp led節能燈泡");
productNames.add("飛利浦 led燈泡 e27螺口家用3w暖白球泡燈節能燈5W燈泡LED單燈7w");
productNames.add("飛利浦led小球泡e14螺口4.5w透明款led節能燈泡照明光源lamp單燈");
productNames.add("飛利浦蒲公英護眼檯燈工作學習閱讀節能燈具30508帶光源");
productNames.add("歐普照明led燈泡蠟燭節能燈泡e14螺口球泡燈超亮照明單燈光源");
productNames.add("歐普照明led燈泡節能燈泡超亮光源e14e27螺旋螺口小球泡暖黃家用");
productNames.add("聚歐普照明led燈泡節能燈泡e27螺口球泡家用led照明單燈超亮光源");
Directory index = createIndex(analyzer, productNames);

最後一行的createIndex方法實現如下:

private static Directory createIndex(IKAnalyzer analyzer, List<String> products) throws IOException {
    Directory index = new RAMDirectory();
    IndexWriterConfig config = new IndexWriterConfig(analyzer);
    IndexWriter writer = new IndexWriter(index, config);
 
    for (String name : products) {
        addDoc(writer, name);
    }
    writer.close();
    return index;
}

這裏傳入的參數有兩個, 第一個是中文分詞器, 第二個是存入的數據;

第一步是創建一個Dictionary, 然後構建一個IndexWriter用於寫入, 這裏要給這個IndexWritter使用一個Config因爲需要使用中文分詞器對傳入的數據進行解析, 然後創建好writer之後循環寫入數據即可, 這裏的addDoc方法實現如下, 最後關閉writer即可:

private static void addDoc(IndexWriter w, String name) throws IOException {
    Document doc = new Document();
    doc.add(new TextField("name", name, Field.Store.YES));
    w.addDocument(doc);
}

在這裏傳入一個寫入器;
其實這裏是創建一個新的Document然後把這個document用writer寫入到之前創建的dictionary裏面, 此處的document是可以有多個Field的, 但是這裏只傳入了一項數據, 所以只需要用到一個TextField叫name;

然後到這裏爲止, 我們的數據部分就準備完成了, 然後下面我們開始執行一次查詢

二.執行一次查詢

先構建一個查詢器

首先我們獲取到用戶提供的關鍵詞, 然後根據這個關鍵詞來構建一個查詢器, 大概代碼如下:

String keyword = "護眼帶光源";
Query query = new QueryParser("name", analyzer).parse(keyword);

這裏我們首先獲得了一個keyword, 然後構造了一個QueryParser解析器, 然後傳入一個analyzer(就是之前創建的中文分詞器), 然後我們要對name進行搜索, 解析keyword, 這樣一個搜索的查詢器就構建好了;

然後進行搜索
IndexReader reader = DirectoryReader.open(index);
IndexSearcher searcher = new IndexSearcher(reader);
int numberPerPage = 1000;
System.out.printf("當前一共有%d條數據%n",productNames.size());
System.out.printf("查詢關鍵字是:\"%s\"%n",keyword);
ScoreDoc[] hits = searcher.search(query, numberPerPage).scoreDocs;

首先要構造一個IndexReader對之前創建的Dictionary進行讀取, 這裏調用的是DirectoryReader的靜態方法, 然後使用IndexSearcher來搜索, 設定每頁要顯示多少條數據, 然後調用searcher的search方法, 這裏的返回的是一個scoreDoc的數組;

然後顯示搜索結果

其實這部分就是要對剛纔獲得的結果, 也就是那個scoreDoc這個數組進行解析, 獲得內容;
大概代碼如下:

private static void showSearchResults(IndexSearcher searcher, ScoreDoc[] hits, Query query, IKAnalyzer analyzer)
        throws Exception {
    System.out.println("找到 " + hits.length + " 個命中.");
    System.out.println("序號\t匹配度得分\t結果");
    for (int i = 0; i < hits.length; ++i) {
        ScoreDoc scoreDoc= hits[i];
        int docId = scoreDoc.doc;
        Document d = searcher.doc(docId);
        List<IndexableField> fields = d.getFields();
        System.out.print((i + 1));
        System.out.print("\t" + scoreDoc.score);
        for (IndexableField f : fields) {
            System.out.print("\t" + d.get(f.name()));
        }
        System.out.println();
    }
}

可以看到, 這裏直接取scoreDoc.doc獲得的是在原來的Dictionary中的document id, 然後要利用searcher的doc方法來獲得這個document的內容, 然後此處的document中只有一個field就是name, 但是往往可能會有多個field, 所以這裏用了遍歷所有的field來輸出內容, 兼容性更好

整理

所以Lucene的基本流程是這樣的:

第一步創建索引
獲得數據 → 創建Dictionary
第二步開始查詢
查詢解析器處理關鍵詞 → 查詢器 → IndexSearcher(IndexReader) → ScoreDoc

一些花裏胡哨的東西

1.分頁查詢
這個lucene可以使用分頁查詢, 可以看到, 在搜索這一步, 有這個操作

ScoreDoc[] hits = searcher.search(query, numberPerPage).scoreDocs;

這一步就是把符合的hits的scoredoc全部存到了這個hits數組裏面, 也就相當於保存到了內存中, 這樣在數據量較大的時候是不太好的

所以可以使用分頁查詢, 比如每頁10條數據, 一共20頁, 我要查第7頁的那10條數據

上面的方法就是, 我把這20頁的200條數據全部存到內存中, 然後去讀取我要的;

但是還可以我先查找到第69條數據, 然後使用searchAfter方法, 查找後面的10條數據, 這樣就不用把所有數據都放到內存裏了, 給個大概的demo把~

    private static ScoreDoc[] pageSearch2(Query query, IndexSearcher searcher, int pageNow, int pageSize)throws IOException{
        int start = (pageNow-1)*pageSize;
        if(start == 0){
            return searcher.search(query, pageSize).scoreDocs;
        }

        TopDocs topDocs = searcher.search(query, start);
        ScoreDoc preScoreDoc = topDocs.scoreDocs[start-1];
        topDocs = searcher.searchAfter(preScoreDoc, query, pageSize);
        return topDocs.scoreDocs;
    }

2.索引的增加刪除修改
Dictionary在創建之後也是可以修改的

增加的方法就和前面創建的時候一樣, 還是用writer來寫進去add就好了;
刪除可以用deleteDocument方法, 類似這樣:

        //刪除id=51173的數據
        IndexWriterConfig config = new IndexWriterConfig(analyzer);
        IndexWriter indexWriter = new IndexWriter(index, config);
        indexWriter.deleteDocuments(new Term("id", "51173"));
        indexWriter.commit();
        indexWriter.close();

然後lucene還支持其他的一些方式的刪除, 比如這樣的:

    DeleteDocuments(Query query):根據Query條件來刪除單個或多個Document
    DeleteDocuments(Query[] queries):根據Query條件來刪除單個或多個Document
    DeleteDocuments(Term term):根據Term來刪除單個或多個Document
    DeleteDocuments(Term[] terms):根據Term來刪除單個或多個Document
    DeleteAll():刪除所有的Document

然後修改也差不多, demo給一個:

IndexWriterConfig config = new IndexWriterConfig(analyzer);
IndexWriter indexWriter = new IndexWriter(index, config);
Document doc = new Document();
doc.add(new TextField("id", "51173", Field.Store.YES));
doc.add(new TextField("name", "神鞭,鞭沒了,神還在", Field.Store.YES));
doc.add(new TextField("category", "道具", Field.Store.YES));
doc.add(new TextField("price", "998", Field.Store.YES));
doc.add(new TextField("place", "南海羣島", Field.Store.YES));
doc.add(new TextField("code", "888888", Field.Store.YES));
indexWriter.updateDocument(new Term("id", "51173"), doc );
indexWriter.commit();
indexWriter.close();

更多內容可以到HOW2J網站學習

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章