要飞起来了,Lucene 高阶查询技巧

Lucene 查询原理 摘要:# 前言 Lucene 是一个基于 Java 的全文信息检索工具包,目前主流的搜索系统Elasticsearch和solr都是基于lucene的索引和搜索能力进行。想要理解搜索系统的实现原理,就需要深入lucene这一层,看看lucene是如何存储需要检索的数据,以及如何完成高效的数据检索。前言Lucene 是一个基于 Java 的全文信息检索工具包,目前主流的搜索系统Elasticsea... 阅读详情

在前面的章节中我们使用了最基础的关键词查询 TermQuery 和 复合查询 BooleanQuery,本节我们来尝试 Lucene 内置的其它高级查询功能。

字符串前缀查询 PrefixQuery

同关系数据库索引一样,得益于 FST 的前缀共享属性,Lucene 也支持前缀查询。它会将共享同样前缀的一系列关键词都找出来,然后再 merge 它们的文档列表。

var query = new PrefixQuery(new Term("title", "动"));

查询结果如下,可以看到 「动物」和「动作」等词汇都搜索出来了

640?wx_fmt=png
图片

但是默认的 PrefixQuery 不会对搜索的结果进行排序,它对所有被搜索出来的文档统一打分 1.0,在实现上可以让查询效率快很多,直接省去了收集所有文档进行排序的过程。如果你希望结果可以排序,可以给 PrefixQuery 设置一个选项,查询的结果可以观察 hit.score 值的大小。

var query = new PrefixQuery(new Term("content", "动"));
query.setRewriteMethod(MultiTermQuery.SCORING_BOOLEAN_REWRITE);
var hits = searcher.search(query, 10).scoreDocs;
for (var hit : hits) {
    var doc = searcher.doc(hit.doc);
    System.out.printf("%s:%.2f => %s\n", doc.get("url"), hit.score, doc.get("title"));
}
640?wx_fmt=png
图片

PrefixQuery 为什么要默认关闭排序呢?这是因为前缀查询能匹配到的关键词可能会很多,merge 所有的文档列表并排序将会是一个非常耗费性能的过程。

数字范围查询 NumericRangeQuery

数字查询和字符串查询不太一样,在内部实现结构上它并不是像字符串那样使用 FST 来组织关键词。如果每一个数字都是关键词,那么使用数字范围定位出的关键词会非常多,需要 merge 的文档列表也会非常多,这样的查询效率就会很差。如果是浮点型数字,那这个问题就更加突出了,可能每个浮点数字只会关联一个文档,而浮点数关键词将会太多太多。

Lucene 将数字进行特殊处理,内部使用 BKD-Tree 来组织数字键。BKD 树也是一个较为复杂的数据结构,简单理解就是将数值比较接近的一些数字联合起来作为一个叶节点共享一个 PostingList,同时在 PostingList 的元素需要存储数字键的值,在查询时会额外多出一个过滤步骤。粗略来看,BKD 树会将一个大的数值范围进行二分,然后再继续二分,一直分到几层之后发现关联的文档数量小于设定的阈值,就不再继续拆分了。BKD 树还可以索引多维数值,这样它就可以应用于地理位置查询。

下面我们给前面的文章索引增加一个字段,文章的 ID,我们需要将目录里的所有文件全部删除,然后运行 Indexer.java 重新构建索引。

var doc = new Document();
doc.add(new TextField("title", title, Field.Store.YES));
doc.add(new TextField("content", content, Field.Store.YES));
doc.add(new IntPoint("article_id", Integer.parseInt(id)));
doc.add(new StoredField("url", url));
indexWriter.addDocument(doc);

IntPoint 类型只索引,不存储,对应的选项默认是 Field.Store.NO,Lucene 只会将它的值放入 PostingList 的元素中。如果需要存储还需要单独增加一个 StoreField 类型,在本例中我们不需要存储。之所以叫 Point 类型是因为它可以支持多维数值,IntPoint 的构造器是一个可变参数,可以填入多个整数形成的数值对。下面我们尝试查询

var query = IntPoint.newRangeQuery("article_id", 400000, 450000);
var hits = searcher.search(query, 10).scoreDocs;

查询结果如下

640?wx_fmt=png
图片

对数值型的查询结果进行排序是没有意义的,返回的文档 score 值都是默认的 1.0。

关于 BKD 树的更多细节,我们后面再继续讨论。

字符串范围查询 TermRangeQuery

同数字范围查询类似,字符串也有范围查询,它是通过遍历关键词前缀树 FST 来实现的,它会按照字典序将范围内的所有词汇都列出来,然后 merge 所有关键词的文档列表。如果这个范围覆盖的关键词很多,可以预见性能是很差的。不过,字典序对于中文来说是没有意义的,所以通常我们不会去使用它。

var query = TermRangeQuery.newStringRange("level", "level1", "level9", true, true);

上面的查询表示等级范围在 [level1, level9] 之间的所有数据,参数中的两个 bool 值表示是否包含边界值。如果是两个 false,那么等级范围就是 (level1, level9)。

短语查询 PhraseQuery

「北京大学」是一个词汇,我们可以使用 TermQuery 关键词查询来快速定位所有包含「北京大学」的内容。比如下面这个查询就可以查出很多文章,因为「北京大学」上镜率很高。

var query = new TermQuery(new Term("content", "北京大学"));

那如何定位「北京xx大学」这样的内容呢?它可以是「北京科技大学」、「北京交通大学」、「北京化工大学」等词,但是不可以匹配「我是北京人,我没上过大学」这样的语句。这时候就可以用到短语查询 PhraseQuery。

var query = new PhraseQuery.Builder()
        .add(new Term("content", "北京"))
        .add(new Term("content", "大学"))
        .setSlop(2)
        .build();

这里有一个关键参数 Slop 表示两个词汇之间最多允许间隔的单词数量,注意这里是「单词」数量,而不是「字符」数量。如此它就可以匹配「北京工业大学」,同时还可以匹配「北京大学」。下面我们来看看它的查询结果

640?wx_fmt=png
图片

奇怪的是它只搜寻到了三篇文章,点开链接进去后发现它只匹配到了「北京林业大学」、「北京体育大学」,就是没有匹配到「北京大学」,难道 PhraseQuery 的 Slop 字段含义另有解释?我也被这个问题愣了一段时间,结果发现是因为在构建索引的时候使用的中文切词分析器的问题。我们用的是 HanLPAnalyser,它会将「北京大学」切成单个词汇,而不是切成三个词汇「北京」、「大学」和「北京大学」,这样就会导致相关文章只会收录到倒排索引中的「北京大学」这个词,而不会索引到「北京」和「大学」这两个词上面。那该如何解决这个问题呢?这需要我们在构建索引时对分词器进行适当的修改。

var analyser = new Analyzer() {
    protected TokenStreamComponents createComponents(String fieldName) {
        var tokenizer = new HanLPTokenizer(HanLP.newSegment().enableOffset(true).enableIndexMode(true), null, false);
        return new TokenStreamComponents(tokenizer);
    }
};

将 HanLP 的 indexMode 打开,如此就可以将 「北京大学」分词成三个词汇「北京」、「大学」和「北京大学」。重新建立索引后,再次尝试查询,就可以看到期望的搜寻结果。

640?wx_fmt=png
图片

从结果中我们可以注意到文章是携带排序分值信息的,「北京」和「大学」词汇越接近,出现的越频繁,文章的评分就越高。同时我们还要注意到它是携带顺序的,它不能匹配「大学xx北京」这样的内容。

正则查询 WildcardQuery

查询「北京xx大学」的方式除了上面的短语查询之外,Lucene 还提供了正则查询。比如上面的查询可以改写成

var query = new WildcardQuery(new Term("content", "北京*大学"));

它可以匹配「北京大学」和「北京林业大学」,* 号表示 0 到多个字符。但是如果你仔细查看查询结果的内容你会发现,所有的内容都是包含「北京大学」这样的词汇,「北京xx大学」这样的文章却没有找出来。这是为什么呢?因为正则匹配的对象是倒排索引的关键词。使用上面的分词器是不会得到「北京xx大学」这样的词汇的,分词的结果是「北京」、「xx」和「大学」这三个词汇。从这点来说它和 PhraseQuery 差异还是很大的。除了 * 号之外还有 ? 号表示单个字符,它不能使用任意的复杂正则表达式。注意如果 * 号位于词汇开头,查询将会尝试扫描所有关键词来寻找出匹配的候选词,这对性能将是一个很大的伤害。所以我们要尽可能避免使用首字母 * 号的正则查询,词汇的前缀越长查询性能越好。

模糊查询 FuzzyQuery

最后我们来看一个更加高级的查询模式 —— 模糊查询。它跟上面的查询方式都不太一样,它是基于「编辑距离」的查询。当我们目标查询是「北京大学」时它可以匹配「北方大学」,还可以匹配「北京中学」,它的性能不怎么样,因为和指定词汇相似的词汇会有很多选择,如此就会匹配非常多的词汇,需要 merge 非常多的文档列表,然后还需要根据编辑距离和词汇的频率进行评分排序。除了 merge 文档列表和排序的代价之外,寻找到相似的词汇也需要一定的代价。它需要搜寻整个关键词的前缀树(FST),然后计算它们之间的编辑距离,再挑选出「最大编辑距离」范围内的词汇。

var query = new FuzzyQuery(new Term("content", "北京大学"));

为了加速关键词匹配的效率,减少相似的关键候选词,FuzzyQuery 给查询做了若干限制条件。它可以设置关键词的前缀数量 prefixLength,比如如果前缀数量为2,那么指定「北京大学」,就不会匹配「北方大学」,但是可以匹配「北京中学」。它可以设置编辑距离的最大值 maxEdits,跟关键词差距太大的词汇就不用考虑了。它还可以设置相似词汇候选集的最大数量 maxExpansions,避免需要 merge 太多的文档列表。这三个选项的默认值分别是

var defaultMaxEdits = 2
var defaultPrefixLength = 0
var defaultMaxExpansions = 50

因为默认的前缀长度是 0,所以它需要扫描整颗关键词树,这个性能很差。使用时务必注意,保险起见,还是别轻易使用 FuzzyQuery。

全表遍历 MatchAllDocsQuery

同关系数据库一样,Lucene 也提供了全表遍历查询 MatchAllDocsQuery,它是不走倒排索引的,因为基数太大,所以默认不评分不排序。如果数据量很大,进行全量评分排序估计会把服务器卡死,最好也不要使用它。

var query = new MatchAllDocsQuery();

下一节我们来讲 Lucene 最高级的查询模式 —— 表达式查询 QueryParser,它可以将一个文本表达式字符串解析成上面各种 Query 的逻辑组合 BooleanQuery,有了 QueryParser 我们就可以非常方便地构造出一个十分复杂的组合逻辑查询。

Chatbox使用指南 阅读详情

相关推荐

Halcon实战:基于图像清晰度自动筛选最佳对焦图片

本文详细介绍了如何使用Halcon实现基于图像清晰度的自动筛选最佳对焦图片技术。通过分析灰度值统计和标准差等核心指标,结合实战代码示例,帮助开发者高效处理工业检测中的对焦问题,提升自动化检测的准确性和效率。

weixin_27015733的博客 443

lucene-对搜索结果进行排序

1、   在IndexSearcher类中包含了几个可重载的search方法,有一个对结果进行排序的search方法声明为search(Query,Sort)   public classSortingExample{     private Directory directory;          public SortingExample(Directory direco

深未来技术 626

SLG大地图实战:从Tilemap到Shader的地表渲染与数据分层架构

本文深入探讨了SLG大地图开发中的关键技术挑战与解决方案,包括Tilemap系统的深度改造、地表渲染的Shader优化以及数据分层架构设计。通过实战案例,展示了如何实现视觉表现丰富、性能开销可控的SLG大地图,特别适合Unity开发者和游戏技术美术参考。

weixin_28693309的博客 523

一个朴素的搜索引擎实现

今天我们要使用 Lucene 来实现一个简单的搜索引擎,我们要使用上一节爬取的果壳网语料库来构建索引,然后在索引的基础上进行关键词查询。上一节果壳网的语料库放在了 Red...

codehole_的博客 754

VCS的增量和分离编译

vcs的增量和分离编译

yunduor909的博客 3303

Sylvester结式法求解多项式方程

Sylvester结式法,结式,消元法,ctfshow 七夕杯 LUCK7,crypto,WriteUp

u010883831的博客 4535

lucene 按照匹配度排序_Lucene搜索详解

上篇说了Lucene索引,建好了索引,后面就是需要查询搜索了。Lucene搜索代码示例public class SearchBaseFlow { public static void main(String[] args) throws IOException, ParseException { // 使用的分词器 Analyzer analyzer = new IKAnalyz...

weixin_39843738的博客 832

古月学院《如何在Gazebo中实现阿克曼转向车的仿真》课程资料在ROS Noetic版本中运行时的常见错误及解决方法

   最近想在Gazebo中开发一些仿真内容,于是购买了古月学院的《如何在Gazebo中实现阿克曼转向车的仿真》课程,但是由于ROS 版本的更新,拿到手的课程资料不能直接在ROS Noetic版本中运行,经过我这几天的探索,对遇到的错误及解决方法进行了整理,方便后面购买课程的小伙伴更快的成功运行程序    在文章正式开始之前,先放一下我之前为了寻找该资料报错的原因,而写的两篇与该课程相关的文章链接,感兴趣的可以自行阅读    以阿克曼转向车为例对URDF和XACRO文件进行解读(点击可跳转)    将x

慕羽★的博客 5975

模糊查询正则语句_要飞起来了,Lucene 高阶查询技巧

在前面的章节中我们使用了最基础的关键词查询 TermQuery 和 复合查询 BooleanQuery,本节我们来尝试 Lucene 内置的其它高级查询功能。字符串前缀查询 PrefixQuery同关系数据库索引一样,得益于 FST 的前缀共享属性,Lucene 也支持前缀查询。它会将共享同样前缀的一系列关键词都找出来,然后再 merge 它们的文档列表。varquery=new...

weixin_39715290的博客 320

黑猫带你学eMMC协议第1篇:全网最全emmc协议中文详讲,这份学习框架图,你值得拥有!!!(持续更新中...)

黑猫带你学eMMC协议第7篇:全网最全emmc写保护详解(write protect) 黑猫带你学eMMC协议第8篇:emmc可靠写详解(Write Reliability) 黑猫带你学eMMC协议第9篇:emmc的分区配置(partitioning) 黑猫带你学eMMC协议第10篇:eMMC命令队列详解(CMDQ) 黑猫带你学eMMC协议第11篇:eMMC擦除详解(erase/trim/discard/sanitize/secure erase/secure trim…) 黑猫带你学eMMC协议第12篇:

黑猫奥利奥的博客 2万+

嵌入式软件工程师面试题目整理(一)

文章目录嵌入式软件工程师面试题目整理(一)Arm有多少32位寄存器?Arm2440和6410有什么区别CPU,MPU,MCU,SOC,SOPC联系与差别上拉&下拉&高阻态串口协议讲一讲RS232和RS485通讯接口有什么区别IIC时序图画一下,IIC有哪些状态,给一个字节,将它发送出去。IIC有什么注意事项?有没有用I/O模拟IIC,如果有需要注意什么?为什么2440的内存起始地址是3后面7个0呢?为什么6410的内存起始地址是5后面7个0呢?内存管理有什么看法?(MMU)锁有哪些?有什么注

嵌入式与Linux那些事的博客 7883

Lucene 高阶查询技巧

在前面的章节中我们使用了最基础的关键词查询 TermQuery 和 复合查询 BooleanQuery,本节我们来尝试 Lucene 内置的其它高级查询功能。 字符串前缀查询 PrefixQuery 同关系数据库索引一样,得益于 FST 的前缀共享属性,Lucene 也支持前缀查询。它会将共享同样前缀的一系列关键词都找出来,然后再 merge 它们的文档列表。 var query = new PrefixQuery(new Term("title", "动")); 查询结果如下,可以看到 「动物」

lgq2016的博客 695

Linux驱动学习--HCI框架及源码分析

目录 一、引言 二、HCI框架 三、pcm_read简要分析 一、引言 最近一直在分析蓝牙驱动,发现在底层驱动与上层协议栈间还有一层HCI,主机控制层,两者之间通过HCI协议通讯 而Linux也将HCI协议抽象层了一个框架。 为上层提供一个统一的接口,让上层协议不依赖于具体硬件的实现。HCI在硬件中的固件与HCI在主机端的驱动通信方式有多种,比如像 UART、USB和PC Card等等。hci_core.c相当于一个框架,用于把各种具体通信方式绞合起来,并提供一些公共函数的实现。 今天就来简单的分析一下 二

文艺小少年的博客 2003

lucene正则查询_Lucene 高阶查询技巧

在前面的章节中我们使用了最基础的关键词查询 TermQuery 和 复合查询 BooleanQuery,本节我们来尝试 Lucene 内置的其它高级查询功能。字符串前缀查询 PrefixQuery同关系数据库索引一样,得益于 FST 的前缀共享属性,Lucene 也支持前缀查询。它会将共享同样前缀的一系列关键词都找出来,然后再 merge 它们的文档列表。var query = new Prefi...

weixin_42393342的博客 864

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

couchdb-lucene查询语法详解:掌握Lucene查询的5个关键技巧

couchdb-lucene是一款能够为CouchDB文档提供强大全文搜索能力的工具,它将Lucene的高效检索功能与CouchDB的灵活文档存储完美结合。本文将分享5个关键技巧,帮助你轻松掌握couchdb-lucene的查询语法,提升文档搜索效率。 ## 1. 基础查询:快速定位关键内容 基础查询是使用couchdb-lucene的第一步,你可以直接输入关键词进行搜索。例如,搜索包含"ex

gitblog_00835的博客 958

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

Lucene 高阶查询的六脉神剑 —— QueryParser

上篇我们介绍了 Lucene 多样的查询模式,每一种都是相互独立的用来解决特定查询目标的 Query 对象。本节我们要将这些查询模式使用 QueryParser 组合起来进行合并查询 —— 用一个文本字符串表达式来表示所有的查询模式。 QueryParser 的基本作用是将一个满足特定语法的字符串转换成相应的查询对象。我们可以不再需要使用组合对象的方式来手动构造复合逻辑查询,而是通过一个单行字符串就可以完成原先需要数行代码才能完成的查询功能。 关键词查询 TermQuery 与组合查询 BooleanQ

lgq2016的博客 1137

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

lucene 按照匹配度排序_Lucene 高阶查询技巧

在前面的章节中我们使用了最基础的关键词查询 TermQuery 和 复合查询 BooleanQuery,本节我们来尝试 Lucene 内置的其它高级查询功能。字符串前缀查询 PrefixQuery同关系数据库索引一样,得益于 FST 的前缀共享属性,Lucene 也支持前缀查询。它会将共享同样前缀的一系列关键词都找出来,然后再 merge 它们的文档列表。var 查询结果如下,可以看到 「动物」和...

weixin_39943678的博客 406

Lucene 高阶查询--QueryParser

QueryParser 的基本作用是将一个满足特定语法的字符串转换成相应的查询对象。我们可以不再需要使用组合对象的方式来手动构造复合逻辑查询,而是通过一个单行字符串就可以完成原先需要数行代码才能完成的查询功能。

小码农吗 379

Lucene之分页查询

Lucene之分页查询

小好的博客 2351

lucene 查询示例_Lucene查询(搜索)语法示例

lucene 查询示例 本文是我们名为“ Apache Lucene基础知识 ”的学院课程的一部分。 在本课程中,您将了解Lucene。 您将了解为什么这样的库很重要,然后了解Lucene中搜索的工作方式。 此外,您将学习如何将Lucene Search集成到您自己的应用程序中,以提供强大的搜索功能。 在这里查看 ! 目录 1.简介 2.查询类 2.1术语查询 ...

最佳 Java 编程 926

Lucene的多域查询、结果中查询、查询结果分页、高亮查询结果和结果评分

1.针对多个域的一次性查询 1.1.三种方案         使用lucene构造搜索引擎的时候,如果要针对多个域进行一次性查询,一般来说有三种方法:     第一种实现方法是创建多值的全包含域的文本进行索引,这个方案最简单。但是这个防范有个缺点:你不能直接对每个域的加权进行控制。     第二种方法是使用MultiFieldQueryParser,它是QueryParser的子类,它会在

Huhui's Blog 7569

lucene中document的所有id_Lucene 高阶查询的六脉神剑 —— QueryParser

上篇我们介绍了 Lucene 多样的查询模式,每一种都是相互独立的用来解决特定查询目标的 Query 对象。本节我们要将这些查询模式使用 QueryParser 组合起来进行合并查询 —— 用一个文本字符串表达式来表示所有的查询模式。QueryParser 的基本作用是将一个满足特定语法的字符串转换成相应的查询对象。我们可以不再需要使用组合对象的方式来手动构造复合逻辑查询,而是通过一个单...

weixin_33255691的博客 183

lucene使用PhraseQuery设置slop进行短语查询

所谓PhraseQuery,就是通过短语来检索。 例如现在有一个字符串,“the quick brown fox jumped over the lazy dog”,我们不知道其中的精确的短语,我们仍然可以通过短语“quick”、"fox"来查找文档。slop就是从一个词到另一个词的距离。 下面是一个模仿lucene in action 第二版的一个例子。 package com.cn;

yyunix的专栏 3024
上一篇: 面试题:请解释一下什么是虚拟内存?
下一篇: Lucene 高阶查询的六脉神剑 —— QueryParser
码洞
博客等级 码龄10年 41粉丝 137原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值