Lucene7.2.1系列(三)查询及高亮

简介: Lucene我想暂时先更新到这里,仅仅这三篇文章想掌握Lucene是远远不够的。另外我这里三篇文章都用的最新的jar包,Lucene更新太快,5系列后的版本和之前的有些地方还是有挺大差距的,就比如为文档域设置权值的setBoost方法6.6以后已经被废除了等等。

系列文章:

Lucene系列(一)快速入门

Lucene系列(二)luke使用及索引文档的基本操作

Lucene系列(三)查询及高亮

一 准备

创建项目并添加Maven依

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.lucene/lucene-core -->
        <!-- Lucene核心库 -->
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-core</artifactId>
            <version>7.2.1</version>
        </dependency>
        <!-- Lucene解析库 -->
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-queryparser</artifactId>
            <version>7.2.1</version>
        </dependency>
        <!-- Lucene附加的分析库 -->
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-analyzers-common</artifactId>
            <version>7.2.1</version>
        </dependency>
        <!-- 高亮显示 -->
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-highlighter</artifactId>
            <version>7.2.1</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.apache.lucene/lucene-analyzers-smartcn -->
        <!-- 中文分词 -->
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-analyzers-smartcn</artifactId>
            <version>7.2.0</version>
        </dependency>
    </dependencies>

二 对特定单词查询/模糊查询和查询表达式

写索引

import java.io.File;
import java.io.FileReader;
import java.nio.file.Paths;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;

public class Indexer {

    private IndexWriter writer; // 写索引实例
    
    /**
     * 构造方法 实例化IndexWriter
     * @param indexDir
     * @throws Exception
     */
    public Indexer(String indexDir)throws Exception{
        Directory dir=FSDirectory.open(Paths.get(indexDir));
        Analyzer analyzer=new StandardAnalyzer(); // 标准分词器
        IndexWriterConfig iwc=new IndexWriterConfig(analyzer);
        writer=new IndexWriter(dir, iwc);
    }
    
    /**
     * 关闭写索引
     * @throws Exception
     */
    public void close()throws Exception{
        writer.close();
    }
    
    /**
     * 索引指定目录的所有文件
     * @param dataDir
     * @throws Exception
     */
    public int index(String dataDir)throws Exception{
        File []files=new File(dataDir).listFiles();
        for(File f:files){
            indexFile(f);
        }
        return writer.numDocs();
    }

    /**
     * 索引指定文件
     * @param f
     */
    private void indexFile(File f) throws Exception{
        System.out.println("索引文件:"+f.getCanonicalPath());
        Document doc=getDocument(f);
        writer.addDocument(doc);
    }

    /**
     * 获取文档,文档里再设置每个字段
     * @param f
     */
    private Document getDocument(File f)throws Exception {
        Document doc=new Document();
        doc.add(new TextField("contents",new FileReader(f)));
        doc.add(new TextField("fileName", f.getName(),Field.Store.YES));
        doc.add(new TextField("fullPath",f.getCanonicalPath(),Field.Store.YES));
        return doc;
    }
    
    public static void main(String[] args) {
        String indexDir="D:\\lucene\\searchindex";
        String dataDir="D:\\lucene\\data";
        Indexer indexer=null;
        int numIndexed=0;
        long start=System.currentTimeMillis();
        try {
            indexer = new Indexer(indexDir);
            numIndexed=indexer.index(dataDir);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }finally{
            try {
                indexer.close();
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        long end=System.currentTimeMillis();
        System.out.println("索引:"+numIndexed+" 个文件 花费了"+(end-start)+" 毫秒");
    }
}

读取索引


import java.nio.file.Paths;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class SearchTest {

    private Directory dir;
    private IndexReader reader;
    private IndexSearcher is;
    
    @Before
    public void setUp() throws Exception {
        dir=FSDirectory.open(Paths.get("D:\\lucene\\searchindex"));
        reader=DirectoryReader.open(dir);
        is=new IndexSearcher(reader);
    }

    @After
    public void tearDown() throws Exception {
        reader.close();
    }
}

对特定单词查询和模糊查询


    /**
     * 对特定单词查询及模糊查询
     * 
     * @throws Exception
     */
    @Test
    public void testTermQuery() throws Exception {
        String searchField = "contents";
        // 所给出的必须是单词,不然差不到
        String q = "authorship";
        // 一个Term表示来自文本的一个单词。
        Term t = new Term(searchField, q);
        // 为Term构造查询。
        Query query = new TermQuery(t);
         /** 
         * 1.需要根据条件查询 
         *  
         * 2.最大可编辑数,取值范围0,1,2 
         * 允许我的查询条件的值,可以错误几个字符 
         *  
         */  
        Query query2 = new FuzzyQuery(new Term(searchField,"authorshioo"),1);  
        TopDocs hits = is.search(query, 10);
        // hits.totalHits:查询的总命中次数。即在几个文档中查到给定单词
        System.out.println("匹配 '" + q + "',总共查询到" + hits.totalHits + "个文档");
        for (ScoreDoc scoreDoc : hits.scoreDocs) {
            Document doc = is.doc(scoreDoc.doc);
            System.out.println(doc.get("fullPath"));
        }
        TopDocs hits2 = is.search(query2, 10);
        // hits.totalHits:查询的总命中次数。即在几个文档中查到给定单词
        System.out.println("匹配 '" + "authorshioo"+ "',总共查询到" + hits2.totalHits + "个文档");
        for (ScoreDoc scoreDoc : hits2.scoreDocs) {
            Document doc = is.doc(scoreDoc.doc);
            System.out.println(doc.get("fullPath"));
        }
    }

我们上面查询了单词“authorship”以及模糊查询了单词"authorshioo",结果如下:

可以看到只在LICENSE.txt文档下找到该单词。
data
那么模糊查询为什么查不到单词"authorshioo"呢?
这是因为我们在这里允许可以错误几个字符为1个,但是我们单词"authorshioo"错误字符个数为2个,所以就查不到。

        Query query2 = new FuzzyQuery(new Term(searchField,"authorshioo"),1);  

解析表达式的使用

    /**
     * 解析查询表达式
     * 
     * @throws Exception
     */
    @Test
    public void testQueryParser() throws Exception {
        // 标准分词器
        Analyzer analyzer = new StandardAnalyzer();
        String searchField = "contents";
        String q = "atomic a atomicReader";
        String q2 = "AtomicReader and AtomicReaderContext";
        // 建立查询解析器
        //searchField:要查询的字段; 
        //analyzer:标准分词器实例
        QueryParser parser = new QueryParser(searchField, analyzer);
        Query query = parser.parse(q);
        //返回查询到的前10项(查到100个相关内容的话也只会返回10个)
        TopDocs hits = is.search(query, 10);
        System.out.println("匹配 " + q + "查询到" + hits.totalHits + "个记录");
        for (ScoreDoc scoreDoc : hits.scoreDocs) {
            Document doc = is.doc(scoreDoc.doc);
            System.out.println(doc.get("fullPath"));
        }
        
        QueryParser parser2 = new QueryParser(searchField, analyzer);
        Query query2 = parser2.parse(q2);
        //返回查询到的前10项(查到100个相关内容的话也只会返回10个)
        TopDocs hits2 = is.search(query2, 10);
        System.out.println("匹配 " + q2 + "查询到" + hits2.totalHits + "个记录");
        for (ScoreDoc scoreDoc : hits2.scoreDocs) {
            Document doc = is.doc(scoreDoc.doc);
            System.out.println(doc.get("fullPath"));
        }
    }

我们上面分别查询了:“atomic a atomicReader”和“AtomicReader and AtomicReaderContext”,通过查询结果可以看出即使稍微改变查询内容,也还是可以查询到和我们给出的表达式相关的文档。
查询结果

三 中文查询及高亮

写索引

import java.nio.file.Paths;

import org.apache.lucene.analysis.cn.smart.SmartChineseAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;  
  
public class Indexer {  
  
    private String[] ids={"1","2","3"};  
    private String citys[]={"青岛","南京","上海"};  
    private String descs[]={  
            "青岛是一个漂亮的城市。",  
            "南京是一个文化的城市。",  
            "上海是一个繁华的城市。"  
    };  
      
    private Directory dir;  
      
    /** 
     *实例化indexerWriter 
     * @return 
     * @throws Exception 
     */  
    private IndexWriter getWriter()throws Exception{  
  
        //中文分词器  
        SmartChineseAnalyzer analyzer=new SmartChineseAnalyzer();  
          
        IndexWriterConfig iwc=new IndexWriterConfig(analyzer);  
          
        IndexWriter writer=new IndexWriter(dir, iwc);  
          
        return writer;  
    }  
      
    /** 
     * 获取indexDir 
     * @param indexDir 
     * @throws Exception 
     */  
    private void index(String indexDir)throws Exception{  
          
        dir=FSDirectory.open(Paths.get(indexDir));  
          
        IndexWriter writer=getWriter();  
          
        for(int i=0;i<ids.length;i++){  
              
            Document doc=new Document();  
              
            doc.add(new StringField("id", ids[i], Field.Store.YES));  
            doc.add(new StringField("city",citys[i],Field.Store.YES));  
            doc.add(new TextField("desc", descs[i], Field.Store.YES));  
              
            writer.addDocument(doc);   
        }  
          
        writer.close();  
    }  
      
      
    public static void main(String[] args) throws Exception {  
          
        new Indexer().index("D:\\lucene\\dataindex2");  
        System.out.println("Success Indexer");  
    }  
      
}  

中文查询及高亮显示

import java.io.StringReader;
import java.nio.file.Paths;

import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.cn.smart.SmartChineseAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.highlight.Fragmenter;
import org.apache.lucene.search.highlight.Highlighter;
import org.apache.lucene.search.highlight.QueryScorer;
import org.apache.lucene.search.highlight.SimpleHTMLFormatter;
import org.apache.lucene.search.highlight.SimpleSpanFragmenter;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;  
  
/** 
 *  
 * 通过索引字段来读取文档 
 * @author LXY 
 * 
 */  
public class SearchTest {  
  
    public static void search(String indexDir, String par) throws Exception{  
                  
            //得到读取索引文件的路径  
            Directory dir = FSDirectory.open(Paths.get(indexDir));  
                  
            //通过dir得到的路径下的所有的文件  
            IndexReader reader = DirectoryReader.open(dir);  
                  
            //建立索引查询器  
            IndexSearcher searcher = new IndexSearcher(reader);  
                  
            //中文分词器  
            SmartChineseAnalyzer analyzer=new SmartChineseAnalyzer();  
                  
            //建立查询解析器  
            /** 
             * 第一个参数是要查询的字段; 
             * 第二个参数是分析器Analyzer 
             * */  
            QueryParser parser = new QueryParser("desc", analyzer);  
                  
            //根据传进来的par查找  
            Query query = parser.parse(par);  
                  
            //计算索引开始时间  
            long start = System.currentTimeMillis();  
                  
            //开始查询  
            /** 
             * 第一个参数是通过传过来的参数来查找得到的query; 
             * 第二个参数是要出查询的行数 
             * */  
            TopDocs topDocs = searcher.search(query, 10);  
                  
            //索引结束时间  
            long end = System.currentTimeMillis();  
                  
            System.out.println("匹配"+par+",总共花费了"+(end-start)+"毫秒,共查到"+topDocs.totalHits+"条记录。");  
                  
                  
            //高亮显示start  
                  
            //算分  
            QueryScorer scorer=new QueryScorer(query);  
                  
            //显示得分高的片段  
            Fragmenter fragmenter=new SimpleSpanFragmenter(scorer);  
                  
        //设置标签内部关键字的颜色  
        //第一个参数:标签的前半部分;第二个参数:标签的后半部分。  
        SimpleHTMLFormatter simpleHTMLFormatter=new SimpleHTMLFormatter("<b><font color='red'>","</font></b>");  
                  
            //第一个参数是对查到的结果进行实例化;第二个是片段得分(显示得分高的片段,即摘要)  
                Highlighter highlighter=new Highlighter(simpleHTMLFormatter, scorer);  
                  
                //设置片段  
                highlighter.setTextFragmenter(fragmenter);  
                  
                //高亮显示end  
                  
                //遍历topDocs  
                /** 
                 * ScoreDoc:是代表一个结果的相关度得分与文档编号等信息的对象。 
                 * scoreDocs:代表文件的数组 
                 * @throws Exception  
                 * */  
                for(ScoreDoc scoreDoc : topDocs.scoreDocs){  
                      
                    //获取文档  
                    Document document = searcher.doc(scoreDoc.doc);  
                      
                    //输出全路径  
                    System.out.println(document.get("city"));  
                    System.out.println(document.get("desc"));  
                      
                    String desc = document.get("desc");  
                    if(desc!=null){  
                          
                        //把全部得分高的摘要给显示出来  
                          
            //第一个参数是对哪个参数进行设置;第二个是以流的方式读入  
            TokenStream tokenStream=analyzer.tokenStream("desc", new StringReader(desc));  
                          
            //获取最高的片段  
            System.out.println(highlighter.getBestFragment(tokenStream, desc));  
                }  
        }  
                  
        reader.close();  
    }  
              
              
    //开始测试  
    public static void main(String[] args) {  
                  
        //索引指定的路径  
        String indexDir = "D:\\lucene\\dataindex2";  
                  
        //查询的字段  
        String par = "南京";  
                  
        try {  
                      
            search(indexDir,par);  
                      
        } catch (Exception e) {  
        // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  
    }         
}  

结果会把我们查询的“南京”单词给高亮显示,这在我们平时搜索中很常见了。
高亮显示
我们平时搜索中的高亮就像下图:

欢迎关注我的微信公众号(分享各种Java学习资源,面试题,以及企业级Java实战项目回复关键字免费领取):
微信公众号

Lucene我想暂时先更新到这里,仅仅这三篇文章想掌握Lucene是远远不够的。另外我这里三篇文章都用的最新的jar包,Lucene更新太快,5系列后的版本和之前的有些地方还是有挺大差距的,就比如为文档域设置权值的setBoost方法6.6以后已经被废除了等等。因为时间有限,所以我就草草的看了一下Lucene的官方文档,大多数内容还是看java1234网站的这个视频来学习的,然后在版本和部分代码上做了改进。截止2018/4/1,上述代码所用的jar包皆为最新。

最后推荐一下自己觉得还不错的Lucene学习网站/博客:

官方网站:Welcome to Apache Lucene

Github:Apache Lucene and Solr

Lucene专栏

搜索系统18:lucene索引文件结构

Lucene6.6的介绍和使用

目录
相关文章
|
3月前
|
人工智能 安全 网络安全
从部署到精通:OpenClaw阿里云+本地安装保姆级教学与必装10个核心Skill解析
很多用户部署OpenClaw后,仅将其当作普通聊天工具,觉得“功能平平”,实则是未挖掘其核心价值——Skill(技能插件)。OpenClaw的本质是可拓展的AI生产力平台,Skill则是赋予其“执行力”的关键,能将AI从“聊天工具”升级为“虚拟员工”,覆盖自动化办公、知识管理、开发协作等全场景。
2919 3
|
3月前
|
数据采集 人工智能 机器人
从“会用”到“会改”:第一次打开OpenClaw配置文件,这些参数都是什么意思?
本文详解 OpenClaw 核心配置文件 `openclaw.json`(JSON5格式),逐层拆解 agents、models、channels、session 等关键模块,聚焦 temperature、模型切换、白名单、会话记忆等高频实用参数,并附热重载技巧、`doctor --fix` 救急命令与注释规范,助你从“能用”迈向“精通”。
|
11月前
|
存储 人工智能 自然语言处理
DeepSeek R1+Open WebUI实现本地知识库的搭建和局域网访问
本文介绍了使用 DeepSeek R1 和 Open WebUI 搭建本地知识库的详细步骤与注意事项,涵盖核心组件介绍、硬件与软件准备、模型部署、知识库构建及问答功能实现等内容,适用于本地文档存储、向量化与检索增强生成(RAG)场景的应用开发。
4267 0
|
9月前
|
人工智能 Ubuntu 数据可视化
【详细教程】如何在Ubuntu上本地部署Dify?
Dify 是一个开源的大语言模型应用开发平台,支持低代码/无代码开发,提供多模型接入、Agent框架、RAG检索增强生成等功能,助力快速构建AI应用。支持本地部署,提供详尽文档与可视化界面,适用于聊天助手、文本生成、自动化任务等多种场景。
9579 124
springboot字段注入@value细节
springboot字段注入@value细节
300 1
|
8月前
|
人工智能 JSON 测试技术
Dify入门实战:5分钟搭建你的第一个AI测试用例生成器
本文教你利用Dify平台,结合大模型API,5分钟内搭建一个无需编程基础的AI测试用例生成器。通过配置提示词、连接AI模型,实现自动输出覆盖正常、异常及边界场景的结构化测试用例,提升测试效率与质量,并支持集成到CI/CD流程,助力智能化测试落地。
|
11月前
|
存储 人工智能 自然语言处理
DeepSeek R1+Ollama+Cherry Studio实现本地知识库的搭建
本文介绍了如何使用Ollama和CherryStudio搭建本地知识库,涵盖核心组件介绍、硬件与软件准备、模型部署流程及知识库构建方法。通过配置DeepSeek R1模型、嵌入模型和Cherry Studio平台,实现本地化自然语言处理与知识检索功能。
3533 0
|
编译器 C语言
【C语言】宏定义详解
宏定义(Macro Definition)是C语言预处理器的一部分,通过`#define`指令引入。宏定义在编译前的预处理阶段进行文本替换,即将代码中的宏名替换为定义的内容。
3290 6
|
云安全 数据采集 弹性计算
阿里云ACP认证考什么?考试费用是多少?
最近几年越来越多的人想进入IT行业,在这之前,他们都选择先考取阿里云ACP认证,这个认证是阿里云旗下的人才认证系统,可以说是打开大厂的敲门砖,是获得良好待遇、丰厚报酬的谈判筹码。
1090 0
阿里云ACP认证考什么?考试费用是多少?

热门文章

最新文章