微服务篇-深入了解 Elasticsearch DSL 查询和 RestClient 查询、数据聚合(Bucket 聚合、带条件聚合、Metric 聚合)

news/2024/12/15 12:42:28/

🔥博客主页: 【小扳_-CSDN博客】
❤感谢大家点赞👍收藏⭐评论✍

文章目录

        1.0 DSL 查询

        1.1 叶子查询

        1.1.1 全文检索查询

        1.1.2 精确查询

        1.2 复合查询

        1.2.1 bool 查询

        1.3 排序

        1.4 分页

        1.4.1 深度分页

        1.5 高亮

        1.5.1 实现高亮

        2.0 RestClient 查询

        2.1 叶子查询

        2.2 复合查询

        2.3 排序和分页

        2.4 高亮

        3.0 数据聚合

        3.1 DSL 实现聚合

        3.1.1 Bucket 聚合

        3.1.2 带条件聚合

        3.1.3 Metric 聚合

        3.2 RestClient 实现聚合


        1.0 DSL 查询

        导入了大量数据到 Elasticsearch 中,实现了数据的存储。不过查询数据时依然采用的是根

据 id 查询,而非模糊搜索。

        所以来研究下 Elasticsearch 的数据搜索功能。Elasticsearch 提供了基于 JSON 的 DSL

(Domain Specific Language)语句来定义查询条件,其 JavaAPI 就是在组织 DSL 条件。

举个例子:

        查询所有数据:

        以最简单的无条件查询为例,无条件查询的类型是:match_all,因此其查询语句如下:

GET /xbs/_search
{"query": {"match_all": {}}
}

查询结果:

        发现虽然是 match_all,但是响应结果中并不会包含索引库中的所有文档,而是仅有 10 条。

这是因为处于安全考虑,elasticsearch 设置了默认的查询页数。

Elasticsearch 的查询可以分为两大类:

        1)叶子查询(Leaf query clauses):一般是在特定的字段里查询特定值,属于简单查询,

很少单独使用。

        2)复合查询(Compound query clauses):以逻辑方式组合多个叶子查询或者更改叶子查

询的行为方式。

        1.1 叶子查询

        叶子查询的类型也可以做进一步细分,详情可以查看官方文档:Query and filter context | Elasticsearch Guide [7.12] | Elastic

这里列举一些常见的,例如:

        1)全文检索查询(Full Text Queries):利用分词器对用户输入搜索条件先分词,得到词

条,然后再利用倒排索引搜索词条。例如:

        match、multi_match

        2)精确查询(Term-level queries):不对用户输入搜索条件分词,根据字段内容精确值匹

配。但只能查找 keyword、数值、日期、boolean 类型的字段。例如:

        ids、term、range

        1.1.1 全文检索查询

        以全文检索中的 match 为例,语法如下:

GET /xbs/_search
{"query": {"match": {"msg": "天才"}}
}

执行结果:

        与 match 类似的还有 multi_match,区别在于可以同时对多个字段搜索,而且多个字段都要

满足,语法示例:

GET /{索引库名}/_search
{"query": {"multi_match": {"query": "搜索条件","fields": ["字段1", "字段2"]}}
}

        1.1.2 精确查询

        精确查询,英文是 Term-level query,顾名思义,词条级别的查询。也就是说不会对用户输

入的搜索条件再分词,而是作为一个词条,与搜索的字段内容精确值匹配。因此推荐查找

keyword、数值、日期、boolean 类型的字段。

        以 term 查询为例,其语法如下:

GET /xbs/_search
{"query": {"term": {"name": {"value": "唐唐"}}}
}

执行结果:

再来看下 range 查询,语法如下:

GET /xbs/_search
{"query": {"range": {"age": {"gte": 20,"lte": 30}}}
}

执行结果:

range 是范围查询,对于范围筛选的关键字有:

        1)gte:大于等于

        2)gt:大于

        3)lte:小于等于

        4)lt:小于

        1.2 复合查询

        复合查询大致可以分为两类:

        第一类:基于逻辑运算组合叶子查询,实现组合条件,例如:bool

        第二类:基于某种算法修改查询时的文档相关性算分,从而改变文档排名。例如:

        function_score、dis_max

其它复合查询及相关语法可以参考官方文档:Compound queries | Elasticsearch Guide [7.12] | Elastic

        1.2.1 bool 查询

        bool 查询,即布尔查询,就是利用逻辑运算来组合一个或多个查询子句的组合。bool 查询支

持的逻辑运算有:

        1)must:必须匹配每个子查询,类似“与”。

        2)should:选择性匹配子查询,类似“或”。

        3)must_not:必须不匹配,不参与算分,类似“非”。

        4)filter:必须匹配,不参与算分。

bool 查询的语法如下:

GET /xbs/_search
{"query": {"bool": {"must": [{"match": {"msg": "天才"}}],"filter": [{"range": {"age": {"gte": 20,"lte": 30}}}]}}
}

执行结果:

        出于性能考虑,与搜索关键字无关的查询尽量采用 must_not 或 filter 逻辑运算,避免参与相

关性算分。

        1.3 排序

        elasticsearch 默认是根据相关度算分(_score)来排序,但是也支持自定义方式对搜索结果

排序。不过分词字段无法排序,能参与排序字段类型有:keyword 类型、数值类型、地理坐标类

型、日期类型等。

语法说明:

GET /indexName/_search
{"query": {"match_all": {}},"sort": [{"排序字段": {"order": "排序方式asc和desc"}}]
}

举个例子:

GET /xbs/_search
{"query": {"match_all": {}},"sort": [{"age": {"order": "desc"}}]
}

执行结果:

        1.4 分页

        elasticsearch 默认情况下只返回 top10 的数据。而如果要查询更多数据就需要修改分页参数

了。

        elasticsearch 中通过修改 from、size 参数来控制要返回的分页结果:

        1)from:从第几个文档开始

        2)size:总共查询几个文档

        简单来说,类似于 mysql 中的 limit ?, ?

语法如下:

GET /items/_search
{"query": {"match_all": {}},"from": 0, // 分页开始的位置,默认为0"size": 10,  // 每页文档数量,默认10"sort": [{"price": {"order": "desc"}}]
}

举个例子:

GET /xbs/_search
{"query": {"match_all": {}},"from": 0,"size": 2
}

执行结果:

        1.4.1 深度分页

        elasticsearch 的数据一般会采用分片存储,也就是把一个索引中的数据分成 N 份,存储到不

同节点上。这种存储方式比较有利于数据扩展,但给分页带来了一些麻烦。

        比如一个索引库中有 100000 条数据,分别存储到 4 个分片,每个分片 25000 条数据。现在

每页查询 10 条,查询第 99 页。那么分页查询的条件如下:

GET /items/_search
{"from": 990, // 从第990条开始查询"size": 10, // 每页查询10条"sort": [{"price": "asc"}]
}

从语句来分析,要查询第 990~1000 名的数据。

        从实现思路来分析,肯定是将所有数据排序,找出前 1000 名,截取其中的 990~1000 的部

分。但问题来了,我们如何才能找到所有数据中的前 1000 名呢?

        要知道每一片的数据都不一样,第 1 片上的第 900~1000,在另 1 个节点上并不一定依然是

900~1000 名。所以我们只能在每一个分片上都找出排名前 1000 的数据,然后汇总到一起,重新

排序,才能找出整个索引库中真正的前 1000 名,此时截取 990~1000 的数据即可。

如图:

        试想一下,假如我们现在要查询的是第 999 页数据呢,是不是要找第 9990~10000 的数据,

那岂不是需要把每个分片中的前 10000 名数据都查询出来,汇总在一起,在内存中排序?如果查

询的分页深度更深呢,需要一次检索的数据岂不是更多?

        由此可知,当查询分页深度较大时,汇总数据过多,对内存和 CPU 会产生非常大的压力。

        因此 elasticsearch 会禁止 from+ size 超过 10000 的请求。

针对深度分页,elasticsearch 提供了两种解决方案:

        1)search after:分页时需要排序,原理是从上一次的排序值开始,查询下一页数据。官方

推荐使用的方式。

        2)scroll:原理将排序后的文档 id 形成快照,保存下来,基于快照做分页。官方已经不推荐

使用。

小结:

        大多数情况下,采用普通分页就可以了。查看百度、京东等网站,会发现其分页都有限

制。例如百度最多支持 77 页,每页不足 20 条。京东最多 100 页,每页最多 60 条。

        因此,一般采用限制分页深度的方式即可,无需实现深度分页。

        1.5 高亮

        什么是高亮显示呢?

在百度,京东搜索时,关键字会变成红色,比较醒目,这叫高亮显示:

观察页面源码,会发现两件事情:

        1)高亮词条都被加了 <em> 标签

        2)<em> 标签都添加了红色样式

        css 样式肯定是前端实现页面的时候写好的,但是前端编写页面的时候是不知道页面要展示

什么数据的,不可能给数据加标签。而服务端实现搜索功能,要是有 elasticsearch 做分词搜索,

是知道哪些词条需要高亮的。

        因此词条的高亮标签肯定是由服务端提供数据的时候已经加上的。

因此实现高亮的思路就是:

        1)用户输入搜索关键字搜索数据。

        2)服务端根据搜索关键字到 elasticsearch 搜索,并给搜索结果中的关键字词条添加 html

标签。

        3)前端提前给约定好的 html 标签添加 CSS 样式。

        1.5.1 实现高亮

事实上 elasticsearch 已经提供了给搜索关键字加标签的语法,无需自己编码。

基本语法如下:

GET /{索引库名}/_search
{"query": {"match": {"搜索字段": "搜索关键字"}},"highlight": {"fields": {"高亮字段名称": {"pre_tags": "<em>","post_tags": "</em>"}}}
}

举个例子:

GET /xbs/_search
{"query": {"match": {"msg": "天才"}},"highlight": {"fields": {"msg": {"pre_tags": "<em>","post_tags": "</em>"}}}
}

执行结果:

        1)搜索必须有查询条件,而且是全文检索类型的查询条件,例如 match 。

        2)参与高亮的字段必须是 text 类型的字段

        3)默认情况下参与高亮的字段要与搜索字段一致,除非添加:required_field_match=false

        2.0 RestClient 查询

        文档的查询依然使用 RestHighLevelClient 对象,查询的基本步骤如下:

        1)创建 request 对象,这次是搜索,所以是 SearchRequest

        2)准备请求参数,也就是查询 DSL 对应的 JSON 参数

        3)发起请求

        4)解析响应,响应结果相对复杂,需要逐层解析

        match_all 查询为例,其 DSL 和 JavaAPI 的对比如图:

代码解读:

        第一步,创建 SearchRequest 对象,指定索引库名 

        第二步,利用 request.source() 构建 DSL,DSL 中可以包含查询、分页、排序、高亮等 

        query():代表查询条件,利用 QueryBuilders.matchAllQuery() 构建一个 match_all 查询

的 DSL

        第三步,利用 client.search() 发送请求,得到响应 这里关键的 API 有两个,一个是

request.source(),它构建的就是 DSL 中的完整 JSON 参数。其中包含了 query、sort、from、

size、highlight 等所有功能:

        另一个是 QueryBuilders,其中包含了各种叶子查询、复合查询等:

        2.1 叶子查询

        所有的查询条件都是由 QueryBuilders 来构建的,叶子查询也不例外。因此整套代码中变化

的部分仅仅是 query 条件构造的方式,其它不动。

        先建立连接:

代码如下:

public class ItemApplicationText {private RestHighLevelClient client;@BeforeEach//先初始化,连接到es服务端public void init() {this.client = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://113.45.166.112:9200")));}@Testpublic void test() throws Exception {if (client != null){System.out.println("连接成功:" + client);}else {System.out.println("连接失败");}}@AfterEach//程序结束后,关闭连接public void close() throws Exception {this.client.close();}}

        1)match 查询:

具体代码如下:

    @Test//叶子查询public void leafQuery() throws IOException {//1.创建Request对象SearchRequest request = new SearchRequest("xbs");//2.准备DSLrequest.source().query(QueryBuilders.matchQuery("msg", "天才"));//3.发送请求SearchResponse response = client.search(request, RequestOptions.DEFAULT);//4,解析结果SearchHits hits = response.getHits();//获取到总记录数long count = hits.getTotalHits().value;SearchHit[] hitsHits = hits.getHits();for (SearchHit hitsHit : hitsHits) {String sourceAsString = hitsHit.getSourceAsString();System.out.println(sourceAsString);}System.out.println("总记录数:"+count);}

执行结果:

        2)range 查询:

代码如下:

    @Test//范围查询public void rangeQuery() throws IOException {//1.创建request对象SearchRequest request = new SearchRequest("xbs");//2.准备DSLrequest.source().query(QueryBuilders.rangeQuery("age").gte(20).lte(30));//3.发送请求SearchResponse search = client.search(request, RequestOptions.DEFAULT);//4.解析结果SearchHits hits = search.getHits();//获取到总记录数long count = hits.getTotalHits().value;SearchHit[] hitsHits = hits.getHits();for (SearchHit hitsHit : hitsHits) {String sourceAsString = hitsHit.getSourceAsString();System.out.println(sourceAsString);}System.out.println("总记录数:"+count);}

执行结果:

        3)term 查询

代码如下:

    @Test//精确查询public void termQuery() throws IOException {//1.创建Request对象SearchRequest request = new SearchRequest("xbs");//2.准备DSLrequest.source().query(QueryBuilders.termQuery("name","唐唐"));//3,发送请求SearchResponse search = client.search(request, RequestOptions.DEFAULT);//4,解析结果SearchHits hits = search.getHits();//获取总数记录long value = hits.getTotalHits().value;//获取具体内容SearchHit[] hitsHits = hits.getHits();for (SearchHit hit : hitsHits) {String sourceAsString = hit.getSourceAsString();System.out.println(sourceAsString);}System.out.println("总记录数:"+value);}

执行结果:

        2.2 复合查询

        复合查询也是由 QueryBuilders 来构建,以 bool 查询为例,DSL 和 JavaAPI 的对比如图:

举个例子:

    @Test//复合查询public void compoundQuery() throws IOException {//1.创建request对象SearchRequest request = new SearchRequest("xbs");//2.准备DSLBoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();boolQueryBuilder.must(QueryBuilders.matchQuery("msg","天才"));boolQueryBuilder.filter(QueryBuilders.rangeQuery("age").gte(20).lte(30));request.source().query(boolQueryBuilder);//3.发送请求SearchResponse search = client.search(request, RequestOptions.DEFAULT);//4.解析结果SearchHits hits = search.getHits();//获取记录总数long value = hits.getTotalHits().value;//获取具体内容SearchHit[] hitsHits = hits.getHits();for (SearchHit hit : hitsHits) {String sourceAsString = hit.getSourceAsString();User bean = JSONUtil.toBean(sourceAsString, User.class);System.out.println(bean);}System.out.println("总记录数:"+value);}

执行结果:

        2.3 排序和分页

        之前说过,requeset.source() 就是整个请求 JSON 参数,所以排序、分页都是基于这个来

设置,其 DSL 和 JavaAPI 的对比如下:

举个例子:

    @Test//分页查询public void pageQuery() throws IOException {//1.创建request对象SearchRequest request = new SearchRequest("xbs");//2.准备DSL//2.1查询所有文档request.source().query(QueryBuilders.matchAllQuery());//2.2分页查询,从索引0开始,查询一共2条request.source().from(0).size(2);//3.0发送请求SearchResponse search = client.search(request, RequestOptions.DEFAULT);//4.解析数据SearchHits hits = search.getHits();//获取数据总数long value = hits.getTotalHits().value;//获取具体内容SearchHit[] hitsHits = hits.getHits();for (SearchHit hitsHit : hitsHits) {String sourceAsString = hitsHit.getSourceAsString();User bean = JSONUtil.toBean(sourceAsString, User.class);System.out.println(bean);}System.out.println("总记录数:"+value);}

执行结果:

        总数据一共有三条,通过分页查询从第一页开始,一页中指的数量为两条,所以查询出来的

数据只有两条。

        2.4 高亮

        高亮查询与前面的查询有两点不同:

        1)条件同样是在 request.source() 中指定,只不过高亮条件要基于 HighlightBuilder 来构

造。

        2)高亮响应结果与搜索的文档结果不在一起,需要单独解析。

首先来看高亮条件构造,其 DSL 和 JavaAPI 的对比如图:

举个例子:

    @Test//高亮展示public void highlightQuery() throws IOException {//1.创建request对象SearchRequest request = new SearchRequest("xbs");//2.准备DSL//2.1 通过全文查询request.source().query(QueryBuilders.matchQuery("msg","天才"));//2.2 添加高亮request.source().highlighter(SearchSourceBuilder.highlight().field("msg").preTags("<em>").postTags("</em>"));//3.发送请求SearchResponse search = client.search(request, RequestOptions.DEFAULT);//4.解析结果SearchHits hits = search.getHits();//获取查询的总条数long value = hits.getTotalHits().value;//获取高亮数据SearchHit[] hitsHits = hits.getHits();for (SearchHit hit : hitsHits) {//原始数据,不带高亮展示String sourceAsString = hit.getSourceAsString();User u = JSONUtil.toBean(sourceAsString, User.class);System.out.println("用户对象:"+u);//接下来获取展示的高亮数据Map<String, HighlightField> highlightFields = hit.getHighlightFields();if (CollUtils.isNotEmpty(highlightFields)){HighlightField msg = highlightFields.get("msg");if (msg != null){Text[] fragments = msg.getFragments();//拼接String s = "";for (Text fragment : fragments) {s += fragment.string();}u.setMsg(s);}}//最后再来展示高亮数据System.out.println("添加了高亮的用户"+u);}}

执行结果:

        3.0 数据聚合

        聚合(aggregations)可以让我们极其方便的实现对数据的统计、分析、运算。例如:

        什么品牌的手机最受欢迎?

        这些手机的平均价格、最高价格、最低价格?

        这些手机每月的销售情况如何?

        实现这些统计功能的比数据库的sql要方便的多,而且查询速度非常快,可以实现近实时搜索

效果。

官方文档:

Aggregations | Elasticsearch Guide [7.12] | Elastic

聚合常见的有三类:

1)桶(Bucket)聚合:用来对文档做分组 

        TermAggregation:按照文档字段值分组,例如按照品牌值分组、按照国家分组

        Date Histogram:按照日期阶梯分组,例如一周为一组,或者一月为一组

2)度量(Metric)聚合:用以计算一些值,比如:最大值、最小值、平均值等 

        Avg:求平均值

        Max:求最大值

        Min:求最小值

        Stats:同时求max、min、avg、sum等

3)管道(pipeline)聚合:其它聚合的结果为基础做进一步运算 

        3.1 DSL 实现聚合

        介绍三种聚合方式:Bucket 聚合、带条件聚合、Metric 聚合

        3.1.1 Bucket 聚合

基本语法如下:

GET /items/_search
{"size": 0, "aggs": {"category_agg": {"terms": {"field": "category","size": 20}}}
}

语法说明:

        size:设置 size 为 0,就是每页查 0 条,则结果中就不包含文档,只包含聚合。

        aggs:定义聚合。

        category_agg:聚合名称,自定义,但不能重复。

        terms:聚合的类型,按分类聚合,所以用 term 。

        field:参与聚合的字段名称。

        size:希望返回的聚合结果的最大数量。
 

举个例子:

GET /xbs/_search
{"size": 0,"aggs": {"category_gender": {"terms": {"field": "gender","size": 10}}}
}

        根据性别进行分类。

执行结果:

        3.1.2 带条件聚合

        默认情况下,Bucket 聚合是对索引库的所有文档做聚合。

        但真实场景下,用户会输入搜索条件,因此聚合必须是对搜索结果聚合。那么聚合必须添加

限定条件。

举个例子:

GET /xbs/_search
{"query": {"range": {"age": {"gte": 10,"lte": 30}}},"aggs": {"category_gender": {"terms": {"field": "gender","size": 10}}}
}

        首先对年龄做了限制,只查询 10 到 30 岁的用户,紧接着再进行根据性别进行分组。

执行结果:

        3.1.3 Metric 聚合

        在之前使用桶聚合将用户根据性别进行分组,那么分组之后的结果进行 Metric 聚合,简单来

说就是对用户获取最小值、最大值、平均值运算。

举个例子:

GET /xbs/_search
{"size": 0,"aggs": {"category_gender": {"terms": {"field": "gender","size": 10},"aggs": {"avg_age": {"avg": {"field": "age"}}}}}
}

        将用户根据性别分组之后,获取分组之后的平均年龄。

执行结果:

        也可以同时获取到最大值、最小最、平均值、总和

代码如下:

GET /xbs/_search
{"size": 0,"aggs": {"category_gender": {"terms": {"field": "gender","size": 10},"aggs": {"avg_age": {"stats": {"field": "age"}}}}}
}

        只需要将类型改为 stats 。

执行结果:

小结:

        1)aggs 代表聚合,与 query 同级,此时 query 的作用是?

        限定聚合的的文档范围

        2)聚合必须的三要素:

                聚合名称

                聚合类型

                聚合字段

        3)聚合可配置属性有:

                size:指定聚合结果数量

                order:指定聚合结果排序方式

                field:指定聚合字段

        3.2 RestClient 实现聚合

        可以看到在 DSL 中,aggs 聚合条件与 query 条件是同一级别,都属于查询 JSON 参数。因

此依然是利用 request.source() 方法来设置。

不过聚合条件的要利用 AggregationBuilders 这个工具类来构造。DSL 与 JavaAPI 的语法对比

如下:

聚合结果与搜索文档同一级别,因此需要单独获取和解析。具体解析语法如下:

举个例子:

    @Test//数据聚合public void aggregation() throws IOException {//1.创建request对象SearchRequest request = new SearchRequest("xbs");//2.准备DSL//2.1 查询全部数据request.source().query(QueryBuilders.matchAllQuery());//2.2 聚合查询request.source().aggregation(AggregationBuilders.terms("category_gender").field("gender").size(10));//3.发送请求SearchResponse search = client.search(request, RequestOptions.DEFAULT);//4.解析结果Aggregations aggregations = search.getAggregations();Terms categoryGender = aggregations.get("category_gender");List<? extends Terms.Bucket> buckets = categoryGender.getBuckets();for (Terms.Bucket bucket : buckets) {String keyAsString = bucket.getKeyAsString();System.out.println("keyAsString = " + keyAsString+":"+bucket.getDocCount());}}

执行结果:

        以上就是桶聚合最简单的写法。

        接下来 Metric 聚合:

代码如下:

    @Test//Metric 聚合public void metricAggregation() throws IOException {//1.创建request对象SearchRequest request = new SearchRequest("xbs");//2.准备DSLrequest.source().query(QueryBuilders.matchAllQuery());request.source().aggregation(AggregationBuilders.terms("category_gender").field("gender").size(10).subAggregation(AggregationBuilders.stats("age_stats").field("age")));//3.发送请求SearchResponse search = client.search(request, RequestOptions.DEFAULT);//4.解析数据Aggregations aggregations = search.getAggregations();Terms categoryGender = aggregations.get("category_gender");if (categoryGender != null){List<? extends Terms.Bucket> buckets = categoryGender.getBuckets();if (CollUtils.isEmpty(buckets)){throw new RuntimeException("没有数据");}for (Terms.Bucket bucket : buckets) {//获取分组字段String keyAsString = bucket.getKeyAsString();//获取统计数long docCount = bucket.getDocCount();//获取平均数Stats ageStats = bucket.getAggregations().get("age_stats");double avg = ageStats.getAvg();System.out.println("分组字段:"+keyAsString+"分组数量:"+docCount+"平均年龄:"+avg);}}}

执行结果:


http://www.ppmy.cn/news/1555294.html

相关文章

汽车租赁系统数据库 E-R 图设计

文章目录 汽车租赁系统数据库 E-R 图设计一、实体&#xff08;Entities&#xff09;二、实体间关系&#xff08;Relationships&#xff09;三、数据表&#xff08;Tables&#xff09; 汽车租赁系统数据库 E-R 图设计 一、实体&#xff08;Entities&#xff09; 用户&#xff0…

[实战]MySQL时间多了一秒

场景 同时保存一条数据在MySQL和Redis中&#xff0c;JAVA系统中显示Redis和MySQL数据差了一秒&#xff0c;即MySQL比Redis中快了一秒。 复现 我们系统中MySQL的时间类型用的是timestamp&#xff0c;问题是一样的。 CREATE TABLE test_date (id int(11) NOT NULL AUTO_INCRE…

【C#设计模式(20)——观察者模式(Observer Pattern)】

前言 观察者模式 观察者模式&#xff1a;定义了一种对象之间的一对多依赖关系&#xff0c;消息发布者发布通知时&#xff0c;它的所有订阅者&#xff08;依赖&#xff09;对象都会自动收到通知并进行相应的更新。 代码 //抽象观察者类 public abstract class Observer {prote…

Java 小抄|解析 JSON 并提取特定层级数据

文章目录 前言环境准备依赖库 示例代码JSON 数据Java 类定义解析 JSON 数据代码解释 结论 前言 在日常开发中&#xff0c;我们经常需要从 JSON 数据中提取特定的信息。本文将介绍如何使用 Java 和 Gson 库解析 JSON 数据&#xff0c;并通过流式处理提取特定层级的数据。我们将…

有效的括号(字节面试题 最优解)

题目来源 20. 有效的括号 - 力扣&#xff08;LeetCode&#xff09; 题目描述 给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串 s &#xff0c;判断字符串是否有效。 有效字符串需满足&#xff1a; 左括号必须用相同类型的右括号…

Qt之网络监测

在Qt中&#xff0c;网络监测通常涉及到检测网络连接状态、网络延迟、带宽使用情况等。Qt提供了一些类和模块来帮助开发者实现这些功能。以下是一些常用的方法和类&#xff1a; 1. 检测网络连接状态 QtNetwork模块中的QNetworkConfigurationManager类可以用来检测设备的网络连…

ConfyUI(sd-webui)-aki-v4.9.1升级安装Torch 2.5.1-CUDA12.4【含安装包】

总结&#xff1a; 原地升级操作三步走【要有一个能正常运行的aki-v4.9.1&#xff0c;先压缩备份它】&#xff1a; 一、在绘世-高级选项-安装PyTorch时&#xff0c;找到接近并且低于N卡CUDA驱动版本的版本&#xff0c;显示安装成功&#xff1b; 二、重启绘世-高级选项-原生组件…

将PDF流使用 canvas 绘制然后转为图片展示在页面上(二)

将PDF流转为图片展示在页面上 使用 pdfjs-dist 库来渲染 PDF 页面到 canvas 上&#xff0c;然后将 canvas 转为图片 安装 pdfjs-dist 依赖 npm install pdfjs-dist 或者 yarn add pdfjs-dist创建一个组件来处理 PDF 流的加载和渲染 该组件中是一个包含 PDF 文件的 ArrayBuffer…