上一篇中我们已经建好了索引,下面该使用索引来做正事了。
这是一段实施基本搜索功能的代码示例:
Directory dir = FSDirectory.open(new File("index")));
IndexSearcher searcher = new IndexSearcher(dir, true);
Query q = new TermQuery(new Term("contents", "java"));
TopDocs hits = searcher.search(q, 10);
searcher.close();
使用索引进行查询的主要步骤:
1、打开已有的索引,创建IndexSearcher对象
2、指定查询用到的Field和查询字符串,创建TermQuery
3、使用IndexSearcher进行查询,查询结果以TopDocs对象返回。在这里search方法的第二个参数指定返回前N个记录。
主要对象说明:
1、Term
Term是查询使用的基本单位,对应与在索引中使用的Field类。可以将其理解为一个map,其中key为索引中Field name,value为查询字符串。
当查询字符串为一个单词的情况下,不会有任何问题;但是当需要查询查询字符串为多个单词或是一句话的时候就会查不出来。这个主要原因是,在建立索引时我们对Field中的内容进行了分词,但在查询时,对查询字符串没有做分词,整个做为一个单词处理,当然查不到了。
要解决这个问题,针对上面的例子,只需要去掉new TermQuery这句,换成下面的代码:
//处理输入的查询字符串
Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_CURRENT);
QueryParser parser = new QueryParser(Version.LUCENE_CURRENT, "java", analyzer);
Query query = parser.parse(queries);
这里需要保证使用的analyzer与建立索引时用的一样即可。new QueryParser的第二个参数就是查询字符串。
我们用query.toString()可以看到转化后的Term内容。
- 当查询字符串="java"时,query.toString()=contents:java (前面为field name,后面为查询内容)
- 当查询字符串="java and system"时,query.toString()=contents:java contents:system。可见已经被做了分词,同时去掉了连接字and
2、TopDocs
此类封装了返回的符合条件的记录,其中:
- totalHits为符合条件的记录总数;
- scoreDocs为符合条件记录的数组,不过里面只记录了Document的ID。Document的实际内容,需通过IndexSearcher取docID对应的Document才能得到。
需要注意-若设置在search方法中设置了返回记录数为N,则scoreDocs最多只会包含前N个文档;但是totalHits会返回匹配的总数量(类似google中显示的匹配的总页面数量)。scoreDocs.length可能不等于totalHits,做scoreDocs遍历时,直接用totalHits做为数组大小用的,容易引起bug!
另外取符合条件Document实际内容的代码如下:
//显示查询结果,字段包括:路径、修改时间
private void printDocs(Searcher searcher, TopDocs docs)
{
try
{
System.out.println("Find " + docs.totalHits + " files!");
ScoreDoc[] sd = docs.scoreDocs;
for (int i = 0; i < sd.length; i++)
{
Document doc = searcher.doc(sd[i].doc);
System.out.println("Path:" + doc.get("path") + "; modified:" + doc.get("modified"));
}
}
catch(Exception ex)
{
System.out.println(ex.getMessage());
}
}