Lucene就是這么簡單

什么是Lucene夸政?茅撞?

Lucene是apache軟件基金會發(fā)布的一個開放源代碼的全文檢索引擎工具包沐批,由資深全文檢索專家Doug Cutting所撰寫,它是一個全文檢索引擎的架構(gòu)昧辽,提供了完整的創(chuàng)建索引和查詢索引酵颁,以及部分文本分析的引擎嫉你,Lucene的目的是為軟件開發(fā)人員提供一個簡單易用的工具包,以方便在目標(biāo)系統(tǒng)中實現(xiàn)全文檢索的功能材义,或者是以此為基礎(chǔ)建立起完整的全文檢索引擎均抽,Lucene在全文檢索領(lǐng)域是一個經(jīng)典的祖先,現(xiàn)在很多檢索引擎都是在其基礎(chǔ)上創(chuàng)建的其掂,思想是相通的油挥。

Lucene是根據(jù)關(guān)健字來搜索的文本搜索工具,只能在某個網(wǎng)站內(nèi)部搜索文本內(nèi)容款熬,不能跨網(wǎng)站搜索

既然談到了網(wǎng)站內(nèi)部的搜索深寥,那么我們就談?wù)勎覀兪煜さ陌俣取oogle那些搜索引擎又是基于什么搜索的呢....

這里寫圖片描述
這里寫圖片描述

從圖上已經(jīng)看得很清楚贤牛,baidu惋鹅、google等搜索引擎其實是通過網(wǎng)絡(luò)爬蟲的程序來進(jìn)行搜索的...


為什么我們要用Lucene?

在介紹Lucene的時候殉簸,我們已經(jīng)說了:Lucene又不是搜索引擎闰集,僅僅是在網(wǎng)站內(nèi)部進(jìn)行文本的搜索。那我們?yōu)槭裁匆獙W(xué)他呢般卑?武鲁??

我們之前編寫納稅服務(wù)系統(tǒng)的時候蝠检,其實就已經(jīng)使用過SQL來進(jìn)行站內(nèi)的搜索..

既然SQL能做的功能沐鼠,我們還要學(xué)Lucene,為什么呢叹谁?饲梭??

我們來看看我們用SQL來搜索的話焰檩,有什么缺點:

  • (1)SQL只能針對數(shù)據(jù)庫表搜索憔涉,不能直接針對硬盤上的文本搜索
  • (2)SQL沒有相關(guān)度排名
  • (3)SQL搜索結(jié)果沒有關(guān)健字高亮顯示
  • (4)SQL需要數(shù)據(jù)庫的支持,數(shù)據(jù)庫本身需要內(nèi)存開銷較大析苫,例如:Oracle
  • (5)SQL搜索有時較慢监氢,尤其是數(shù)據(jù)庫不在本地時布蔗,超慢,例如:Oracle
這里寫圖片描述

我們來看看在baidu中搜索Lucene為關(guān)鍵字搜索出的內(nèi)容是怎么樣的:

這里寫圖片描述

以上所說的浪腐,我們?nèi)绻褂肧QL的話纵揍,是做不到的。因此我們就學(xué)習(xí)Lucene來幫我們在站內(nèi)根據(jù)文本關(guān)鍵字來進(jìn)行搜索數(shù)據(jù)议街!


我們?nèi)绻W(wǎng)站需要根據(jù)關(guān)鍵字來進(jìn)行搜索泽谨,可以使用SQL,也可以使用Lucene...那么我們Lucene和SQL是一樣的特漩,都是在持久層中編寫代碼的吧雹。。

這里寫圖片描述

一涂身、快速入門

接下來雄卷,我們就講解怎么使用Lucene了.....在講解Lucene的API之前,我們首先來講講Lucene存放的究竟是什么內(nèi)容...我們的SQL使用的是數(shù)據(jù)庫中的內(nèi)存蛤售,在硬盤中為DBF文件...那么我們Lucene內(nèi)部又是什么東西呢丁鹉??

Lucene中存的就是一系列的二進(jìn)制壓縮文件和一些控制文件悴能,它們位于計算機的硬盤上揣钦,
這些內(nèi)容統(tǒng)稱為索引庫,索引庫有二部份組成:

  • (1)原始記錄
    • 存入到索引庫中的原始文本漠酿,例如:我是鐘福成
  • (2)詞匯表
    • 按照一定的拆分策略(即分詞器)將原始記錄中的每個字符拆開后冯凹,存入一個供將來搜索的表

也就是說:Lucene存放數(shù)據(jù)的地方我們通常稱之為索引庫,索引庫又分為兩部分組成:原始記錄和詞匯表....

1.1原始記錄和詞匯表

當(dāng)我們想要把數(shù)據(jù)存到索引庫的時候炒嘲,我們首先存入的是將數(shù)據(jù)存到原始記錄上面去....

又由于我們給用戶使用的時候宇姚,用戶使用的是關(guān)鍵字來進(jìn)行查詢我們的具體記錄。因此夫凸,我們需要把我們原始存進(jìn)的數(shù)據(jù)進(jìn)行拆分空凸!將拆分出來的數(shù)據(jù)存進(jìn)詞匯表中

詞匯表就是類似于我們在學(xué)Oracle中的索引表寸痢,拆分的時候會給出對應(yīng)的索引值。

一旦用戶根據(jù)關(guān)鍵字來進(jìn)行搜索紊选,那么程序就先去查詢詞匯表中有沒有該關(guān)鍵字啼止,如果有該關(guān)鍵字就定位到原始記錄表中,將符合條件的原始記錄返回給用戶查看兵罢。

我們查看以下的圖方便理解:

這里寫圖片描述

到了這里献烦,有人可能就會疑問:難道原始記錄拆分的數(shù)據(jù)都是一個一個漢字進(jìn)行拆分的嗎?卖词?然后在詞匯表中不就有很多的關(guān)鍵字了巩那?吏夯??

其實即横,我們在存到原始記錄表中的時候噪生,可以指定我們使用哪種算法來將數(shù)據(jù)拆分,存到詞匯表中.....我們的圖是Lucene的標(biāo)準(zhǔn)分詞算法东囚,一個一個漢字進(jìn)行拆分跺嗽。我們可以使用別的分詞算法,兩個兩個拆分或者其他的算法页藻。

1.2編寫第一個Lucene程序

首先桨嫁,我們來導(dǎo)入Lucene的必要開發(fā)包:

  • lucene-core-3.0.2.jar【Lucene核心】
  • lucene-analyzers-3.0.2.jar【分詞器】
  • lucene-highlighter-3.0.2.jar【Lucene會將搜索出來的字,高亮顯示份帐,提示用戶】
  • lucene-memory-3.0.2.jar【索引庫優(yōu)化策略】

創(chuàng)建User對象璃吧,User對象封裝了數(shù)據(jù)....


/**
 * Created by ozc on 2017/7/12.
 */
public class User {

    private String id ;
    private String userName;
    private String sal;

    public User() {

    }
    public User(String id, String userName, String sal) {
        this.id = id;
        this.userName = userName;
        this.sal = sal;
    }
    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getSal() {
        return sal;
    }

    public void setSal(String sal) {
        this.sal = sal;
    }
}

我們想要使用Lucene來查詢出站內(nèi)的數(shù)據(jù),首先我們得要有個索引庫吧废境!于是我們先創(chuàng)建索引庫畜挨,將我們的數(shù)據(jù)存到索引庫中

創(chuàng)建索引庫的步驟:

  • 1)創(chuàng)建JavaBean對象
  • 2)創(chuàng)建Docment對象
  • 3)將JavaBean對象所有的屬性值彬坏,均放到Document對象中去朦促,屬性名可以和JavaBean相同或不同
  • 4)創(chuàng)建IndexWriter對象
  • 5)將Document對象通過IndexWriter對象寫入索引庫中
  • 6)關(guān)閉IndexWriter對象

    @Test
    public void createIndexDB() throws Exception {

        //把數(shù)據(jù)填充到JavaBean對象中
        User user = new User("1", "鐘福成", "未來的程序員");

        //創(chuàng)建Document對象【導(dǎo)入的是Lucene包下的Document對象】
        Document document = new Document();

        //將JavaBean對象所有的屬性值,均放到Document對象中去栓始,屬性名可以和JavaBean相同或不同

        /**
         * 向Document對象加入一個字段
         * 參數(shù)一:字段的關(guān)鍵字
         * 參數(shù)二:字符的值
         * 參數(shù)三:是否要存儲到原始記錄表中
         *      YES表示是
         *      NO表示否
         * 參數(shù)四:是否需要將存儲的數(shù)據(jù)拆分到詞匯表中
         *      ANALYZED表示拆分
         *      NOT_ANALYZED表示不拆分
         *
         * */
        document.add(new Field("id", user.getId(), Field.Store.YES, Field.Index.ANALYZED));
        document.add(new Field("userName", user.getUserName(), Field.Store.YES, Field.Index.ANALYZED));
        document.add(new Field("sal", user.getSal(), Field.Store.YES, Field.Index.ANALYZED));

        //創(chuàng)建IndexWriter對象
        //目錄指定為E:/createIndexDB
        Directory directory = FSDirectory.open(new File("E:/createIndexDB"));

        //使用標(biāo)準(zhǔn)的分詞算法對原始記錄表進(jìn)行拆分
        Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_30);

        //LIMITED默認(rèn)是1W個
        IndexWriter.MaxFieldLength maxFieldLength = IndexWriter.MaxFieldLength.LIMITED;
        /**
         * IndexWriter將我們的document對象寫到硬盤中
         *
         * 參數(shù)一:Directory d,寫到硬盤中的目錄路徑是什么
         * 參數(shù)二:Analyzer a, 以何種算法來對document中的原始記錄表數(shù)據(jù)進(jìn)行拆分成詞匯表
         * 參數(shù)三:MaxFieldLength mfl 最多將文本拆分出多少個詞匯
         *
         * */
        IndexWriter indexWriter = new IndexWriter(directory, analyzer, maxFieldLength);

        //將Document對象通過IndexWriter對象寫入索引庫中
        indexWriter.addDocument(document);

        //關(guān)閉IndexWriter對象
        indexWriter.close();

    }
這里寫圖片描述

程序執(zhí)行完务冕,我們就會在硬盤中見到我們的索引庫。

這里寫圖片描述

那我們現(xiàn)在是不知道記錄是否真真正正存儲到索引庫中的幻赚,因為我們看不見禀忆。索引庫存放的數(shù)據(jù)放在cfs文件下,我們也是不能打開cfs文件的落恼。

于是箩退,我們現(xiàn)在用一個關(guān)鍵字,把索引庫的數(shù)據(jù)讀取佳谦〈骼裕看看讀取數(shù)據(jù)是否成功。

根據(jù)關(guān)鍵字查詢索引庫中的內(nèi)容:

  • 1)創(chuàng)建IndexSearcher對象
  • 2)創(chuàng)建QueryParser對象
  • 3)創(chuàng)建Query對象來封裝關(guān)鍵字
  • 4)用IndexSearcher對象去索引庫中查詢符合條件的前100條記錄钻蔑,不足100條記錄的以實際為準(zhǔn)
  • 5)獲取符合條件的編號
  • 6)用indexSearcher對象去索引庫中查詢編號對應(yīng)的Document對象
  • 7)將Document對象中的所有屬性取出啥刻,再封裝回JavaBean對象中去,并加入到集合中保存咪笑,以備將之用

    @Test
    public void findIndexDB() throws Exception {

        /**
         * 參數(shù)一: IndexSearcher(Directory path)查詢以xxx目錄的索引庫
         *
         * */
        Directory directory = FSDirectory.open(new File("E:/createIndexDB"));
        //創(chuàng)建IndexSearcher對象
        IndexSearcher indexSearcher = new IndexSearcher(directory);

        //創(chuàng)建QueryParser對象
        /**
         * 參數(shù)一: Version matchVersion 版本號【和上面是一樣的】
         * 參數(shù)二:String f,【要查詢的字段】
         * 參數(shù)三:Analyzer a【使用的拆詞算法】
         * */
        Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_30);
        QueryParser queryParser = new QueryParser(Version.LUCENE_30, "userName", analyzer);

        //給出要查詢的關(guān)鍵字
        String keyWords = "鐘";

        //創(chuàng)建Query對象來封裝關(guān)鍵字
        Query query = queryParser.parse(keyWords);

        //用IndexSearcher對象去索引庫中查詢符合條件的前100條記錄可帽,不足100條記錄的以實際為準(zhǔn)
        TopDocs topDocs = indexSearcher.search(query, 100);

        //獲取符合條件的編號

        for (int i = 0; i < topDocs.scoreDocs.length; i++) {

            ScoreDoc scoreDoc = topDocs.scoreDocs[i];
            int no = scoreDoc.doc;
            //用indexSearcher對象去索引庫中查詢編號對應(yīng)的Document對象
            Document document = indexSearcher.doc(no);

            //將Document對象中的所有屬性取出,再封裝回JavaBean對象中去
            String id = document.get("id");
            String userName = document.get("userName");
            String sal = document.get("sal");

            User user = new User(id, userName, sal);
            System.out.println(user);

        }
這里寫圖片描述

效果:

這里寫圖片描述

1.3進(jìn)一步說明Lucene代碼

我們的Lucene程序就是大概這么一個思路:將JavaBean對象封裝到Document對象中窗怒,然后通過IndexWriter把document寫入到索引庫中映跟。當(dāng)用戶需要查詢的時候蓄拣,就使用IndexSearcher從索引庫中讀取數(shù)據(jù),找到對應(yīng)的Document對象努隙,從而解析里邊的內(nèi)容球恤,再封裝到JavaBean對象中讓我們使用

這里寫圖片描述

二剃法、對Lucene代碼優(yōu)化

我們再次看回我們上一篇快速入門寫過的代碼碎捺,我來截取一些有代表性的:

以下代碼在把數(shù)據(jù)填充到索引庫,和從索引庫查詢數(shù)據(jù)的時候贷洲,都出現(xiàn)了收厨。是重復(fù)代碼


        Directory directory = FSDirectory.open(new File("E:/createIndexDB"));

        //使用標(biāo)準(zhǔn)的分詞算法對原始記錄表進(jìn)行拆分
        Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_30);

以下的代碼其實就是將JavaBean的數(shù)據(jù)封裝到Document對象中优构,我們是可以通過反射來對其進(jìn)行封裝....如果不封裝的話诵叁,我們?nèi)绻泻芏郕avaBean都要添加到Document對象中,就會出現(xiàn)很多類似的代碼钦椭。


        document.add(new Field("id", user.getId(), Field.Store.YES, Field.Index.ANALYZED));
        document.add(new Field("userName", user.getUserName(), Field.Store.YES, Field.Index.ANALYZED));
        document.add(new Field("sal", user.getSal(), Field.Store.YES, Field.Index.ANALYZED));

以下代碼就是從Document對象中把數(shù)據(jù)取出來拧额,封裝到JavaBean去。如果JavaBean中有很多屬性彪腔,也是需要我們寫很多次類似代碼....


            //將Document對象中的所有屬性取出侥锦,再封裝回JavaBean對象中去
            String id = document.get("id");
            String userName = document.get("userName");
            String sal = document.get("sal");
            User user = new User(id, userName, sal);

2.1編寫Lucene工具類

在編寫工具類的時候,值得注意的地方:

  • 當(dāng)我們得到了對象的屬性的時候德挣,就可以把屬性的get方法封裝起來
  • 得到get方法恭垦,就可以調(diào)用它,得到對應(yīng)的值
  • 在操作對象的屬性時格嗅,我們要使用暴力訪問
  • 如果有屬性番挺,值,對象這三個變量屯掖,我們記得使用BeanUtils組件

import org.apache.commons.beanutils.BeanUtils;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
import org.junit.Test;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
 * Created by ozc on 2017/7/12.
 */

/**
 * 使用單例事例模式
 * */
public class LuceneUtils {
    private static Directory directory;
    private static Analyzer analyzer;
    private static IndexWriter.MaxFieldLength maxFieldLength;

    private LuceneUtils() {}

    static{
        try {
            directory = FSDirectory.open(new File("E:/createIndexDB"));
            analyzer = new StandardAnalyzer(Version.LUCENE_30);
            maxFieldLength = IndexWriter.MaxFieldLength.LIMITED;
        } catch (Exception e) {
            e.printStackTrace();

        }
    }

    public static Directory getDirectory() {
        return directory;
    }

    public static Analyzer getAnalyzer() {
        return analyzer;
    }

    public static IndexWriter.MaxFieldLength getMaxFieldLength() {
        return maxFieldLength;
    }

    /**
     * @param object 傳入的JavaBean類型
     * @return 返回Document對象
     */
    public static Document javaBean2Document(Object object) {
        try {
            Document document = new Document();
            //得到JavaBean的字節(jié)碼文件對象
            Class<?> aClass = object.getClass();

            //通過字節(jié)碼文件對象得到對應(yīng)的屬性【全部的屬性玄柏,不能僅僅調(diào)用getFields()】
            Field[] fields = aClass.getDeclaredFields();

            //得到每個屬性的名字
            for (Field field : fields) {
                String name = field.getName();
                //得到屬性的值【也就是調(diào)用getter方法獲取對應(yīng)的值】
                String method = "get" + name.substring(0, 1).toUpperCase() + name.substring(1);
                //得到對應(yīng)的值【就是得到具體的方法,然后調(diào)用就行了贴铜。因為是get方法粪摘,沒有參數(shù)】
                Method aClassMethod = aClass.getDeclaredMethod(method, null);
                String value = aClassMethod.invoke(object).toString();
                System.out.println(value);

                //把數(shù)據(jù)封裝到Document對象中。
                document.add(new org.apache.lucene.document.Field(name, value, org.apache.lucene.document.Field.Store.YES, org.apache.lucene.document.Field.Index.ANALYZED));
            }
            return document;
        }  catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * @param aClass   要解析的對象類型绍坝,要用戶傳入進(jìn)來
     * @param document 將Document對象傳入進(jìn)來
     * @return 返回一個JavaBean
     */
    public static Object Document2JavaBean(Document document, Class aClass) {
        try {
            //創(chuàng)建該JavaBean對象
            Object obj = aClass.newInstance();
            //得到該JavaBean所有的成員變量
            Field[] fields = aClass.getDeclaredFields();
            for (Field field : fields) {

                //設(shè)置允許暴力訪問
                field.setAccessible(true);
                String name = field.getName();
                String value = document.get(name);
                //使用BeanUtils把數(shù)據(jù)封裝到Bean中
                BeanUtils.setProperty(obj, name, value);
            }
            return obj;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    @Test
    public void test() {
        User user = new User();
        LuceneUtils.javaBean2Document(user);
    }

}

2.2使用LuceneUtils改造程序


    @Test
    public void createIndexDB() throws Exception {
        //把數(shù)據(jù)填充到JavaBean對象中
        User user = new User("2", "鐘福成2", "未來的程序員2");
        Document document = LuceneUtils.javaBean2Document(user);
        /**
         * IndexWriter將我們的document對象寫到硬盤中
         *
         * 參數(shù)一:Directory d,寫到硬盤中的目錄路徑是什么
         * 參數(shù)二:Analyzer a, 以何種算法來對document中的原始記錄表數(shù)據(jù)進(jìn)行拆分成詞匯表
         * 參數(shù)三:MaxFieldLength mfl 最多將文本拆分出多少個詞匯
         *
         * */
        IndexWriter indexWriter = new IndexWriter(LuceneUtils.getDirectory(), LuceneUtils.getAnalyzer(), LuceneUtils.getMaxFieldLength());

        //將Document對象通過IndexWriter對象寫入索引庫中
        indexWriter.addDocument(document);
        //關(guān)閉IndexWriter對象
        indexWriter.close();
    }

    @Test
    public void findIndexDB() throws Exception {

        //創(chuàng)建IndexSearcher對象
        IndexSearcher indexSearcher = new IndexSearcher(LuceneUtils.getDirectory());
        //創(chuàng)建QueryParser對象
        QueryParser queryParser = new QueryParser(Version.LUCENE_30, "userName", LuceneUtils.getAnalyzer());
        //給出要查詢的關(guān)鍵字
        String keyWords = "鐘";
        //創(chuàng)建Query對象來封裝關(guān)鍵字
        Query query = queryParser.parse(keyWords);
        //用IndexSearcher對象去索引庫中查詢符合條件的前100條記錄徘意,不足100條記錄的以實際為準(zhǔn)
        TopDocs topDocs = indexSearcher.search(query, 100);
        //獲取符合條件的編號
        for (int i = 0; i < topDocs.scoreDocs.length; i++) {
            ScoreDoc scoreDoc = topDocs.scoreDocs[i];
            int no = scoreDoc.doc;
            //用indexSearcher對象去索引庫中查詢編號對應(yīng)的Document對象
            Document document = indexSearcher.doc(no);
            //將Document對象中的所有屬性取出,再封裝回JavaBean對象中去
            User user = (User) LuceneUtils.Document2JavaBean(document, User.class);
            System.out.println(user);

        }
    }


三陷嘴、索引庫優(yōu)化

我們已經(jīng)可以創(chuàng)建索引庫并且從索引庫讀取對象的數(shù)據(jù)了。其實索引庫還有地方可以優(yōu)化的....

3.1合并文件

我們把數(shù)據(jù)添加到索引庫中的時候间坐,每添加一次灾挨,都會幫我們自動創(chuàng)建一個cfs文件...

這里寫圖片描述

這樣其實不好邑退,因為如果數(shù)據(jù)量一大,我們的硬盤就有非常非常多的cfs文件了.....其實索引庫會幫我們自動合并文件的劳澄,默認(rèn)是10個地技。

如果,我們想要修改默認(rèn)的值秒拔,我們可以通過以下的代碼修改:


//索引庫優(yōu)化
indexWriter.optimize();

//設(shè)置合并因子為3莫矗,每當(dāng)有3個cfs文件,就合并
indexWriter.setMergeFactor(3);

3.2設(shè)置內(nèi)存索引庫

我們的目前的程序是直接與文件進(jìn)行操作砂缩,這樣對IO的開銷其實是比較大的作谚。而且速度相對較慢....我們可以使用內(nèi)存索引庫來提高我們的讀寫效率...

對于內(nèi)存索引庫而言,它的速度是很快的庵芭,因為我們直接操作內(nèi)存...但是呢妹懒,我們要將內(nèi)存索引庫是要到硬盤索引庫中保存起來的。當(dāng)我們讀取數(shù)據(jù)的時候双吆,先要把硬盤索引庫的數(shù)據(jù)同步到內(nèi)存索引庫中去的眨唬。

這里寫圖片描述

        Article article = new Article(1,"培訓(xùn)","傳智是一家Java培訓(xùn)機構(gòu)");
        Document document = LuceneUtil.javabean2document(article);

        Directory fsDirectory = FSDirectory.open(new File("E:/indexDBDBDBDBDBDBDBDB"));
        Directory ramDirectory = new RAMDirectory(fsDirectory);

        IndexWriter fsIndexWriter = new IndexWriter(fsDirectory,LuceneUtil.getAnalyzer(),true,LuceneUtil.getMaxFieldLength());
        IndexWriter ramIndexWriter = new IndexWriter(ramDirectory,LuceneUtil.getAnalyzer(),LuceneUtil.getMaxFieldLength());

        ramIndexWriter.addDocument(document);
        ramIndexWriter.close();

        fsIndexWriter.addIndexesNoOptimize(ramDirectory);
        fsIndexWriter.close();

四、分詞器

我們在前面中就已經(jīng)說過了好乐,在把數(shù)據(jù)存到索引庫的時候匾竿,我們會使用某些算法,將原始記錄表的數(shù)據(jù)存到詞匯表中.....那么這些算法總和我們可以稱之為分詞器

分詞器: ** 采用一種算法蔚万,將中英文本中的字符拆分開來岭妖,形成詞匯,以待用戶輸入關(guān)健字后搜索**

對于為什么要使用分詞器笛坦,我們也明確地說過:由于用戶不可能把我們的原始記錄數(shù)據(jù)完完整整地記錄下來区转,于是他們在搜索的時候,是通過關(guān)鍵字進(jìn)行對原始記錄表的查詢....此時版扩,我們就采用分詞器來最大限度地匹配相關(guān)的數(shù)據(jù)

這里寫圖片描述

4.1分詞器流程

  • 步一:按分詞器拆分出詞匯
  • 步二:去除停用詞和禁用詞
  • 步三:如果有英文废离,把英文字母轉(zhuǎn)為小寫,即搜索不分大小寫

4.2分詞器API

我們在選擇分詞算法的時候礁芦,我們會發(fā)現(xiàn)有非常非常多地分詞器API蜻韭,我們可以用以下代碼來看看該分詞器是怎么將數(shù)據(jù)分割的


    private static void testAnalyzer(Analyzer analyzer, String text) throws Exception {
        System.out.println("當(dāng)前使用的分詞器:" + analyzer.getClass());
        TokenStream tokenStream = analyzer.tokenStream("content",new StringReader(text));
        tokenStream.addAttribute(TermAttribute.class);
        while (tokenStream.incrementToken()) {
            TermAttribute termAttribute = tokenStream.getAttribute(TermAttribute.class);
            System.out.println(termAttribute.term());
        }
    }

在實驗完之后,我們就可以選擇恰當(dāng)?shù)姆衷~算法了....

4.3IKAnalyzer分詞器

這是一個第三方的分詞器柿扣,我們?nèi)绻褂玫脑捫枰獙?dǎo)入對應(yīng)的jar包

  • IKAnalyzer3.2.0Stable.jar
  • 步二:將IKAnalyzer.cfg.xml和stopword.dic和xxx.dic文件復(fù)制到MyEclipse的src目錄下肖方,再進(jìn)行配置,在配置時未状,首行需要一個空行

這個第三方的分詞器有什么好呢俯画??司草?艰垂?他是中文首選的分詞器...也就是說:他是按照中文的詞語來進(jìn)行拆分的!


五泡仗、對搜索結(jié)果進(jìn)行處理

5.1搜索結(jié)果高亮

我們在使用SQL時,搜索出來的數(shù)據(jù)是沒有高亮的...而我們使用Lucene猜憎,搜索出來的內(nèi)容我們可以設(shè)置關(guān)鍵字為高亮...這樣一來就更加注重用戶體驗了娩怎!


        String keywords = "鐘福成";
        List<Article> articleList = new ArrayList<Article>();
        QueryParser queryParser = new QueryParser(LuceneUtil.getVersion(),"content",LuceneUtil.getAnalyzer());
        Query query = queryParser.parse(keywords);
        IndexSearcher indexSearcher = new IndexSearcher(LuceneUtil.getDirectory());
        TopDocs topDocs = indexSearcher.search(query,1000000);

        //設(shè)置關(guān)鍵字高亮
        Formatter formatter = new SimpleHTMLFormatter("<font color='red'>","</font>");
        Scorer scorer = new QueryScorer(query);
        Highlighter highlighter = new Highlighter(formatter,scorer);

        for(int i=0;i<topDocs.scoreDocs.length;i++){
            ScoreDoc scoreDoc = topDocs.scoreDocs[i];
            int no = scoreDoc.doc;
            Document document = indexSearcher.doc(no);

            //設(shè)置內(nèi)容高亮
            String highlighterContent = highlighter.getBestFragment(LuceneUtil.getAnalyzer(),"content",document.get("content"));
            document.getField("content").setValue(highlighterContent);

            Article article = (Article) LuceneUtil.document2javabean(document,Article.class);
            articleList.add(article);
        }
        for(Article article : articleList){
            System.out.println(article);
        }
    }

5.2搜索結(jié)果摘要

如果我們搜索出來的文章內(nèi)容太大了,而我們只想顯示部分的內(nèi)容胰柑,那么我們可以對其進(jìn)行摘要...

值得注意的是:搜索結(jié)果摘要需要與設(shè)置高亮一起使用


String keywords = "鐘福成";
        List<Article> articleList = new ArrayList<Article>();
        QueryParser queryParser = new QueryParser(LuceneUtil.getVersion(),"content",LuceneUtil.getAnalyzer());
        Query query = queryParser.parse(keywords);
        IndexSearcher indexSearcher = new IndexSearcher(LuceneUtil.getDirectory());
        TopDocs topDocs = indexSearcher.search(query,1000000);

        Formatter formatter = new SimpleHTMLFormatter("<font color='red'>","</font>");
        Scorer scorer = new QueryScorer(query);
        Highlighter highlighter = new Highlighter(formatter,scorer);

        //設(shè)置摘要
        Fragmenter fragmenter  = new SimpleFragmenter(4);
        highlighter.setTextFragmenter(fragmenter);

        for(int i=0;i<topDocs.scoreDocs.length;i++){
            ScoreDoc scoreDoc = topDocs.scoreDocs[i];
            int no = scoreDoc.doc;
            Document document = indexSearcher.doc(no);

            String highlighterContent = highlighter.getBestFragment(LuceneUtil.getAnalyzer(),"content",document.get("content"));
            document.getField("content").setValue(highlighterContent);

            Article article = (Article) LuceneUtil.document2javabean(document,Article.class);
            articleList.add(article);
        }
        for(Article article : articleList){
            System.out.println(article);
        }
    }

5.3搜索結(jié)果排序

我們搜索引擎肯定用得也不少截亦,使用不同的搜索引擎來搜索相同的內(nèi)容。他們首頁的排行順序也會不同...這就是它們內(nèi)部用了搜索結(jié)果排序....

影響網(wǎng)頁的排序有非常多種:

  • head/meta/【keywords關(guān)鍵字】
  • 網(wǎng)頁的標(biāo)簽整潔
  • 網(wǎng)頁執(zhí)行速度
  • 采用div+css
  • 等等等等

而在Lucene中我們就可以設(shè)置相關(guān)度得分來使不同的結(jié)果對其進(jìn)行排序:


        IndexWriter indexWriter = new IndexWriter(LuceneUtil.getDirectory(),LuceneUtil.getAnalyzer(),LuceneUtil.getMaxFieldLength());
        //為結(jié)果設(shè)置得分
        document.setBoost(20F);
        indexWriter.addDocument(document);
        indexWriter.close();

當(dāng)然了柬讨,我們也可以按單個字段排序:

    //true表示降序
    Sort sort = new Sort(new SortField("id",SortField.INT,true));
    TopDocs topDocs = indexSearcher.search(query,null,1000000,sort);

也可以按多個字段排序:在多字段排序中崩瓤,只有第一個字段排序結(jié)果相同時,第二個字段排序才有作用 提倡用數(shù)值型排序


        Sort sort = new Sort(new SortField("count",SortField.INT,true),new SortField("id",SortField.INT,true));
        TopDocs topDocs = indexSearcher.search(query,null,1000000,sort);

5.4條件搜索

在我們的例子中姐浮,我們使用的是根據(jù)一個關(guān)鍵字來對某個字段的內(nèi)容進(jìn)行搜索谷遂。語法類似于下面:

    QueryParser queryParser = new QueryParser(LuceneUtil.getVersion(),"content",LuceneUtil.getAnalyzer());

其實,我們也可以使用關(guān)鍵字來對多個字段進(jìn)行搜索卖鲤,也就是多條件搜索肾扰。我們實際中常常用到的是多條件搜索,多條件搜索可以使用我們最大限度匹配對應(yīng)的數(shù)據(jù)蛋逾!


QueryParser queryParser = new MultiFieldQueryParser(LuceneUtil.getVersion(),new String[]{"content","title"},LuceneUtil.getAnalyzer());

六集晚、總結(jié)

  • Lucene是全文索引引擎的祖先,后面的Solr区匣、Elasticsearch都是基于Lucene的(后面會有一篇講Elasticsearch的偷拔,敬請期待~)
  • Lucene中存的就是一系列的二進(jìn)制壓縮文件和一些控制文件,這些內(nèi)容統(tǒng)稱為索引庫,索引庫又分了兩個部分:
    • 原始記錄
    • 詞匯表
  • 了解索引庫的優(yōu)化方式:1、合并文件 2亏钩、設(shè)置內(nèi)存索引庫
  • Lucene的分詞器有非常多種莲绰,選擇自己適合的一種進(jìn)行分詞
  • 查詢出來的結(jié)果可對其設(shè)置高亮、摘要姑丑、排序

這篇這是Lucene的冰山一角蛤签,一般現(xiàn)在用的可能都是Solr、Elasticsearch的了栅哀,但想要更加深入了解Lucene可翻閱其他資料哦~

如果文章有錯的地方歡迎指正涉茧,大家互相交流朽们。習(xí)慣在微信看技術(shù)文章没卸,想要獲取更多的Java資源的同學(xué)泞辐,可以關(guān)注微信公眾號:Java3y

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市痴柔,隨后出現(xiàn)的幾起案子沦偎,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件豪嚎,死亡現(xiàn)場離奇詭異鸿捧,居然都是意外死亡,警方通過查閱死者的電腦和手機疙渣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來堆巧,“玉大人妄荔,你說我怎么就攤上這事〉簦” “怎么了啦租?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長荒揣。 經(jīng)常有香客問我篷角,道長,這世上最難降的妖魔是什么系任? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任恳蹲,我火速辦了婚禮,結(jié)果婚禮上俩滥,老公的妹妹穿的比我還像新娘嘉蕾。我一直安慰自己,他們只是感情好霜旧,可當(dāng)我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布错忱。 她就那樣靜靜地躺著,像睡著了一般挂据。 火紅的嫁衣襯著肌膚如雪以清。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天崎逃,我揣著相機與錄音掷倔,去河邊找鬼。 笑死婚脱,一個胖子當(dāng)著我的面吹牛今魔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播障贸,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼错森,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了篮洁?” 一聲冷哼從身側(cè)響起涩维,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后瓦阐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蜗侈,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年睡蟋,在試婚紗的時候發(fā)現(xiàn)自己被綠了踏幻。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡戳杀,死狀恐怖该面,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情信卡,我是刑警寧澤隔缀,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站傍菇,受9級特大地震影響猾瘸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜丢习,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一牵触、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧咐低,春花似錦荒吏、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至锡宋,卻和暖如春儡湾,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背执俩。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工徐钠, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人役首。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓尝丐,卻偏偏與公主長得像,于是被迫代替她去往敵國和親衡奥。 傳聞我的和親對象是個殘疾皇子爹袁,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,864評論 2 354

推薦閱讀更多精彩內(nèi)容