美文网首页
Spring Boot 集成 Lucence

Spring Boot 集成 Lucence

作者: ApesKingMan | 来源:发表于2021-03-20 20:01 被阅读0次

    Lucence 和全文检索

            Lucene 是 Apache Jakarta 家族中的一个开源项目,它不是一个完整的搜索应用程序,但可为我们的应用程序提供索引和搜索功能。Lucene 也是目前流行的基于 Java 的开源全文检索工具包。

            多应用程序基于 Lucene 实现了搜索功能,比如 Eclipse 帮助系统的搜索功能。Lucene 能为文本类型的数据建立索引,我们只要能把需要建立索引的数据转化为文本格式,Lucene 就能对该文档建立索引并实现搜索。

            文件中的数据属于非结构化数据,要提高搜索效率,首先需将非结构化数据中的一部分信息提取出来,重新组织,使其具有一定的结构,然后再对这些结构化的数据进行搜索,从而达到提高搜索效率的目的。这一过程就是全文搜索,即先建立索引,再对索引进行搜索。

    Spring Boot 集成 Lucence

    1. 依赖导入

    首先需要导入 Lucene 的依赖,它的依赖有好几个,如下:

    Lucene 默认支持英文分词,可以增加最后这个依赖,让它支持中文分词。倒数第二个依赖支持分词高亮,本文最后将利用它,实现搜索内容的高亮显示,模拟当前互联网上的做法,大家可以运用到实际项目中。

    2. 快速入门

    根据上文的分析,全文检索有两个步骤,先建立索引,再检索。为了测试这个过程,我新建了两个 Java 类,一个用来建立索引,另一个用来检索。

    1)建立索引

    首先,我们可以先在 D:\lucene\data 目录下保存几个文件。然后,新建一个 Indexer 类,实现建立索引的功能。首先在构造方法中初始化标准分词器和写索引实例。

    如下代码,首先将存放索引的文件夹路径传入构造方法中,并构建标准分词器(支持英文),之后利用标准分词器实例化写索引对象。

    接下来开始建立索引。大家可以结合代码注释来理解这一构建过程:

    这样索引就建好了,我们在该类中写一个 main 方法测试一下:

    我从 Tomcat 的 conf 目录下拷贝了 catalina.properties 和 logging.properties 两个文件,并放在 D:\lucene\data 目录下(这两个文件也可以在源码中获取)。执行上面代码,可以看到控制台输出了以下内容:

    接着,我们打开 D:\lucene\ 目录可以看到一些索引文件,这些文件不能删除,否则需重新构建索引。因为没有索引,便无法检索内容了。

    2)检索内容

    这两个文件的索引已经建立好了,接下来就可以写检索程序了,在这两个文件中查找特定的词。

    OK,检索代码写完了,大家可以结合代码注释来理解这段程序。下面写个 main 方法测试一下:

    查找 security 字符串,执行完成后,可以看到控制台打印出如下结果:

    可以看出,耗时了 23 毫秒后,在其中一个文件中找到了 security 这个字符串,并输出了文件的名称。上面的代码写得也很详细,大家可以应用在生产环境中。

    3. 中文分词检索高亮

    上文完成了建立索引和检索代码的编写,但在实际项目中,往往需在页面中将查询结果展示出来,比如查某个关键字,查到之后,将相关的信息点展示出来,并将查询的关键字高亮显示等等。这种需求在实际项目中非常常见,大多数网站上基本都有这种效果。本文,我们就使用 Lucene 来实现该效果。

    1)中文分词

    首先新建 ChineseIndexer 类,用来建立中文索引。中文索引与英文索引的建立过程一样,不同之处在于这里使用的是中文分词器。上面我们通过读取文件建立索引,这里我们不读取文件,直接对一个字符串建立索引。因为在实际项目中,绝大部分情况下,会获取一些文本字符串(比如从表中查询出来的结果),然后对该文本字符串建立索引。

    建立索引时,首先获取 IndexWriter 对象,将相关的内容生成索引。索引的 Key 可以根据项目的情况自定义,内容来自我们处理过的文本,或者从数据库中查询出来的文本。生成时,需要使用中文分词器,代码如下:

    这里我们用 id、city、desc 分别代表 id、城市名称和城市描述,将它们作为关键字来建立索引。后面我们获取的内容主要是城市描述,我故意将南京的城市描述写得长了些,下文检索时,根据不同的关键字会检索到不同部分的信息,这其中涉及到一个权重的概念。

    执行 main 方法,索引文件被保存到 D:\lucene2\ 中。

    2)中文分词查询

    中文分词查询代码的逻辑和默认查询代码差不多,主要区别在于,我们需要将查询出来的关键字做标红加粗等处理,此时需要计算出一个得分片段。比如分别搜索“南京文化”和“南京文明”,应该返回不同的结果,这个结果根据计算出的得分片段来确定。

    举个简单的例子,比如有一段文本:“你好,我的名字叫大佬,金迅教育王牌导师……,江湖人都叫我大大,我一直觉得,人与人之间讲得是真诚,而不是套路……”。

    如果搜“大佬”,可能会返回这样结果:“我的名字叫大佬,金迅教育王牌导师”。

    如果搜“大大”,可能会返回:“江湖人都叫我大大,我一直觉得”。

    Lucene 会根据搜索的关键字,在待搜索文本中计算关键字出现片段的得分,返回最能反映用户搜索意图的一段文本作为结果。明白了这个原理,我们看一下代码和注释:

    public class ChineseSearch {

        private static final Logger logger = LoggerFactory.getLogger(ChineseSearch.class);

        public static List search(String indexDir, String q) throws Exception {

            //获取要查询的路径,也就是索引所在的位置

    Directory dir = FSDirectory.open(Paths.get(indexDir));

    IndexReader reader = DirectoryReader.open(dir);

    IndexSearcher searcher = new IndexSearcher(reader);

            //使用中文分词器

    SmartChineseAnalyzer analyzer = new SmartChineseAnalyzer();

            //由中文分词器初始化查询解析器

    QueryParser parser = new QueryParser("desc", analyzer);

            //通过解析要查询的 String,获取查询对象

    Query query = parser.parse(q);

           //记录索引开始时间

           long startTime = System.currentTimeMillis();

           //开始查询,查询前 10 条数据,将记录保存在 docs 中

    TopDocs docs = searcher.search(query, 10);

           //记录索引结束时间

            long endTime = System.currentTimeMillis();

    logger.info("匹配{}共耗时{}毫秒", q, (endTime - startTime));

    logger.info("查询到{}条记录", docs.totalHits);

            //如果不指定参数的话,默认是加粗,即 <b><b/>

    SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter("<b><font color=red>","</font></b>");

            //根据查询对象计算得分,会初始化一个查询结果最高的得分

    QueryScorer scorer = new QueryScorer(query);

            //根据这个得分计算出一个片段

    Fragmenter fragmenter = new SimpleSpanFragmenter(scorer);

            //将这个片段中的关键字用上面初始化好的高亮格式高亮

    Highlighter highlighter = new Highlighter(simpleHTMLFormatter, scorer);

            //设置一下要显示的片段

    highlighter.setTextFragmenter(fragmenter);

            //取出每条查询结果

    List list = new ArrayList<>();

            for(ScoreDoc scoreDoc : docs.scoreDocs) {

                //scoreDoc.doc 相当于 docID,根据这个 docID 来获取文档

    Document doc = searcher.doc(scoreDoc.doc);

    logger.info("city:{}", doc.get("city"));

    logger.info("desc:{}", doc.get("desc"));

    String desc = doc.get("desc");

                //显示高亮

                if(desc != null) {

    TokenStream tokenStream = analyzer.tokenStream("desc", new StringReader(desc));

    String summary = highlighter.getBestFragment(tokenStream, desc);

    logger.info("高亮后的desc:{}", summary);

    list.add(summary);

    }

    }

    reader.close();

            return list;

    }

    }

    每一步代码都有注释,就不赘述了。接下来我们测试一下效果。

    3)测试一下

    我们使用 Thymeleaf 写个简单的页面来展示获取到的数据,并高亮展示。在 Controller 中,我们指定索引的目录和需要查询的字符串,如下:

    直接返回到 result.html 页面,该页面主要用来展示 Model 中的数据。

    这里注意下,不能使用 th:test,否则字符串中的 html 标签都会被转义,无法完面页面渲染。

    启动服务,在浏览器中输入:http://localhost:8080/lucene/test,我们搜索“南京文化”,看一下测试效果。

    再将 Controller 中的搜索关键字改成“南京文明”,看下命中的效果。

    可以看出,不同的关键词,它会计算一个得分片段,也就是说不同的关键字会命中不同位置的内容,最后将关键字按我们设定的形式高亮显示。从结果中可以看出,Lucene 也可以很智能地将关键字拆分命中,这在实际项目中很好用。

    相关文章

      网友评论

          本文标题:Spring Boot 集成 Lucence

          本文链接:https://www.haomeiwen.com/subject/ajyeihtx.html