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 = {"liwei@gmail.com", "zhouguang@163.com", "liaoqunying@yahoo.com.cn", "yuanlian@126.com", "wudi@sina.com", "huzhenyu@qq.com"};
    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<String, Float> scores = new HashMap<String, Float>();

    /**
     * 设置日期类型的数据
     */
    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;i<ids.length;i++) {
                doc = new 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<Integer> 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);
    }
}
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
目录 序言: 1 第一章 LUCENE基础 2 1.1 索引部分的核心类 2 1.2 分词部分的核心类 2 1.3 搜索部分的核心类 2 第二章 索引建立 3 2.1 创建Directory 3 2.2 创建Writer 3 2.3 创建文档并且添加索引 4 2.4 查询索引的基本信息 5 2.5 删除和更新索引 5 (1) 使用writer删除 5 (2) 使用reader删除 5 (3) 恢复删除 5 (4) 彻底删除 6 (5) 更新索引 6 (6) 手动优化 6 2.6 索引文件作用 7 第三章 搜索功能 8 3.1 简单搜索 8 (1) 创建IndexReader 8 (2) 创建IndexSearcher 8 (3) 创建Term和TermQuery 9 (5) 根据TopDocs获取ScoreDoc 9 (6) 根据ScoreDoc获取相应文档 9 3.2 其他搜索 9 (1) 范围查询(TermRangeQuery) 10 (2) 数字查询(NumericRangeQuery) 11 (3) 前缀查询(PrefixQuery) 11 (4) 通配符查询(WildcardQuery) 11 (5) 多条件查询(BooleanQuery) 12 (6) 短语查询(PhraseQuery) 12 (7) 模糊查询(FuzzyQuery) 12 3.3 QueryParser 13 (1) 创建QueryParser 13 (2) 各种匹配方式 13 3.4 分页搜索 14 (1) 普通分页 14 (2) searchAfter分页 15 第四章 分词基础 17 4.1 分词效果 17 (1) 准备分词输出类 17 (2) 创建分词器 18 (3) 英文分词效果 18 (4) 中文分词效果 19 4.2 分词原理 21 (1) TokenStream 21 (2) Tokenizer 22 (3) TokenFilter 23 4.3 分词属性 23 (1) 分词属性查看 24 (2) 分词属性对比 25 4.4 自定义分词器 26 (1) 自定义Stop分词器 26 (2) 实现简单同义词索引 27 第五章 高级搜索 32 5.1 搜索排序 34 (1) 建立搜索类 34 (2) 默认排序 35 (3) 根据评分排序 35 (4) 根据索引号排序 36 (5) 根据文件大小排序 36 (6) 根据日期排序 37 (7) 根据文件名排序(倒序) 37 (8) 多条件排序 38 5.2 搜索过滤 39 (1) 建立搜索类 39 (2) 文本域范围过滤(TermRangeFilter) 40 (3) 数字域范围过滤(NumericRangeFilter) 40 (4) 查询结果过滤(QueryWrapperFilter) 40 5.3 自定义评分 41 (1) 创建一个类继承CustomScoreQuery 41 (2) 创建一个类继承CustomScoreProvider 42 (3) 自定义评分效果测试 43 5.4 自定义QueryParser 44 (1) 限制低性能的QueryParser 44 (2) 扩展基于数字和日期的查询 45 (3) 自定义QueryParser效果测试 46 5.5 自定义过滤器 49 (1) 分析需求,创建接口 49 (2) 创建过滤器,继承Filter 50 (3) 实现接口,效果演示 52 第六章 LUCENE扩展 54 6.1 Luke 54 (1) 启动Luke 54 (2) 索引概述页面 55 (3) 查看索引信息 56 (4) 查询索引信息 57 6.2 Tika 58 (1) Tika的使用 58 (2) Tika的原理 59 6.3 高亮显示 61 (1) 自定义高亮标签 61 (2) 多个域高亮显示 62 6.4 近实时搜索 65 (1) 近实时搜索的创建 66 (2) 近实时搜索的使用 67

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值