3.2节我们已经运行了一个Lucene实现检索的小程序,这一节我们将以这个小程序为例,讲一下Lucene检索的基本步骤,同时介绍关键词高亮显示和分页返回结果这两个有用的技巧。
一、Lucene检索的基本步骤
1 import java.nio.file.Paths; 2 import java.io.*; 3 4 import org.apache.lucene.analysis.standard.StandardAnalyzer; 5 import org.apache.lucene.document.Document; 6 import org.apache.lucene.index.DirectoryReader; 7 import org.apache.lucene.queryparser.classic.QueryParser; 8 import org.apache.lucene.search.IndexSearcher; 9 import org.apache.lucene.search.Query; 10 import org.apache.lucene.search.ScoreDoc; 11 import org.apache.lucene.search.TopDocs; 12 import org.apache.lucene.store.Directory; 13 import org.apache.lucene.store.FSDirectory; 14 import org.apache.lucene.util.Version; 15 16 /** 17 * @author csl 18 * @description: 19 * 依赖jar:Lucene-core,lucene-analyzers-common,lucene-queryparser 20 * 作用:使用索引搜索文件 21 */ 22 public class Searcher { 23 public static Version luceneVersion = Version.LATEST; 24 /** 25 * 查询内容 26 */ 27 public static String indexSearch(String keywords){ 28 String res = ""; 29 DirectoryReader reader = null; 30 try{ 31 // 1、创建Directory 32 Directory directory = FSDirectory.open(Paths.get("index"));//在硬盘上生成Directory 33 // 2、创建IndexReader 34 reader = DirectoryReader.open(directory); 35 // 3、根据IndexReader创建IndexSearcher 36 IndexSearcher searcher = new IndexSearcher(reader); 37 // 4、创建搜索的query 38 // 创建parse用来确定搜索的内容,第二个参数表示搜索的域 39 QueryParser parser = new QueryParser("content",new StandardAnalyzer());//content表示搜索的域或者说字段 40 Query query = parser.parse(keywords);//被搜索的内容 41 // 5、根据Searcher返回TopDocs 42 TopDocs tds = searcher.search(query, 20);//查询20条记录 43 // 6、根据TopDocs获取ScoreDoc 44 ScoreDoc[] sds = tds.scoreDocs; 45 // 7、根据Searcher和ScoreDoc获取搜索到的document对象 46 int cou=0; 47 for(ScoreDoc sd:sds){ 48 cou++; 49 Document d = searcher.doc(sd.doc); 50 // 8、根据document对象获取查询的字段值 51 /** 查询结果中content为空,是因为索引中没有存储content的内容,需要根据索引path和name从原文件中获取content**/ 52 res+=cou+". "+d.get("path")+" "+d.get("name")+" "+d.get("content")+" "; 53 } 54 55 56 }catch(Exception e){ 57 e.printStackTrace(); 58 }finally{ 59 //9、关闭reader 60 try { 61 reader.close(); 62 } catch (IOException e) { 63 e.printStackTrace(); 64 } 65 } 66 return res; 67 } 68 public static void main(String[] args) throws IOException 69 { 70 System.out.println(indexSearch("你好")); //搜索的内容可以修改 71 } 72 }
搜索的过程总的来说就是将词典及倒排表信息从索引中读出来,根据用户输入的查询语句合并倒排表,得到结果文档集并对文档进行打分的过程。
总结起来检索有以下以下五个步骤:
1. 打开IndexReader指向索引文件夹。
1 Directory directory = FSDirectory.open(Paths.get("index")); 2 IndexReader reader = DirectoryReader.open(directory);
这一步骤将磁盘上的索引信息读入内存。
2. 创建IndexSearcher准备进行搜索。
1 IndexSearcher searcher = new IndexSearcher(reader);
IndexSearcher提供了两个非常重要的函数:
- void setSimilarity(Similarity similarity),用户可以实现自己的Similarity对象,从而影响搜索过程的打分。
- 一系列search函数,是搜索过程的关键,主要负责打分的计算和倒排表的合并。
3. 创建QueryParser解析查询语句生成查询对象。
1 QueryParser parser = new QueryParser("content",new StandardAnalyzer());//content表示搜索的域或者说字段 2 Query query = parser.parse(keywords);//被搜索的内容
解析分为两个过程:
- 创建Analyer用来对查询语句进行词法分析和语言处理。
- QueryParser调用parser进行语法分析,形成查询语法树,放到Query中。
4. IndexSearcher调用search对查询语法树Query进行搜索,得到结果集Topdocs。
1 // 5、根据Searcher返回TopDocs 2 TopDocs tds = searcher.search(query, 20);//查询20条记录 3 // 6、根据TopDocs获取ScoreDoc 4 ScoreDoc[] sds = tds.scoreDocs;
该方法收集文档集合并计算打分。
5. 返回查询结果给用户。
1 int cou=0; 2 for(ScoreDoc sd:sds){ 3 cou++; 4 Document d = searcher.doc(sd.doc); 5 // 8、根据document对象获取查询的字段值 6 /** 查询结果中content为空,是因为索引中没有存储content的内容,需要根据索引path和name从原文件中获取content**/ 7 res+=cou+". "+d.get("path")+" "+d.get("name")+" "+d.get("content")+" "; 8 }
在返回查询结果给用户时,为了提高用户体验,我们可以给关键词标注高亮和分页返回结果。
5.1 给关键词标注高亮。
1 public static String displayHtmlHighlight(Query query, String fieldName, String fieldContent) throws IOException, InvalidTokenOffsetsException 2 { 3 MyIkAnalyzer analyzer=new MyIkAnalyzer(); 4 //设置高亮标签,可以自定义 5 SimpleHTMLFormatter formatter = new SimpleHTMLFormatter("<font color='#ff0000'>", "</font>"); 6 /**创建QueryScorer*/ 7 //评分 8 QueryScorer scorer=new QueryScorer(query); 9 /**创建Fragmenter*/ 10 Fragmenter fragmenter = new SimpleSpanFragmenter(scorer); 11 //高亮分析器 12 Highlighter highlight=new Highlighter(formatter,scorer); 13 highlight.setTextFragmenter(fragmenter); 14 //fieldname是域名,如"title",fieldContent是d.get("title"); 15 String str=highlight.getBestFragment(analyzer, fieldName, fieldContent); 16 if (str==null) return fieldContent; 17 return str; 18 }
该函数有三个参数:
- Query query是第4步产生的查询对象。
- String fieldName是要标注内容的域名,比如“title”
- String fieldContent是要标注的具体内容,比如某一个“title”的具体内容。
该函数实现了两个基本功能:
- 如果要标注内容fieldContent为空,返回空串。
- 不为空时,对fieldContent进行自定义的html标签标注。
1 SimpleHTMLFormatter formatter = new SimpleHTMLFormatter("<font color='#ff0000'>", "</font>");
这里可以进行个性化定制。关于HighLighter的具体用法大家可以参考我的另一篇博客【lucene系列学习二】Lucene实现高亮显示关键词
关于关键词高亮的具体原理可以参考搜索结果的处理和显示《第六篇》
5.2 分页展示结果。
这里介绍一种简单的分页方法:
1 int start=(pageIndex-1)*pageSize; 2 int end=pageIndex*pageSize; 3 Document d=null; 4 int cnt=0; 5 for(int i=start;i<end&&i<sds.length;i++) 6 { 7 d = searcher.doc(sds[i].doc); 8 //输出d 9 }
其中pageIndex和pageSize可以是前端传的参数。
以上五个步骤就可以基本实现Lucene的检索、关键词高亮和分页返回结果了,是不是很简单呢?
下节我们会介绍Lucene的高级检索方式~~