• Lucene第七篇:在你的项目中集成全文检索 原创

  • user image
    • jack
    • 2018-04-09 23:28:38 +0800

        在第四篇中我列举了Lucene搜索索引时常用核心类,并详细说明了其用途。本文不再重复,主要的目的是进行索引的搜索演示,如何在你的项目中集成全文检索,包括全文任意匹配、搜索分页显示和近时搜索的应用。

       虽然Lucene内置很多查询对象,但是在实际的项目开发中应用的不多。一方面因为实现的类太多了,我们有时候却不知所措,到底该用哪个?另一方面用户输入文本关键字词组是无法预知的,你也不能去规范用户输入什么样格式的关键字词,导致查询非常复杂。

        这个时候QueryParser出现了,它制定了一套Query语法,对用户的输入进行词法分析和语法分析,从而生成复杂的基本查询对象组合。实际上这利用编译原理,通过编译原理我们知道,解析一个语法表达式,需要经过词法分析和语法分析的过程,QueryParser的解析语法是通过JavaCC来实现词法分析器和语法分析器的。

         这里就交代完了,实际代码中我使用的是QueryParser的子类MultiFieldQueryParser。代码的验证依赖于第六篇生成的索引,当然你也可以自己生成索引使用进行测试。

  •          Searcher.java:全文任意匹配、搜索分页显示和近时搜索的应用。

  •          Test3.java:验证代码。

 

 Test3.java

 

import java.util.List;

import java.util.Map;

public class Test3 {

    public static void main(String[] args) {

        Searcher searcher = Searcher.instance();

        

        int pageNum = 1;

        String indexDirecory = "d:/lucence/index101";

        String keyword = "lucene 核心类";

        String multipleFieldsText = "title,content";

        String targetFieldsText = "id,title";

        

        Map<String, Object> searchInfo = searcher.search(pageNum, indexDirecory, keyword, multipleFieldsText, targetFieldsText);

        List<Map<String, Object>> searchDatas = (List<Map<String, Object>>) searchInfo.get("search");

        

        for(Map<String, Object> data : searchDatas) {

            System.out.println(data.toString());

        }

        

        System.out.println(searchInfo);

    }

}

 


Searcher.java

 

import java.io.File;

import java.io.IOException;

import java.nio.file.Path;

import java.util.ArrayList;

import java.util.HashMap;

import java.util.List;

import java.util.Map;

import org.apache.lucene.analysis.Analyzer;

import org.apache.lucene.analysis.standard.StandardAnalyzer;

import org.apache.lucene.document.Document;

import org.apache.lucene.index.DirectoryReader;

import org.apache.lucene.index.IndexReader;

import org.apache.lucene.queryparser.classic.MultiFieldQueryParser;

import org.apache.lucene.queryparser.classic.ParseException;

import org.apache.lucene.search.IndexSearcher;

import org.apache.lucene.search.Query;

import org.apache.lucene.search.ScoreDoc;

import org.apache.lucene.search.TopDocs;

import org.apache.lucene.store.FSDirectory;

public class Searcher {

    private static Searcher _searcher = null;

    /** index manager,对同一个索引目录共享indexReader,key为索引目录。 */

    public static Map<String, IndexReader> _indexReaderManager = new HashMap<String, IndexReader>();

    /** 返回最高匹配搜索结果数目,根据自己需要调整 */

    public static int _TOP_N_HITS = 10000;

    /** 最每页返回的搜索结果数目,根据自己需要调整 */

    public static int _PAGE_SIZE = 20;

    /**

     * 私有化默认构造方法,使用单例模式。

     */

    private Searcher() {}

    public static Searcher instance() {

        if (_searcher == null) {

            _searcher = new Searcher();

        }

        return _searcher;

    }

    /**

     * 按关键字词搜索,返回指定的field结果集。

     * 

     * @param pageNum

     *            页号

     * @param indexDirecory

     *            索引文件目录,注意大小写。

     * @param keyword

     *            关键字词,多个关键词以空格分隔,例如:"Lucene 演示"。(不要引号)

     * @param multipleFieldsText

     *            搜索指定的field,以英文逗号分隔,例如:"title,content"。(不要引号)

     * @param targetFieldsText

     *            指定要返回的field,以英文逗号分隔,例如:"id,title"。(不要引号)

     * @return 返回配置搜索结果,格式:{"previous": 上一页页号, "current": 当前页号,"nextp":

     *         上一页页号,"last": 最后页页号,"search": [{"field1": value, "field2":

     *         value}]}。

     */

    public Map<String, Object> search(int pageNum, String indexDirecory, String keyword, String multipleFieldsText,

            String targetFieldsText) {

        Map<String, Object> hitData = null;

        if (isNULL(indexDirecory) || isNULL(keyword) || isNULL(multipleFieldsText) || isNULL(targetFieldsText)) {

            hitData = new HashMap<String, Object>();

            hitData.put("previous", 0);

            hitData.put("current", 0);

            hitData.put("nextp", 0);

            hitData.put("last", 0);

            hitData.put("search", new ArrayList<Map<String, Object>>());

        } else {

            String parseText = parseKeyword(keyword);

            String[] multipleFields = multipleFieldsText.split(",");

            String[] targetFields = targetFieldsText.split(",");

            hitData = searchByPaging(pageNum, indexDirecory, parseText, multipleFields, targetFields);

        }

        return hitData;

    }

    

    private Map<String, Object> searchByPaging(int pageNum, String indexDirecory, String parseText,

            String[] multipleFields, String[] targetFields) {

        Map<String, Object> hitData = new HashMap<String, Object>();

        try {

            IndexSearcher indexSearcher = getIndexSearcher(indexDirecory);

            ScoreDoc[] scoreDoc = searchByMultipleFields(indexDirecory, parseText, multipleFields, indexSearcher);

            if (scoreDoc != null && scoreDoc.length > 0) {

                paging(pageNum, hitData, scoreDoc, indexSearcher, targetFields);

            } else {

                hitData.put("previous", 0);

                hitData.put("current", 0);

                hitData.put("nextp", 0);

                hitData.put("last", 0);

                hitData.put("search", new ArrayList<Map<String, Object>>());

            }

        } catch (Exception e) {

            // TODO: handle exception

            e.printStackTrace();

            hitData.put("previous", 0);

            hitData.put("current", 0);

            hitData.put("nextp", 0);

            hitData.put("last", 0);

            hitData.put("search", new ArrayList<Map<String, Object>>());

        }

        return hitData;

    }

    

    private ScoreDoc[] searchByMultipleFields(String indexDirecory, String parseText, String[] multipleFields,

            IndexSearcher indexSearcher) throws IOException, ParseException {

        Analyzer analyzer = new StandardAnalyzer();

        MultiFieldQueryParser queryParser = new MultiFieldQueryParser(multipleFields, analyzer);

        Query query = queryParser.parse(parseText);

        TopDocs topDocs = indexSearcher.search(query, _TOP_N_HITS);

        return topDocs.scoreDocs;

    }

    /**

     * 移除indexReader

     * 

     * @param indexDirecory

     * @return

     */

    public IndexReader removeIndexReader(String indexDirecory) {

        IndexReader indexReader = _indexReaderManager.remove(indexDirecory);

        return indexReader;

    }

    

    private String parseKeyword(String keyword) {

        keyword = keyword.trim();

        String[] keywords = keyword.split(" ");

        StringBuilder parseText = new StringBuilder();

        for (String text : keywords) {

            parseText.append("\"").append(text).append("\" ");

        }

        keyword = parseText.toString().trim();

        return keyword;

    }

    /**

     * 近时搜索

     * @param indexDirecory

     * @return

     * @throws IOException

     */

    private IndexSearcher getIndexSearcher(String indexDirecory) throws IOException {

        IndexReader indexReader = null;

        if (_indexReaderManager.containsKey(indexDirecory)) {

            indexReader = _indexReaderManager.get(indexDirecory);

            // openIfChanged这个方法类似3.x中的reader.reopen方法,这样就实现了近实时搜索,避免多次新建IndexReader耗费系统资源

            IndexReader newReader = DirectoryReader.openIfChanged((DirectoryReader) indexReader);

            if (newReader != null) {

                // 要记得将原来的 IndexReader 对象关掉

                indexReader.close();

                indexReader = newReader;

            }

        } else {

            File ifDir = new File(indexDirecory);

            Path path = ifDir.toPath();

            FSDirectory fSDirectory = FSDirectory.open(path);

            indexReader = DirectoryReader.open(fSDirectory);

        }

        _indexReaderManager.put(indexDirecory, indexReader);

        IndexSearcher indexSearcher = new IndexSearcher(indexReader);

        return indexSearcher;

    }

    /**

     * 计算分页信息及返回指定页结果集

     * @param pageNum

     * @param hitData

     * @param scoreDoc

     * @param indexSearcher

     * @param targetFields

     * @throws IOException

     */

    private void paging(int pageNum, Map<String, Object> hitData, ScoreDoc[] scoreDoc, IndexSearcher indexSearcher,

            String[] targetFields) throws IOException {

        int maxHitNum = scoreDoc.length;

        Double maxNum = Double.parseDouble(Integer.toString(maxHitNum));

        int last = (int) Math.ceil(maxNum / _PAGE_SIZE);

        pageNum = (pageNum > 0 && pageNum <= last) ? pageNum : 1;

        int previous = pageNum - 1;

        previous = previous >= 1 ? previous : 1;

        int nextp = pageNum + 1;

        nextp = nextp <= last ? nextp : 1;

        hitData.put("previous", previous);

        hitData.put("current", pageNum);

        hitData.put("nextp", nextp);

        hitData.put("last", last);

        int maxIndex = pageNum * _PAGE_SIZE;

        List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();

        for (int i = _PAGE_SIZE; i > 0; i--) {

            int index = maxIndex - i;

            if (index < maxHitNum) {

                ScoreDoc scoreDocData = scoreDoc[maxIndex - i];

                Document document = indexSearcher.doc(scoreDocData.doc);

                Map<String, Object> item = new HashMap<String, Object>();

                for (String targetField : targetFields) {

                    item.put(targetField, document.get(targetField));

                }

                list.add(item);

            } else {

                break;

            }

        }

        hitData.put("search", list);

    }

    private boolean isNULL(String text) {

        return text != null && text.trim().length() > 0 ? false : true;

    }

}

 

       到这里综合第6篇和本文,你能很轻松的在你的项目中集成Lucene的全文搜索功能了。使用QueryParser用户可以傻瓜输入,进行查询得到匹配度高的搜索结果,到这里也够用了。当然了解规范的Query语法,更能精准的定位和缩小搜索结果集的范围,在后面另行介绍Query语法。

  • 查看 0 条评论
登 录 | 注册账号,开始评论。