Lucene5 学习笔记(3) —— 重用 IndexReader 和常用的搜索方法

优化 IndexReader 的使用

下面的一个模式是我们经常使用的。
相对于索引的创建而言,索引的搜索是使用频繁的。所以 IndexReader 是会经常使用的,所以我们很自然地想到应该将 IndexReader 设计成一个单例模式。但是索引增加、修改、删除以后,IndexReader 须要重新读取索引信息,才能保证我们的索引信息是准确的,那有没有办法不用重新打开索引,就能保证我们的 IndexReader 是读取最新的索引呢?

有的 , 使用 DirectoryReader 类的静态方法 openIfChanged 就可以达到目的,这个判断会先判断索引是否变更,如果变更,我们要先把原来的 IndexReader 释放。下面的例子展示了 IndexReader 的使用过程。

/**
 * 重用一些旧的 IndexReader
 * @return
 */
public IndexSearcher getSearcher() {
    try {
        if(reader==null) {
            reader = DirectoryReader.open(directory);
        } else {
            // 如果 IndexReader 不为空,就使用 DirectoryReader 打开一个索引变更过的 IndexReader 类
            // 此时要记得把旧的索引对象关闭
            // 参考资料:Lucene系列-近实时搜索(1)
            // http://blog.csdn.net/whuqin/article/details/42922813
            IndexReader tr = DirectoryReader.openIfChanged((DirectoryReader)reader);
            if(tr!=null) {
                reader.close();
                reader = tr;
            }
        }
        return new IndexSearcher(reader);
    } catch (CorruptIndexException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return null;
}

常用的搜索方法

下面归纳了一些常用的搜索方法,最后给出了整个搜索的测试例子,供大家参考。

1、TermQuery 搜索特定的项(上一节已经介绍过)

// 搜索特定的项
Query query = new TermQuery(new Term(field,value));

2、TermRangeQuery 搜索特定范围的项
这个 Query 不适用于数字范围查询,数字范围查询请使用 NumericRangeQuery 代替

Query query = new TermRangeQuery(field,new BytesRef(start.getBytes()),new BytesRef(end.getBytes()),true,true);

3、NumericRangeQuery 搜索数字范围的项

NumericRangeQuery<Integer> query = NumericRangeQuery.newIntRange(field,start,end,true,true);

4、PrefixQuery 前缀匹配搜索

Query query = new PrefixQuery(new Term(field,value));

5、WildcardQuery 通配符搜索

Query query = new WildcardQuery(new Term(field,value));

6、FuzzyQuery 模糊匹配搜索
模糊匹配的意思是:搜索的关键字即使有错,在一定范围内都可以被搜索到

FuzzyQuery query = new FuzzyQuery(new Term(field,value),maxEdits,prefixLength);

7、BooleanQuery 多个条件的查询

BooleanQuery.Builder booleanQuery = new BooleanQuery.Builder();
Query query1 = new TermQuery(new Term(field1,value1));
Query query2 = new TermQuery(new Term(field2,value2));
booleanQuery.add(query1,BooleanClause.Occur.MUST);
booleanQuery.add(query2,BooleanClause.Occur.MUST);

8、PhraseQuery 短语查询

PhraseQuery phraseQuery = new PhraseQuery();
phraseQuery.setSlop(slop);
phraseQuery.add(new Term(field,value1));
phraseQuery.add(new Term(field,value2));

9、QueryParser 方式的查询
功能最最强大,几乎涵盖上上面几种方式的查询。

Analyzer analyzer = new SimpleAnalyzer();
// QueryParser 构造器的第 1 个参数表示默认的搜索域
// 实例化 QueryParser 的时候,需要指定一个分词器(构造函数的第 2 个参数)
// 【重要】这个分析器不一定要和索引的时候使用的分析器相同
QueryParser queryParser = new QueryParser(filedName,analyzer);
// 开启第一个字符的通配符匹配,默认关闭因为效率不高
// queryParser.setAllowLeadingWildcard(true);
// 改变空格的默认操作符,以下可以改成AND
// parser.setDefaultOperator(Operator.AND);
Query query = queryParser.parse(el);

QueryParser 构造函数的第 1 个字段表示默认的搜索域。
其中 el 表示查询表达式,查询表达式的内容非常丰富,我们要通过查询表达式来完成复杂的查询工作。
例如:
(1)“- name:mike + like” 表示 “匹配 name 中没有 mike 但是 content 中必须有 like 的, + 和 - 要放置到域说明前面”;
(2) “\”I like football\”” 表示完全匹配 I like football 。

关于查询表达式更详细的内容,可以参考《Lucene 实战》。

下面展示出了一整个搜索工具类供大家参考如何使用:

public class SearcherUtil {

    private String[] ids = {"1", "2", "3", "4", "5", "6"};
    private String[] names = {"liwei", "zhouguang", "liaoqunying", "yuanlian", "wudi", "huzhenyu"};
    private String[] emails = {"[email protected]", "[email protected]", "[email protected]", "[email protected]", "[email protected]", "[email protected]"};
    private String[] contents = {
            "I enjoy a folk song",
            "I come from Shanghai jiaotong university",
            "I am a university professor",
            "I am very cool",
            "I like football and I like basketball too",
            "I am a operations engineer"
    };
    // 用于测试创建日期数据索引
    private Date[] dates = null;
    // 用于测试创建数字索引
    private int[] attachs = {4, 0, 17, 4, 7, 3};


    private Directory directory;

    private IndexReader indexReader;

    private String indexDir = "C:\\dev\\lucene";


    private Map scores = new HashMap();

    /**
     * 设置日期类型的数据
     */
    private void setDates() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        try {
            dates = new Date[ids.length];
            dates[0] = sdf.parse("1987-07-06");
            dates[1] = sdf.parse("1990-03-20");
            dates[2] = sdf.parse("1989-01-06");
            dates[3] = sdf.parse("1993-03-17");
            dates[4] = sdf.parse("1974-07-27");
            dates[5] = sdf.parse("1987-05-07");
        } catch (ParseException e) {
            e.printStackTrace();
        }
    }

    public SearcherUtil(){
        setDates();

        scores.put("163.com",2.0f);
        scores.put("qq.com", 1.5f);

        try {

            directory = FSDirectory.open(Paths.get(indexDir));
        } catch (IOException e) {
            e.printStackTrace();
        }

    }


    private IndexWriter getIndexWriter(){
        IndexWriter indexWriter = null;
        Analyzer analyzer = new SimpleAnalyzer();
        IndexWriterConfig iwc = new IndexWriterConfig(analyzer);
        try {
            indexWriter= new IndexWriter(directory,iwc);
        }catch (IOException e){
            e.printStackTrace();
        }
        return indexWriter;
    }


    /**
     * 创建索引
     */
    public void index() {
        IndexWriter writer = null;
        try {
            writer = getIndexWriter();
            writer.deleteAll();
            Document doc = null;
            for(int i=0;inew Document();
                doc.add(new StringField("id",ids[i], Field.Store.YES));
                StringField emailField = new StringField("email",emails[i],Field.Store.YES);

                String et = emails[i].substring(emails[i].lastIndexOf("@")+1);
                // System.out.println("email 的后缀 => " + et);
                // 目前还不清楚如何使用加权
                /*if(scores.containsKey(et)) {
                    emailField.setBoost(scores.get(et));
                } else {
                    emailField.setBoost(1.0f);
                }*/

                doc.add(emailField);
                doc.add(new TextField("content",contents[i],Field.Store.NO));
                doc.add(new StringField("name",names[i],Field.Store.YES));

                // 参考资料:一步一步跟我学习lucene(2)---lucene的各种Field及其排序
                // http://blog.csdn.net/wuyinggui10000/article/details/45538155
                //存储数字的 Field
                doc.add(new IntField("attach",attachs[i], Field.Store.YES));
                //存储日期的 Field
                doc.add(new LongField("date",dates[i].getTime(), Field.Store.YES));
                writer.addDocument(doc);
            }
        } catch (CorruptIndexException e) {
            e.printStackTrace();
        } catch (LockObtainFailedException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(writer!=null)writer.close();
            } catch (CorruptIndexException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * 获得 IndexSearcher
     * 因为操作 IndexReader 是一种消耗比较大的操作,因此我们要将 IndexReader 设计成单例
     * 但是我们又不希望索引的更改导致我们要重新读取索引
     * 这是一种标准的写法,要记录下来
     * @return
     */
    public IndexSearcher getIndexSearcher(){
        try {
            if(indexReader==null){
                indexReader = DirectoryReader.open(directory);
            }else {
                IndexReader newReader = DirectoryReader.openIfChanged((DirectoryReader) indexReader);
                if(newReader!=null){
                    // 要记得将原来的 IndexReader 对象关掉
                    indexReader.close();
                    indexReader = newReader;
                }
            }
            IndexSearcher indexSearcher = new IndexSearcher(indexReader);
            return indexSearcher;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 与上面的方法重载,传入一个 Directory 对象
     * @param directory
     * @return
     */
    public IndexSearcher getIndexSearcher(Directory directory){
        try {
            if(indexReader == null){
                indexReader = DirectoryReader.open(directory);
            }else {
                IndexReader newReader = DirectoryReader.openIfChanged((DirectoryReader)indexReader);
                indexReader.close();
                indexReader = newReader;
            }
            return new IndexSearcher(indexReader);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }


    public void searchByTerm(String field,String value,int num){
        // 首先获得 IndexSearcher
        IndexSearcher searcher = getIndexSearcher();
        // 搜索特定的项
        Query query = new TermQuery(new Term(field,value));
        try {
            TopDocs topDocs = searcher.search(query,num);
            System.out.println("实际搜索到的记录数 => " + topDocs.totalHits);
            Document document = null;
            for(ScoreDoc scoreDoc:topDocs.scoreDocs){
                document = searcher.doc(scoreDoc.doc);
                String result = "name => " + document.get("name") + "\t email => "+ document.get("email") +
                        "\t id => " + document.get("id") + "\t attach => " + document.get("attach") + "\t date => " + document.get("date");
                System.out.println(result);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            // IndexSearcher 都不用关闭了
            IndexReader reader = searcher.getIndexReader();
            System.out.println("测试 reader 是否一样 => " +  (reader == indexReader) );
        }
    }


    /**
     *
     * @param field
     * @param start
     * @param end
     * @param num
     */
    public void searchByTermRange(String field,String start,String end,int num){
        IndexSearcher searcher = getIndexSearcher();
        /**
         * 这个 Query 不适用于数字范围查询,数字范围查询请使用 NumericRangeQuery 代替
         */
        Query query = new TermRangeQuery(field,new BytesRef(start.getBytes()),new BytesRef(end.getBytes()),true,true);
        showQueryResult(searcher,query,num);
    }

    /**
     *
     * @param field
     * @param start
     * @param end
     * @param num
     */
    public void searchByNumericRangeQuery(String field,Integer start,Integer end,int num){
        IndexSearcher searcher = getIndexSearcher();
        NumericRangeQuery query = NumericRangeQuery.newIntRange(field,start,end,true,true);
        showQueryResult(searcher,query,num);

    }

    /**
     *
     * @param searcher
     * @param query
     * @param num
     */
    private void showQueryResult(IndexSearcher searcher,Query query,Integer num){
        TopDocs topDocs = null;
        try {
            topDocs = searcher.search(query,num);
            System.out.println("实际搜索到的记录数 => " + topDocs.totalHits);
            Document document = null;
            for(ScoreDoc scoreDoc:topDocs.scoreDocs){
                document = searcher.doc(scoreDoc.doc);
                String result = "name => " + document.get("name") + "\t email => "+ document.get("email") +
                        "\t id => " + document.get("id") + "\t attach => " + document.get("attach") + "\t date => " + document.get("date");
                System.out.println(result);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 前缀匹配查询
     * @param field
     * @param value
     * @param num
     */
    public void searchByPrefix(String field,String value,int num){
        IndexSearcher searcher = getIndexSearcher();
        Query query = new PrefixQuery(new Term(field,value));
        showQueryResult(searcher,query,num);
    }

    /**
     * 通配符查询
     * 通配符: * 表示匹配任意多个字符,? 表示匹配一个字符
     * @param field
     * @param value
     * @param num
     */
    public void searchByWildcard(String field,String value,int num){
        IndexSearcher searcher = getIndexSearcher();
        Query query = new WildcardQuery(new Term(field,value));
        showQueryResult(searcher,query,num);
    }

    /**
     * 多个条件的查询
     * MUST 表示必须要有,即“且,交集”
     * SHOULD 表示可以有,也可以没有,即“或者,并集”
     * @param field1
     * @param value1
     * @param field2
     * @param value2
     * @param num
     */
    public void searchByBoolean(String field1,String value1,String field2,String value2, int num){
        IndexSearcher searcher = getIndexSearcher();
        BooleanQuery.Builder booleanQuery = new BooleanQuery.Builder();
        Query query1 = new TermQuery(new Term(field1,value1));
        Query query2 = new TermQuery(new Term(field2,value2));
        booleanQuery.add(query1,BooleanClause.Occur.MUST);
        booleanQuery.add(query2,BooleanClause.Occur.MUST);
        showQueryResult(searcher,booleanQuery.build(),num);
    }


    /**
     *
     * slop 表示一个半径,正着走,反着走都是可以查询到的
     * 但是要主要搜索的关键字必须是小写
     *
     * 短语查询,仅仅针对英文有效,中文并不支持
     * @param field
     * @param value1
     * @param value2
     * @param num
     */
    public void searchByPrase(String field,String value1,String value2,int slop,int num){
        IndexSearcher searcher = getIndexSearcher();
        PhraseQuery phraseQuery = new PhraseQuery();
        phraseQuery.setSlop(slop);
        phraseQuery.add(new Term(field,value1));
        //第一个Term
        phraseQuery.add(new Term(field,value2));
        showQueryResult(searcher,phraseQuery,num);
    }


    /**
     * 模糊查询
     * @param field
     * @param value
     * @param num
     */
    public void searchByFuzzy(String field,String value,int maxEdits, int prefixLength,int num){
        IndexSearcher searcher = getIndexSearcher();
        FuzzyQuery query = new FuzzyQuery(new Term(field,value),maxEdits,prefixLength);
        showQueryResult(searcher,query,num);
    }


    /**
     * 根据一个字符串,实现了上述各种特殊的查询功能
     * 那就要通过 QueryParser 来完成
     * @param query
     * @param num
     */
    public void searchByQueryParser(Query query,int num){
        IndexSearcher searcher = getIndexSearcher();
        showQueryResult(searcher,query,num);
    }
}

你可能感兴趣的:(lucene)