自己动手写分布式搜索引擎
上QQ阅读APP看书,第一时间看更新

3.3.1 查询过程

Lucene中使用IndexSearcher类对索引进行检索。IndexSearcher类继承自抽象类Searcher。查询依赖一个或者多个索引库,IndexSearcher依赖一个或者多个IndexReader。查询执行过程如图3-9所示。

图3-9 查询执行过程

在初始化一个IndexSearcher类时,重要的就是要告诉它使用哪个索引完成查找任务。可以用IndexReader初始化一个IndexSearcher对象。

        Directory directory = FSDirectory.open(new
        File("E:/luceneTest/fileindex"));
        IndexReader indexReader = DirectoryReader.open(directory);
        IndexSearcher indexSearcher = new IndexSearcher(indexReader);

DirectoryReader是IndexReader的子类。使用DirectoryReader.open(directory)方法得到IndexReader的实例。

Directory类型的对象包含索引存放的路径信息,从而可以定位索引。这里使用Directory类型的对象来创建IndexReader,然后再用IndexReader构建IndexSearcher。要查询的索引往往只有一个,所以也可以直接使用Directory类型的对象来构建IndexSearcher。

        String indexDir = "D:/indexdir"; //索引库路径
        Directory directory = FSDirectory.open(new File(indexDir)); //打开一个文件路径
        //只读的方式使用directory,所以read-only=true
        IndexSearcher searcher = new IndexSearcher(directory, true); //隐式创建了
        //IndexReader

上面只是一个隐式创建IndexReader的简单写法。使用Directory构造的IndexSearcher实例仍然各自持有一个IndexReader实例,若系统中存在同一个索引的多个IndexSearcher实例时,会占用过多的内存空间。这时,应该是一份索引用一个IndexReader实例打开,用IndexReader构造IndexSearcher实例。

可以用DirectoryReader.getVersion()取得版本号:

        System.out.println("版本号 :"+reader.getVersion());

DirectoryReader类是IndexReader的子类。

把111.doc分成111,1111.doc分成1111,11111.doc分成11111。这样输入111,就只能搜索到111.doc。一段有意义的文字需要通过Analyzer分割成一个个词语后才能按关键词搜索。Analyzer就是分析器,StandardAnalyzer是Lucene中最常用的分析器。

        Analyzer analyzer = new StandardAnalyzer();

为了达到更好的搜索效果,不同的语言可以使用不同的分析器,例如CnAnalyzer就是一个主要处理中文的分析器。

Analyzer返回的结果就是一串Token。Token包含一个代表词本身含义的字符串和该词在文章中相应的起止偏移位置,Token还包含一个用来存储词类型的字符串。

Term是搜索语法的最小单位,复杂的搜索语法会分解成一个个的Term查询,它表示文档中的一个词语。Term由两部分组成:它表示的词语和这个词语所出现的Field。例如:

        new Term("url", "http://www.lietu.com");

最简单的Query对象是TermQuery。根据TermQuery查询索引库。

        Query query = new TermQuery(new Term("url", "http://www.lietu.com"));
        //搜索索引库并且返回最相关的10个文档
        TopDocs tds = searcher.search(query, 10);

要是用户输入一个逗号,也会搜出文档来,逗号是停用词。一般停用词进索引,但是搜索的时候会过滤掉。指用户输入逗号,系统识别为停用词就给提示说它不能进行搜索。

查询串中可能包括一些高级查询语法,例如,要查找包含java的PDF文件,可以使用查询串“java filetype:pdf”。所以用查询分析器QueryParser来解析查询串,也就是根据查询串生成Query对象。例如:

        Analyzer analyzer = new StandardAnalyzer();
        QueryParser qp = new QueryParser(fields, analyzer);
        query = qp.parse(queryString);

使用IndexSearcher的search()方法执行搜索,返回一个TopDocs对象。TopDocs对象中的totalHits属性记录搜索返回结果的总条数。基本的关键词查询代码如下:

        String indexDir = "D:/indexdir"; //索引库路径
        Directory directory = FSDirectory.open(new File(indexDir)); //打开一个文件路径
        // read-only=true
        IndexSearcher searcher = new IndexSearcher(directory, true); //搜索


        String defaultField = "title"; //默认查询列
        String queryString ="NBA"; //查询词
        Analyzer analyzer = new StandardAnalyzer();
        QueryParser parser = new QueryParser(defaultField,
                                            analyzer);


        Query query = parser.parse(queryString);
        TopDocs docs = searcher.search(query, 10);  //查询最多只返回前10条结果

这里的FSDirectory表示硬盘中的索引,RAMDirectory表示内存中的索引,可以用来测试索引和查找过程。

遍历查询结果:

        ScoreDoc[] hits = docs.scoreDocs; //从TopDocs取得查询结果


        // 遍历结果
        for (ScoreDoc hit : hits){
            Document hitDoc = searcher.doc(hit.doc);
            System.out.println(hitDoc.get("title") +hit.score); //输出标题和文档相关度分值
        }

ScoreDoc中的score属性就是相关度。相关度得分是1~0的值。1表示相关度最高,而0则表示不相关。文档的相关度跟很多因素有关。比如字段的长短、里面词条的权重等。决定score的因素简单概括如下:

(1) 项频率,即查询项在某个文档中出现的次数;

(2) 文档频率,即查询项在很多文档中出现的次数。

完整的查询代码:

        Directory directory = FSDirectory.open(new File("d:/testindex"));
        // DirectoryReader读入一个目录下的索引文件
        IndexReader ir = DirectoryReader.open(directory);
        //打开索引库
        IndexSearcher searcher = new IndexSearcher(ir);


        //根据查询词搜索索引库
        TopDocs docs = searcher.search(new TermQuery(new Term("title", "text")), 10);
        //遍历查询结果
        ScoreDoc[] hits = docs.scoreDocs;
        for (ScoreDoc hit : hits){
            System.out.print("doc:"+hit.doc+" score:"+hit.score+"\n");
        }
        //关闭索引库
        ir.close();