Lucene第二篇【抽取工具類、索引庫優化、分詞器、高亮、摘要、排序、多條件搜索】

對Lucene代碼優化

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

如下代碼在把數據填充到索引庫,和從索引庫查詢數據的時候,都出現了。是重複代碼css

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

        //使用標準的分詞算法對原始記錄表進行拆分
        Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_30);

如下的代碼其實就是將JavaBean的數據封裝到Document對象中,咱們是能夠經過反射來對其進行封裝….若是不封裝的話,咱們若是有不少JavaBean都要添加到Document對象中,就會出現不少相似的代碼。java

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對象中把數據取出來,封裝到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);

編寫Lucene工具類

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

  • 當咱們獲得了對象的屬性的時候,就能夠把屬性的get方法封裝起來
  • 獲得get方法,就能夠調用它,獲得對應的值
  • 在操做對象的屬性時,咱們要使用暴力訪問
  • 若是有屬性,值,對象這三個變量,咱們記得使用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的字節碼文件對象
            Class<?> aClass = object.getClass();

            //經過字節碼文件對象獲得對應的屬性【所有的屬性,不能僅僅調用getFields()】
            Field[] fields = aClass.getDeclaredFields();

            //獲得每一個屬性的名字
            for (Field field : fields) {
                String name = field.getName();
                //獲得屬性的值【也就是調用getter方法獲取對應的值】
                String method = "get" + name.substring(0, 1).toUpperCase() + name.substring(1);
                //獲得對應的值【就是獲得具體的方法,而後調用就好了。由於是get方法,沒有參數】
                Method aClassMethod = aClass.getDeclaredMethod(method, null);
                String value = aClassMethod.invoke(object).toString();
                System.out.println(value);


                //把數據封裝到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 要解析的對象類型,要用戶傳入進來 * @param document 將Document對象傳入進來 * @return 返回一個JavaBean */
    public static Object Document2JavaBean(Document document, Class aClass) {
        try {
            //建立該JavaBean對象
            Object obj = aClass.newInstance();
            //獲得該JavaBean全部的成員變量
            Field[] fields = aClass.getDeclaredFields();
            for (Field field : fields) {

                //設置容許暴力訪問
                field.setAccessible(true);
                String name = field.getName();
                String value = document.get(name);
                //使用BeanUtils把數據封裝到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);
    }

}

使用LuceneUtils改造程序

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

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


    @Test
    public void findIndexDB() throws Exception {


        //建立IndexSearcher對象
        IndexSearcher indexSearcher = new IndexSearcher(LuceneUtils.getDirectory());
        //建立QueryParser對象
        QueryParser queryParser = new QueryParser(Version.LUCENE_30, "userName", LuceneUtils.getAnalyzer());
        //給出要查詢的關鍵字
        String keyWords = "鍾";
        //建立Query對象來封裝關鍵字
        Query query = queryParser.parse(keyWords);
        //用IndexSearcher對象去索引庫中查詢符合條件的前100條記錄,不足100條記錄的以實際爲準
        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對象去索引庫中查詢編號對應的Document對象
            Document document = indexSearcher.doc(no);
            //將Document對象中的全部屬性取出,再封裝回JavaBean對象中去
            User user = (User) LuceneUtils.Document2JavaBean(document, User.class);
            System.out.println(user);

        }
    }

索引庫優化

咱們已經能夠建立索引庫而且從索引庫讀取對象的數據了。其實索引庫還有地方能夠優化的….apache

合併文件

咱們把數據添加到索引庫中的時候,每添加一次,都會幫咱們自動建立一個cfs文件markdown

這裏寫圖片描述

這樣其實很差,由於若是數據量一大,咱們的硬盤就有很是很是多的cfs文件了…..其實索引庫會幫咱們自動合併文件的,默認是10個工具

若是,咱們想要修改默認的值,咱們能夠經過如下的代碼修改:優化

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

//設置合併因子爲3,每當有3個cfs文件,就合併
indexWriter.setMergeFactor(3);

設置內存索引庫

咱們的目前的程序是直接與文件進行操做,這樣對IO的開銷實際上是比較大的。並且速度相對較慢….咱們可使用內存索引庫來提升咱們的讀寫效率…搜索引擎

對於內存索引庫而言,它的速度是很快的,由於咱們直接操做內存…可是呢,咱們要將內存索引庫是要到硬盤索引庫中保存起來的。當咱們讀取數據的時候,先要把硬盤索引庫的數據同步到內存索引庫中去的。

這裏寫圖片描述

Article article = new Article(1,"培訓","傳智是一家Java培訓機構");
        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();

分詞器

咱們在Lucene第一篇中就已經說過了,在把數據存到索引庫的時候,咱們會使用某些算法,將原始記錄表的數據存到詞彙表中…..那麼這些算法總和咱們能夠稱之爲分詞器

分詞器: * 採用一種算法,將中英文本中的字符拆分開來,造成詞彙,以待用戶輸入關健字後搜索*

對於爲何要使用分詞器,咱們也明確地說過:因爲用戶不可能把咱們的原始記錄數據完完整整地記錄下來,因而他們在搜索的時候,是經過關鍵字進行對原始記錄表的查詢….此時,咱們就採用分詞器來最大限度地匹配相關的數據

這裏寫圖片描述

分詞器流程

  • 步一:按分詞器拆分出詞彙
  • 步二:去除停用詞和禁用詞
  • 步三:若是有英文,把英文字母轉爲小寫,即搜索不分大小寫

分詞器API

咱們在選擇分詞算法的時候,咱們會發現有很是很是多地分詞器API,咱們能夠用如下代碼來看看該分詞器是怎麼將數據分割的

private static void testAnalyzer(Analyzer analyzer, String text) throws Exception {
        System.out.println("當前使用的分詞器:" + 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());
        }
    }

在實驗完以後,咱們就能夠選擇恰當的分詞算法了….

IKAnalyzer分詞器

這是一個第三方的分詞器,咱們若是要使用的話須要導入對應的jar包

  • IKAnalyzer3.2.0Stable.jar
  • 步二:將IKAnalyzer.cfg.xml和stopword.dic和xxx.dic文件複製到MyEclipse的src目錄下,再進行配置,在配置時,首行須要一個空行

這個第三方的分詞器有什麼好呢????他是中文首選的分詞器…也就是說:他是按照中文的詞語來進行拆分的!


搜索結果高亮

咱們在使用SQL時,搜索出來的數據是沒有高亮的…而咱們使用Lucene,搜索出來的內容咱們能夠設置關鍵字爲高亮…這樣一來就更加註重用戶體驗了!

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);

        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);
        }
    }

搜索結果摘要

若是咱們搜索出來的文章內容太大了,而咱們只想顯示部分的內容,那麼咱們能夠對其進行摘要…

值得注意的是:搜索結果摘要須要與設置高亮一塊兒使用

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);

        //設置摘要
        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);
        }
    }

搜索結果排序

咱們搜索引擎確定用得也很多,使用不一樣的搜索引擎來搜索相同的內容。他們首頁的排行順序也會不一樣…這就是它們內部用了搜索結果排序….

影響網頁的排序有很是多種:

  • head/meta/【keywords關鍵字】
  • 網頁的標籤整潔
  • 網頁執行速度
  • 採用div+css
  • 等等等等

而在Lucene中咱們就能夠設置相關度得分來使不一樣的結果對其進行排序:

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

固然了,咱們也能夠按單個字段排序:

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

也能夠按多個字段排序:在多字段排序中,只有第一個字段排序結果相同時,第二個字段排序纔有做用 提倡用數值型排序

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

條件搜索

在咱們的例子中,咱們使用的是根據一個關鍵字來對某個字段的內容進行搜索。語法相似於下面:

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

其實,咱們也可使用關鍵字來對多個字段進行搜索,也就是多條件搜索。咱們實際中經常用到的是多條件搜索,多條件搜索可使用咱們最大限度匹配對應的數據

QueryParser queryParser = new MultiFieldQueryParser(LuceneUtil.getVersion(),new String[]{"content","title"},LuceneUtil.getAnalyzer());
相關文章
相關標籤/搜索