在Lucene的org.apache.lucene.search.highlight包中提供了关于高亮显示检索关键字的工具。我们在使用百度、Google搜索的时候,检索结果显示的时候,在摘要中实现与关键字相同的词条进行高亮显示,百度和Google指定红色高亮显示。
有了Lucene提供的高亮显示的工具,可以很方便地实现高亮显示的功能。
高亮显示,就是根据用户输入的检索关键字,检索找到该关键字对应的检索结果文件,提取对应于该文件的摘要文本,然后根据设置的高亮格式,将格式写入到摘要文本中对应的与关键字相同或相似的词条上,在网页上显示出来,该摘要中的与关键字有关的文本就会以高亮的格式显示出来。
高亮显示模块需要两个独立的输入:完整的原始文本以用来提供操作数据,以及来源于该文本的一个TokenStream。为了创建TokenStream,你必须对文本进行重新分析,此时需要使用与索引期间相同的分析器进行。
Highlighter依赖于词汇单元流中每个词汇单元的起始和结束位置偏移量来将原始输入文本中的字符片段进行精确定位,来用于高亮显示。
高亮处理的相关概念:
Fragmenter
作用是将原始字符串拆分成独立的片段。
NullFragmenter 是该接口的一个具体实现类,它将整个字符串作为单个片段返回,这适合于处理title域和前台文本较短的域,而对于这些域来说,我们是希望在搜索结果中全部展示。
SimpleFragmenter 是负责将文本拆分封固定字符长度的片段,但它并处理子边界。你可以指定每个片段的字符长度(默认情况100)但这类片段有点过于简单,在创建片段时,他并不限制查询语句的位置,因此对于跨度的匹配操作会轻易被拆分到两个片段中;
SimpleSpanFragmenter 是尝试将让片段永远包含跨度匹配的文档。
如果不Highlighter实例中设置Fragmenter,那么它会默认的SimpleFragmenter。
Scorer
Fragmenter输出的是文本片段序列,而Highlighter必须从中挑选出最适合的一个或多个片段呈现给客户,为了做到这点,Highlighter会要求Java接口Scorer来对每个片段进行评分。
Highlighter提供了两个Scorer具体实现类:QueryTermScorer和QueryScorer
QueryTermScorer 基于片段中对应Query的项数进行评分。
QueryScorer只对促成文档匹配的实际项进行评分。
Encoder
他的目的很简单,将初始文本编码成外部格式。该接口实现有两个:
DefaultEncoder:默认情况下供Hightlighter使用,它并不对文本进行任何操作。
SimpleHTMLEncoder:负责将文本编码成HTML,并忽略一些如< 、>以及其它非ASCII等特殊字符。一旦完成编码,最后一步就是对片段进行格式化处理向用户展现。
Formatter
它负责将片段转换成String形式,以及将被高亮显示的项一起用于搜索结果展示以及高亮显示。
- public class Demo {
- /**
- * 将即将检索的资源写入索引库
- * @param writer
- * @throws Exception
- */
- public void buildDocs(IndexWriter writer)throws Exception {
- writer.deleteAll();//清空索引库里已存在的文档(document)
- List<User> list = DataUtil.getUsers_more();//得到数据资源
- System.out.println("buildDocs()->总人数为 :"+list.size());
- for(User user :list){
- Document doc = new Document();//创建索引库的文档
- doc.add(new Field("id",String.valueOf(user.getId()),Store.YES,Index.NO));
- doc.add(new Field("name",user.getName(),Store.YES,Index.ANALYZED));
- doc.add(new Field("age",String.valueOf(user.getAge()),Store.YES,Index.ANALYZED));
- doc.add(new Field("sex",user.getSex(),Store.YES,Index.ANALYZED));
- doc.add(new Field("birthday",String.valueOf(user.getBirthday()),Store.YES,Index.ANALYZED));
- writer.addDocument(doc);//将文档写入索引库
- }
- int count =writer.numDocs();
- writer.forceMerge(100);//合并索引库文件
- writer.close();
- System.out.println("buildDocs()->存入索引库的数量:"+count);
- }
- }
- public class HighlighterDemo extends Demo {
- /**
- * 从索引库中搜索你要查询的数据,并做最简单的高亮处理 html的<B>标签修饰
- * @param searcher
- * @throws IOException
- * @throws InvalidTokenOffsetsException
- * @throws ParseException
- */
- public void searToHighlighter(Analyzer analyzer,IndexSearcher searcher) throws IOException, InvalidTokenOffsetsException, ParseException{
- //Term term =new Term("sex", "男生");//查询条件,意思是我要查找性别为“男生”的人
- //PrefixQuery query =new PrefixQuery(term);
- QueryParser parser = new QueryParser(Version.LUCENE_36,"sex", analyzer);
- Query query = parser.parse("男生");
- TopDocs docs =searcher.search(query,null, 10);//查找
- System.out.println("searcherDoc()->男生人数:"+docs.totalHits);
- QueryScorer scorer=new QueryScorer(query);
- Fragmenter fragmenter = new SimpleSpanFragmenter(scorer);
- Highlighter highlight=new Highlighter(scorer);
- highlight.setTextFragmenter(fragmenter);
- int seq=0;
- for(ScoreDoc doc:docs.scoreDocs){//获取查找的文档的属性数据
- seq++;
- int docID=doc.doc;
- Document document =searcher.doc(docID);
- String str="序号:"+seq+",ID:"+document.get("id")+",姓名:"+document.get("name")+",性别:" ;
- String value =document.get("sex");
- if (value != null) {
- TokenStream tokenStream = analyzer.tokenStream("sex", new StringReader(value));
- String str1 = highlight.getBestFragment(tokenStream, value);
- str=str+str1;
- }
- System.out.println("查询出人员:"+str);
- }
- }
- /**
- * 从索引库中搜索你要查询的数据,使用CSS进行高亮显示处理
- * @param analyzer
- * @param searcher
- * @throws IOException
- * @throws InvalidTokenOffsetsException
- */
- public void searToHighlighterCss(Analyzer analyzer,IndexSearcher searcher) throws IOException, InvalidTokenOffsetsException{
- Term term =new Term("sex", "男生");//查询条件,意思是我要查找性别为“男生”的人
- TermQuery query =new TermQuery(term);
- TopDocs docs =searcher.search(query, 10);//查找
- System.out.println("searcherDoc()->男生人数:"+docs.totalHits);
- /**自定义标注高亮文本标签*/
- SimpleHTMLFormatter formatter = new SimpleHTMLFormatter("<span class=\"hightlighterCss\">","</span>");
- /**创建QueryScorer*/
- QueryScorer scorer=new QueryScorer(query);
- /**创建Fragmenter*/
- Fragmenter fragmenter = new SimpleSpanFragmenter(scorer);
- Highlighter highlight=new Highlighter(formatter,scorer);
- highlight.setTextFragmenter(fragmenter);
- int seq=0;
- for(ScoreDoc doc:docs.scoreDocs){//获取查找的文档的属性数据
- seq++;
- int docID=doc.doc;
- Document document =searcher.doc(docID);
- String str="序号:"+seq+",ID:"+document.get("id")+",姓名:"+document.get("name")+",性别:" ;
- String value =document.get("sex");
- if (value != null) {
- TokenStream tokenStream = analyzer.tokenStream("sex", new StringReader(value));
- String str1 = highlight.getBestFragment(tokenStream, value);
- str=str+str1;
- }
- System.out.println("查询出人员:"+str);
- }
- }
- }
- public class TestHighlighter {
- private IndexWriter writer=null;
- private Directory directory=null;
- private IndexReader reader = null;
- private IndexSearcher searcher=null;
- private HighlighterDemo demo =new HighlighterDemo();
- private Analyzer analyzer =null;
- @Before
- public void setUp() throws Exception {
- directory = new SimpleFSDirectory(new File("F:/luc_dir"));
- analyzer =new IKAnalyzer(); //new StandardAnalyzer(Version.LUCENE_36);
- IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_36,analyzer);
- writer = new IndexWriter(directory,config);
- }
- @Test
- public void testSearToHighlighter()throws Exception {
- /**生成索引库*/
- demo.buildDocs(writer);
- /**查询数据*/
- reader = IndexReader.open(directory);
- searcher =new IndexSearcher(reader);
- demo.searToHighlighter(analyzer,searcher);
- }
- @Test
- public void testSearToHighlighterCss()throws Exception {
- /**生成索引库*/
- demo.buildDocs(writer);
- /**查询数据*/
- reader = IndexReader.open(directory);
- searcher =new IndexSearcher(reader);
- demo.searToHighlighterCss(new IKAnalyzer(),searcher);
- }
- }
testSearToHighlighter测试结果:
- buildDocs()->总人数为 :100
- buildDocs()->存入索引库的数量:100
- searcherDoc()->男生人数:66
- 查询出人员:序号:1,ID:1,姓名:张三1,性别:我是<B>男生</B>
- 查询出人员:序号:2,ID:2,姓名:张三2,性别:我是<B>男生</B>
- 查询出人员:序号:3,ID:4,姓名:张三4,性别:我是<B>男生</B>
- 查询出人员:序号:4,ID:5,姓名:张三5,性别:我是<B>男生</B>
- 查询出人员:序号:5,ID:7,姓名:张三7,性别:我是<B>男生</B>
- 查询出人员:序号:6,ID:8,姓名:张三8,性别:我是<B>男生</B>
- 查询出人员:序号:7,ID:10,姓名:张三10,性别:我是<B>男生</B>
- 查询出人员:序号:8,ID:11,姓名:张三11,性别:我是<B>男生</B>
- 查询出人员:序号:9,ID:13,姓名:张三13,性别:我是<B>男生</B>
- 查询出人员:序号:10,ID:14,姓名:张三14,性别:我是<B>男生</B>
testSearToHighlighterCss 测试结果
- buildDocs()->总人数为 :100
- buildDocs()->存入索引库的数量:100
- searcherDoc()->男生人数:66
- 查询出人员:序号:1,ID:1,姓名:张三1,性别:我是<span class="hightlighterCss">男生</span>
- 查询出人员:序号:2,ID:2,姓名:张三2,性别:我是<span class="hightlighterCss">男生</span>
- 查询出人员:序号:3,ID:4,姓名:张三4,性别:我是<span class="hightlighterCss">男生</span>
- 查询出人员:序号:4,ID:5,姓名:张三5,性别:我是<span class="hightlighterCss">男生</span>
- 查询出人员:序号:5,ID:7,姓名:张三7,性别:我是<span class="hightlighterCss">男生</span>
- 查询出人员:序号:6,ID:8,姓名:张三8,性别:我是<span class="hightlighterCss">男生</span>
- 查询出人员:序号:7,ID:10,姓名:张三10,性别:我是<span class="hightlighterCss">男生</span>
- 查询出人员:序号:8,ID:11,姓名:张三11,性别:我是<span class="hightlighterCss">男生</span>
- 查询出人员:序号:9,ID:13,姓名:张三13,性别:我是<span class="hightlighterCss">男生</span>
- 查询出人员:序号:10,ID:14,姓名:张三14,性别:我是<span class="hightlighterCss">男生</span>