1. 程式人生 > >lucene 初探

lucene 初探

聚集 分享圖片 ive 獲取 出現 void top readfile 創建索引

前言:

  window文件管理右上角, 有個搜索功能, 可以根據文件名進行搜索. 那如果從文件名上判斷不出內容, 我豈不是要一個一個的打開文件, 查看文件的內容, 去判斷是否是我要的文件?

  幾個, 十幾個文件還好, 如果是幾百個甚至幾萬上百萬, 我也能這麽去找麽?

  這不是找文件了, 而是找不自在, 找虐.

  那這個問題, 該怎麽解決呢?

  那就牽出了今天的話題了. lucene, 讓軟件去幫我們找就好了嘛.

lucene初探:

一. 原理介紹:

  在介紹原理之前, 先來使用一下百度搜索吧. 這個大家都用過的.

技術分享圖片

我明明搜索的是: 歡迎使用lucene, 但是從下面的結果來看, 並不是直接搜的全部, 而是將搜索語句進行了一個拆分操作, 然後綜合搜索. 最後一條尤其明顯.

那lucene裏面, 其實也是一樣的. 在搜索的時候也會進行拆分操作.

那文檔這麽多, lucene也是一個一個文件去找麽?

我們在進行數據庫查詢的時候, 在大數據量的時候都可以很快的找到想要的數據, 這是因為數據庫將數據進行了有序排列. 這種有序排列, 分兩種,

  一種叫聚集索引(id), 這個排列是跟具體存儲內容無關的, 是數據庫根據進入先後自己排的順序.

  另一種叫非聚集索引, 是根據要存儲數據的邏輯來排序的.

就像是查字典, 如果後面的字並不按照拼音排序, 而是雜亂無章的, 那麽我們通過字典前面的索引, 還是可以快讀定位到要查找的字.

lucene在解析文件的時候, 也是建立了索引的. 和數據庫一樣, 也會生成一個自己的主鍵id, 根據這個id可以非常快的定位到文件.

除了id之外, 還會解析出非聚集索引. 例如在 a.txt , b.txt 中, 都還有一個 字符串 : "索引", 那麽在解析之後, 就會得出這麽個東西:

  "索引" 2次 1,2

這裏是按照次數倒敘排列的, 出現的越多, 越會靠前出現(這裏和百度不同, 百度是你給的錢越多, 越靠前).

最後, 可能還需要理解接個對象:

lucene 解析文件的時候會創建 Document 文件對象(相當於數據庫中的表的概念), 在Document裏面, 有Field 域對象(相當於數據庫中的字段, 只不過域可重名),

Field 對象裏面就存放著 分詞器解析後的結果(Term s). 分詞器解析的結果就是 Term .

如在二分分詞器裏面, "我是中國人" 會被解析成為: "我是"(Term), "是中"(Term), "中國"(Term),"國人"(Term), 然後將這四個Term放在一個Field中.

二. 項目搭建

pom.xml:

這裏使用的lucene版本是4.10.3. 最新版本已經到7.2.0了. 這裏就不介紹最新版了, 大差不差, 有興趣的朋友可以自己去看一下.

    <properties>
        <lucene.version>4.10.3</lucene.version>
    </properties>

    <dependencies> 
        <!-- https://mvnrepository.com/artifact/org.apache.lucene/lucene-core -->
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-core</artifactId>
            <version>${lucene.version}</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.apache.lucene/lucene-queryparser -->
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-queryparser</artifactId>
            <version>${lucene.version}</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.apache.lucene/lucene-analyzers-common -->
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-analyzers-common</artifactId>
            <version>${lucene.version}</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.apache.lucene/lucene-highlighter -->
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-highlighter</artifactId>
            <version>${lucene.version}</version>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>RELEASE</version>
        </dependency>


        <!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>

     <!--ik分詞器--> <!-- https://mvnrepository.com/artifact/com.janeluo/ikanalyzer --> <dependency> <groupId>com.janeluo</groupId> <artifactId>ikanalyzer</artifactId> <version>2012_u6</version> </dependency> </dependencies>

三. 分詞器配置

官方有個推薦的分詞器, Stand開頭的, 那個分詞器是給歪果仁用的, 我們用不了那個.

這裏用的是IK分詞器, 雖然已經不更新了, 但是這個是可擴展的, 對於新的流行詞匯, 加進去之後, 是可以識別出來的. 能滿足使用就行了. 對於別的分詞器, 有好的, 也可以用.

IKAnalyzer.cfg.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">  
<properties>  
    <comment>IK Analyzer 擴展配置</comment>
    <!--用戶可以在這裏配置自己的擴展字典 -->
    <entry key="ext_dict">lucene/ext.dic;</entry>
    
    <!--用戶可以在這裏配置自己的擴展停止詞字典-->
    <entry key="ext_stopwords">lucene/stopword.dic;</entry>
    
</properties>

ext.dic:

要錘得錘
吃瓜群眾
藍瘦香菇

stopword.dic:



是 a an and are as at be but by for if in into is it no not of on or such that the their then there these they this to was will with

四. 新建索引

     /**
     * 索引存放目錄
     */
    private String indexDir = "E:\\Java\\mylucene\\temp\\index";

    /**
     * 待解析文件目錄
     */
    private String fileDir = "E:\\Java\\mylucene\\temp\\files";

     /**
     * 獲取 index 操作對象
     * @return
     * @throws Exception
     */
    private IndexWriter getWriter() throws Exception {
        //1. 創建一個 indexwriter對象
        //1.1 指定索引庫的存放位置 directory 對象
        //1.2 指定一個分析器, 對文檔內容進行分析
        Directory directory = FSDirectory.open(new File(indexDir));
        Analyzer analyzer = new IKAnalyzer(); //ik分詞
        IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_4_10_3, analyzer);
        IndexWriter indexWriter = new IndexWriter(directory, config);
        return indexWriter;
    }

     /**
     * 創建索引
     *
     * @throws IOException
     */
    @Test
    public void createIndex() throws Exception {
        //1. 獲取寫入對象
        IndexWriter indexWriter = getWriter();

        //2. 獲取要解析的文件
        File[] files = new File(fileDir).listFiles();
//3. 遍歷文件, 存儲解析結果 for (File file : files) { //3.1 創建document對象 Document document = new Document(); //文件名稱 String file_name = file.getName(); Field fileNameField = new TextField("fileName", file_name, Field.Store.YES); document.add(fileNameField); //文件大小 long file_size = FileUtils.sizeOf(file); Field fileSzieField = new LongField("fileSize", file_size, Field.Store.YES); document.add(fileSzieField); //文件路徑 String file_path = file.getPath(); Field filePathField = new StoredField("filePath", file_path); document.add(filePathField); //文件內容 String file_content = FileUtils.readFileToString(file, "utf-8"); Field fileContentField = new TextField("fileContent", file_content, Field.Store.YES); document.add(fileContentField); //3.2. 使用indexwriter對象, 將document對象寫入索引庫, 此過程中進行索引創建, 並將索引和document對象寫入索引庫 indexWriter.addDocument(document); } //4. 關閉indexwriter對象 indexWriter.close(); }

這裏我放了兩個文檔, 一個 國歌.txt, 一個軍中綠花.txt.

解析之後, 可以使用 luke 去查看索引. 具體下載地址: https://github.com/DmitryKey/luke/releases/tag/luke-4.10.3

技術分享圖片

一般文件比較多, 所以看這個, 也沒啥太大意義.

五. 索引刪除

刪除一般有兩種, 一種是什麽都不管, 一鍋端. 另一種是根據條件過濾刪除.

     /**
     * 根據條件刪除索引
     *
     * @throws Exception
     */
    @Test
    public void deleteBy() throws Exception {
        IndexWriter writer = getWriter();

        //根據條件精確刪除
        Query query = new TermQuery(new Term("fileName", "花"));
        writer.deleteDocuments(query);

        //解析查詢條件來刪除
        QueryParser queryParser = new QueryParser("fileName", new IKAnalyzer());
        Query query1 = queryParser.parse("花");
        writer.deleteDocuments(query1);

        writer.close();
    }

六. 修改索引

     /**
     * 修改索引
     *
     * @throws Exception
     */
    @Test
    public void updateIndex() throws Exception {
        IndexWriter writer = getWriter();
        Document doc = new Document();
        doc.add(new TextField("fileName", "live", Field.Store.YES));
        doc.add(new TextField("fileContent", "live 生活", Field.Store.YES));
        writer.updateDocument(new Term("fileName", "生活"), doc);
        writer.close();
    }

這裏的修改是刪除再新增的, 其實就是根據 term 刪除之前的document, 然後用新的 doc

lucene 初探