主键顺序影响——如何优化 ClickHouse 索引(二)

news2025/1/16 1:49:25

回顾一下上一篇文章,ClickHouse 的存储设计,从存储目录出发,讲 ClickHouse 的数据读取:

  1. 第一阶段,通过隐含的 granule 单位读取主键索引 idx 文件
  2. 通过二分搜索过滤不需要的 Granule,再关联对应的 mk2 文件,映射 Granule 和数据文件的 offset
  3. 进入第二阶段,并发解压、读取 bin 数据文件,进一步排除数据,完成读取。

一. MySQL 索引原则能挪用到 ClickHouse 吗

然而,这一切建立在查询条件为主键或主键最左前缀 (prefix) 的一部分,请注意,是最左前缀的一部分,而不是一部分。你可能想到了 MySQL 使用索引的最左前缀匹配原则,这里先给结论,虽然 ClickHouse 不是 BTree,但在这里的确也适用。

你可能还想到 MySQL 建立索引的数据区分度原则区分度高的列可以减少扫描的行。但是,ClickHouse 数据存储是按主键排序的,它的隐含读取单位是 Granule,默认情况下,8192 行只有一个主键标记,即主键是稀疏的,这时候数据区分度高还要优先考虑吗?

有关 MySQL 索引,参见 MySQL索引原理及慢查询优化-美团技术

二. 当查询条件不满足最左匹配主键时

  1. 同为高基数,主键顺序影响不大
  2. 先低后高,低基数在次级排序中效率降低
  3. 不同基数排序顺序还影响压缩效率

2.1 实验环境准备

  1. Docker 搭建 ClickHouse
docker run -d --name clickhouse-local --ulimit nofile=262144:262144 -p 9000:9000 --volume=/Users/abc/clickhouse-local/:/var/lib/clickhouse yandex/clickhouse-server

默认情况下,ClickHouse Docker 已经打开 trace log,方便查看查询执行详细日志:

# /etc/clickhouse-server/config.xml
<logger>
    <!-- 等级需为 trace -->
    <level>trace</level>
    <log>/var/log/clickhouse-server/clickhouse-server.log</log>
    <errorlog>/var/log/clickhouse-server/clickhouse-server.err.log</errorlog>
    <size>1000M</size>
    <count>10</count>
</logger>

Ref: Logger - ClickHouse Configuration

  1. 准备数据
    这里创建一个先按 UserID, 再按 URL 排序主键表,同时数据插入按照 UserID, URL, EventTime 排序
-- 建表
CREATE TABLE hits_UserID_URL
(
    `UserID` UInt32,
    `URL` String,
    `EventTime` DateTime
)
ENGINE = MergeTree
PRIMARY KEY (UserID, URL)
ORDER BY (UserID, URL, EventTime)
SETTINGS index_granularity = 8192, index_granularity_bytes = 0;

-- 导入数据
INSERT INTO hits_UserID_URL SELECT
   intHash32(c11::UInt64) AS UserID,
   c15 AS URL,
   c5 AS EventTime
FROM url('https://datasets.clickhouse.com/hits/tsv/hits_v1.tsv.xz')
WHERE URL != '';
-- 线上大表谨慎操作
OPTIMIZE TABLE hits_UserID_URL FINAL;

2.2 高基数列未命中主键情形

为了更好地查看日志,这里只过滤查询部分日志

tail -f /var/log/clickhouse-server/clickhouse-server.log | grep generic -C 5

下方的查询条件 URL 为主键的第二列

SELECT
    UserID,
    count(UserID) AS Count
FROM hits_UserID_URL
WHERE URL = 'http://public_search'
GROUP BY UserID
ORDER BY Count DESC
LIMIT 10

会看到类似输出,关键信息是使用 generic exclusion search 查询,索引 url_skipping_index 过滤了 0/273 个 Granule,1076/1083 由主键选中的标记,即仅有 7 个被过滤,1076 个需要读取。约 881 万数据被四个流并行读取,接近全表扫描。

...Key condition: (column 1 in ['http://public_search', 'http://public_search'])
...Used generic exclusion search over index for part all_10_18_2 with 1537 steps
...Index `url_skipping_index` has dropped 0/273 granules.
... Selected 1/1 parts by partition key, 1 parts by primary key, 1076/1083 marks by primary key, 1076 marks to read from 5 ranges
...Reading approx. 8814592 rows with 4 streams

当查询条件为第一列 UserID,满足最左匹配原则,此时使用二分搜索

# 此时查看日志时过滤 binary
tail -f /var/log/clickhouse-server/clickhouse-server.log | grep binary -C 5

执行:

SELECT
    URL,
    count(URL) AS cnt
FROM hits_UserID_URL
WHERE UserID = 2459550954
GROUP BY URL
ORDER BY cnt DESC
LIMIT 10

可以看到 ClickHouse 用二分搜索找到单增区间(这里的 continuous range)的标记,最终过滤了 1081 个标记,读取约 1.6 w条,过滤效率很高。

Key condition: (column 0 in [2459550954, 2459550954])
(SelectExecutor): Running binary search on index range for part all_10_18_2 (1083 marks)
...Found (LEFT) boundary mark: 610
...Found (RIGHT) boundary mark: 612
... Found continuous range in 19 steps
...Selected 1/1 parts by partition key, 1 parts by primary key, 2/1083 marks by primary key, 2 marks to read from 1 ranges
Reading 1 ranges in order from part all_10_18_2, approx. 16384 rows starting from 4997120

这个时候你可能会想,把主键顺序换过来,是不是就变快了?
验证一下:

CREATE TABLE hits_URL_UserID
(
    `UserID` UInt32,
    `URL` String,
    `EventTime` DateTime
)
ENGINE = MergeTree
PRIMARY KEY (URL, UserID)
ORDER BY (URL, UserID, EventTime)
SETTINGS index_granularity = 8192, index_granularity_bytes = 0;

数据和查询保持一致

-- 线上大表谨慎操作
INSERT INTO hits_URL_UserID(UserID, URL, EventTime) SELECT
UserID, URL, EventTime FROM hits_UserID_URL;
-- 再次提醒!线上大表谨慎操作
OPTIMIZE TABLE hits_URL_UserID FINAL;
SELECT UserID, count(UserID) AS Count FROM hits_URL_UserID WHERE URL = 'http://public_search' GROUP BY UserID ORDER BY Count DESC LIMIT 10;

再次查看日志,执行的算法由 Generic Search 换为二分搜索,使用主键过滤,需要读取 39 个标记块

... Key condition: (column 0 in ['http://public_search', 'http://public_search'])
... Running binary search on index range for part all_1_9_2 (1083 marks)
... Selected 1/1 parts by partition key, 1 parts by primary key, 39/1083 marks by primary key, 39 marks to read from 1 ranges

同样地,查询非最左的 UserID,又回到 generic exclusion search,比前一张表好一些,过滤 96 个 parts,但读取 808 万行和 880 万行实际是同一个量级

对数据唯一值计数得到基数,一个十万级别,另一个达到百万级别

SELECT
    uniq(UserID) AS uid_cardinality,
    uniq(URL) AS url_cardinality
FROM hits_URL_UserID

Query id: fc18a45d-43e1-42d5-9e34-c70601ec04a2

┌─uid_cardinality─┬─url_cardinality─┐
│          119217 │         2391374 │
└─────────────────┴─────────────────┘

ClickHouse 低基数 (LowCardinality) 类型中提到 10000 这个阈值,一个列的基数超过此值,使用此类型将变得低效,我们可以把 UserID, URL 视为高基数列。

对于高基数列,主键排序顺序对于未命中主键的搜索性能的影响不大,接近全表扫描。

LowCardinality 使用基于字典的(Dictionary Coder)无损压缩算法
Ref: LowCardinality Data Type

2.3 低基数列未命中主键

SELECT
    uniq(UserID) AS uid_cardinality,
    uniq(URL) AS url_cardinality,
    uniq(IsRobot) AS robot_cardinality
FROM hits_IsRobot_UserID_URL

┌─uid_cardinality─┬─url_cardinality─┬─robot_cardinality─┐
│          119217 │         2391374 │                 4 │
└─────────────────┴─────────────────┴───────────────────┘

根据基数大小,分别以升序排列和降序排列建表,升序排列的表,查询非最左主键的 UserID 扫描行数降了一个量级:

SELECT count()
FROM hits_URL_UserID_IsRobot
WHERE UserID = 3731416266
┌─count()─┐
│   31519 │
└─────────┘

1 rows in set. Elapsed: 0.137 sec. Processed 8.07 million rows, 32.29 MB (58.75 million rows/s., 234.99 MB/s.)

SELECT count()
FROM hits_IsRobot_UserID_URL
WHERE UserID = 3731416266

┌─count()─┐
│   31519 │
└─────────┘

1 rows in set. Elapsed: 0.029 sec. Processed 126.82 thousand rows, 507.27 KB (4.31 million rows/s., 17.23 MB/s.)

而对于低基数的 IsRobot 字段,尤其是查询的某个值出现频率低时,将更为明显。

SELECT
    IsRobot,
    count() AS cnt
FROM hits_IsRobot_UserID_URL
GROUP BY IsRobot
ORDER BY cnt DESC
LIMIT 10

┌─IsRobot─┬─────cnt─┐
│       08606048 │
│       2261083 │
│       3471 │
│       479 │
└─────────┴─────────┘

SELECT count()
FROM hits_IsRobot_UserID_URL
WHERE IsRobot = 4

┌─count()─┐
│      79 │
└─────────┘

1 rows in set. Elapsed: 0.055 sec. Processed 28.51 thousand rows, 28.51 KB (514.68 thousand rows/s., 514.68 KB/s.)

SELECT count()
FROM hits_URL_UserID_IsRobot
WHERE IsRobot = 4

┌─count()─┐
│      79 │
└─────────┘

1 rows in set. Elapsed: 0.074 sec. Processed 8.87 million rows, 8.87 MB (120.24 million rows/s., 120.24 MB/s.)

这很像典型的 Web 日志场景,碰到 API 出错,需要去查的是不常见的错误码 400、422 等,正常情况下,出现的比例比较低,如果查一次触发一次全表扫描,可能容易引发 OOM。

三. 高低基数对数据的影响

可以简化为两个

  1. 压缩效率
  2. 存储效率

3.1 数据的局部性

内存访问、程序等局部性原理,在压缩算法中有类似的表述。
压缩算法将单个数据值及其连续的计数称为运行长度编码Run length of Data),同一数据值在原始数据中连续出现的长度越长,压缩效果越好。

把某压缩算法简化,下方的例子:

  1. 压缩 LOAOWOAOO 得到 LOAOWOAO2
  2. 压缩 OOOOAALW 得到 O4A2LW

在数据局部性差的例1,数据压缩前后的大小没有差别,而例子2减少20%

前面我们已经知道,ClickHouse 的数据是按主键排列存储,主键第一列,已经排好顺序,对于数据例子 OOOOAALW 可能存储数据中是 A2LO4W,压缩效率是最好的。在查询中,也可以排除尽可能多的数据。

3.2 次级数据的排列

同等数据量,数据有序,对高基数的列而言,压缩效果提升有限,对于低基数的列,压缩效果较好。

高低基数典型的例子是同等像素拍摄一张五彩的花海和拍摄一张白纸。后者的大小是显著小于前者的。而同等基数的情况下,一张太极图和一张东京涩谷十字路口的黑白照片,可能黑白像素总体是各50%,但图片大小却相差甚远

讲述例子前,明确一下,查询条件为次级主键索引列时,使用 Generic Exclude Search 算法而不是二分搜索来排除数据块(前面我们已经提过,数据读取是两步),该算法排除前提是:两个直接后续索引标记的最左主键值与当前标记相同,否则,该块会被选中。

为什么是连续两个,因为只有一个时,这个块可能包含了其他主键值,导致次级索引不是单增,若为范围查询,需解压读取该块数据后才可以判断。

回到测试数据,假定用户 ID 是低基数的,那么,对于按 UserID, URL 排序的数据:

  1. 相同用户 ID 数据很可能覆盖多个 Granule 和 index mark
  2. 同用户 ID 的数据里,URL 升序排列。不同用户 ID 的数据,URL 顺序则无法保证。相同用户 ID 的数据块,可以排除值不等于查询 URL 的块。

例如,查询 W3,对于 U1 用户,覆盖 W1 到 W6 的 URL,那么mark0 最大值 W2 < W3,可以排除,mark2 最小值 W4 > W3, 则 mark 2 之后的 U1 块都可以排除。

最左主键为低基数

反之,假定用户 ID 是高基数的,那么,对于按 UserID,URL 排序的数据,相同用户 ID 不能覆盖多个块,同个块中 URL 不是单调递增,导致查询 URL 时有更多的块被选中。包括下图中 Granule1 和 Granule3 这两个我们认为可以直接排除的块也被选中了。

同样的查询 W3,U1 mark0 包含了 W3 范围,不能排除,U2 mark1 包含 U1,不能排除,同样地,U3、U4 也不能排除。

最左主键为高基数

因此,对于低基高基混合的数据,如果均有查询、排序需求,应当按先低基再高基的主键顺序。否则低基的列查询会容易触发全表扫描。

3.3 主键排列对数据压缩率的影响

前面提到了数据的局部性,以及排序对此值的影响。
对比高低基列排序下,对 UserID 存储(因为它是次级索引)的影响,先低后高的 hits_IsRobot_UserID_URL 表,未压缩的数据是压缩后的 28 倍,而先高后低的 hits_URL_UserID_IsRobot 则只有 3 倍,压缩效率很低。查询时解压所需的内存也会随着上升。

SELECT
    table AS Table,
    name AS Column,
    formatReadableSize(data_uncompressed_bytes) AS Uncompressed,
    formatReadableSize(data_compressed_bytes) AS Compressed,
    round(data_uncompressed_bytes / data_compressed_bytes, 0) AS Ratio
FROM system.columns
WHERE ((table = 'hits_URL_UserID_IsRobot') OR (table = 'hits_IsRobot_UserID_URL')) AND (name = 'UserID')
ORDER BY Ratio ASC

Query id: e8a8973b-b466-475d-9f54-fb15998a5a79

┌─Table───────────────────┬─Column─┬─Uncompressed─┬─Compressed─┬─Ratio─┐
│ hits_URL_UserID_IsRobot │ UserID │ 33.83 MiB    │ 11.79 MiB  │     3 │
│ hits_IsRobot_UserID_URL │ UserID │ 33.71 MiB    │ 1.19 MiB   │    28 │
└─────────────────────────┴────────┴──────────────┴────────────┴───────┘

作为最低基数的 IsRobot,则影响更大

SELECT
    table AS Table,
    name AS Column,
    formatReadableSize(data_uncompressed_bytes) AS Uncompressed,
    formatReadableSize(data_compressed_bytes) AS Compressed,
    round(data_uncompressed_bytes / data_compressed_bytes, 0) AS Ratio
FROM system.columns
WHERE ((table = 'hits_URL_UserID_IsRobot') OR (table = 'hits_IsRobot_UserID_URL')) AND (name = 'IsRobot')
ORDER BY Ratio ASC

Query id: 608474b3-6699-4055-9352-df2045c53364

┌─Table───────────────────┬─Column──┬─Uncompressed─┬─Compressed─┬─Ratio─┐
│ hits_URL_UserID_IsRobot │ IsRobot │ 8.46 MiB     │ 338.76 KiB │    26 │
│ hits_IsRobot_UserID_URL │ IsRobot │ 8.43 MiB     │ 38.49 KiB  │   224 │
└─────────────────────────┴─────────┴──────────────┴────────────┴───────┘

而高基数的 URL,如何排序,在哪个表,对存储效率的影响则很不明显

SELECT
    table AS Table,
    name AS Column,
    formatReadableSize(data_uncompressed_bytes) AS Uncompressed,
    formatReadableSize(data_compressed_bytes) AS Compressed,
    round(data_uncompressed_bytes / data_compressed_bytes, 0) AS Ratio
FROM system.columns
WHERE ((table = 'hits_URL_UserID_IsRobot') OR (table = 'hits_IsRobot_UserID_URL')) AND (name = 'URL')
ORDER BY Ratio ASC

Query id: 2b6c5e4b-5d78-4d69-8664-431c04454c1b

┌─Table───────────────────┬─Column─┬─Uncompressed─┬─Compressed─┬─Ratio─┐
│ hits_IsRobot_UserID_URL │ URL    │ 663.07 MiB   │ 183.61 MiB │     4 │
│ hits_URL_UserID_IsRobot │ URL    │ 665.62 MiB   │ 139.80 MiB │     5 │
└─────────────────────────┴────────┴──────────────┴────────────┴───────┘

SELECT
    table AS Table,
    name AS Column,
    formatReadableSize(data_uncompressed_bytes) AS Uncompressed,
    formatReadableSize(data_compressed_bytes) AS Compressed,
    round(data_uncompressed_bytes / data_compressed_bytes, 0) AS Ratio
FROM system.columns
WHERE ((table = 'hits_URL_UserID') OR (table = 'hits_UserID_URL')) AND (name = 'URL')
ORDER BY Ratio ASC

Query id: 8f42d6b7-a930-440b-9217-4e3cec638662

┌─Table───────────┬─Column─┬─Uncompressed─┬─Compressed─┬─Ratio─┐
│ hits_UserID_URL │ URL    │ 665.62 MiB   │ 174.01 MiB │     4 │
│ hits_URL_UserID │ URL    │ 665.62 MiB   │ 131.09 MiB │     5 │
└─────────────────┴────────┴──────────────┴────────────┴───────┘

3.4 多个高基数高效查询

多个高基数主键情况下,无论哪种主键顺序,查询次级主键都可能触发全表扫描。
还有什么方法优化呢?ClickHouse 并没有像 MySQL 一样提供立竿见影的二级索引。只有:

  1. 同为稀疏索引的 DataSkippingIndexes (之后展开)
  2. 创建多个主键索引。

创建多个主键索引的名字很有迷惑性,ClickHouse 还提供了三种方式,但其本质都是创建一张影子表,区别只是影子表对于客户端是否透明。即:客户端发起请求,服务端能否根据查询条件自动选择命中最左索引的表。

**注:**在 MySQL 等数据库,影子表可能是服务于表切换等过程的中间表,用后即弃,但此处是服务于真实请求的隐藏表

工作原理

  1. 创建另一张相同的表,通过物化视图同步写入,但客户端需要根据查询手动指定表
    Materialize View

  2. 通过隐藏的存储数据的物化视图同步写入,只需要在创建物化视图时使用 POPULATE 参数,ClickHouse 就会自动创建一张隐藏表,源表的写入会自动触发写入到隐藏表(包括旧数据,一般的物化视图只从创建时触发)。但客户端仍需手动指定。

-- POPULATE
CREATE MATERIALIZED VIEW mv_hits_URL_UserID
ENGINE = MergeTree()
PRIMARY KEY (URL, UserID)
ORDER BY (URL, UserID, EventTime)
POPULATE
AS SELECT * FROM hits_UserID_URL;
  1. 通过投影(Projection)创建隐藏表
    下面的语句表示创建按 URL,UserID 排序的隐藏表
ALTER TABLE hits_UserID_URL
    ADD PROJECTION prj_url_userid
    (
        SELECT *
        ORDER BY (URL, UserID)
    );

下面的语句表示立刻同步数据到隐藏表

ALTER TABLE hits_UserID_URL
    MATERIALIZE PROJECTION prj_url_userid;

创建完毕后,ClickHouse 将自动根据语句估计扫描的数据量,选择扫描数据量低的表。

多主键存储文件夹

可见,无论哪种方式,都是以空间换时间,且换的效率远不如 MySQL。这也是在选型数据库时,需要考虑的一个方面:有查询多列高基数需求时,能接受多大的空间冗余?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/93982.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

elasticsearch7.17 与minio集成,并快照备份与恢复

elasticsearch 7.6以支持 一、monio 1、部暑minio mkdir -p /data/minio/{data,config}cat > /data/minio/start.sh << EOF docker run -d \ -p 9000:9000 \ -p 9001:9001 \ --name minio \ --restartalways \ -e "MINIO_ROOT_USERadmin" \ -e "MINI…

Linux学习01-Linux基础认知

笔记来源于鸟哥的Linux私房菜&#xff08;第四版&#xff09;&#xff0c;这本书写的真的非常好。 1 简介 早期的Linux是针对386的计算来开发的&#xff0c;由于Linux只是一个操作系统&#xff0c;并不含有其他的应用程序&#xff0c;因此很多工程师在下载了Linux内核并安装&a…

【Linux】进程的程序替换(execl、execlp、execle、execvpe等替换函数)

文章目录1、进程程序替换1.1 理解进程替换原理1.2 进程相应替换函数1.3 进一步理解程序替换1、进程程序替换 父进程创建子进程的目的&#xff1a; 1.想让子进程执行父进程代码的一部分。&#xff08;子承父业&#xff09; 2.想让子进程执行一个全新的程序。   进程程序替换讨…

深圳大学数学文化赏析MOOC第一次作业答案(满分)

一、单选题 (共 40.00 分) 1. 关于归纳推理&#xff0c;以下说法错误的是 A. 归纳推理是从特殊到一般的推理。 B. 归纳推理属于发散性思维。 C. 归纳推理的结论一定是正确的。 D. 归纳推理具有创新性。 满分&#xff1a;2.00 分 得分&#xff1a;2.00 分 你的答案&a…

[MySQL]-数据库恢复工具之binlog2sql

[MySQL]-数据库恢复工具之binlog2sql 森格 | 2022年12月 本文主要介绍工具binlog的使用&#xff0c;它可以帮助我们快速解析出原始SQL、回滚SQL、去除主键的INSERT SQL等。 一、工具介绍 1.1 概述 我们可以去设想&#xff0c;当开发人员使用了delete语句误删除了某表的数据&…

微服务实用篇5-分布式搜索elasticsearch篇1

今天的主要学习任务是分布式搜索&#xff0c;首先了解elasticsearch&#xff0c;然后学习索引库的操作、文档的操作、RestAPI等。elasticsearch是非常强大的开源搜索引擎&#xff0c;可以帮助我们从海量数据中快速定位到我们需要的内容。这一篇主要学习ES的基本使用&#xff0c…

rocketmq源码-关于消费者push模式和pull模式的对比

在rocketmq中&#xff0c;对于消费者而言&#xff0c;有两种模式&#xff0c;push和pull 我在没有看源码之前&#xff0c;看其他博客的时候&#xff0c;大部分的说法是&#xff1a; mq中有两种获取消息的模式&#xff0c;一种是push&#xff0c;一种是pull&#xff1b;pull这种…

技术人员必备的便携版卸载清理工具 - Uninstall Tool 3 便携版直接U盘中启动软件,专为单个用户在多台电脑上使用而设计的。

Uninstall Tool &#xff0c;快速、强大的卸载清理软件&#xff0c;可完全彻底删除已安装软件。彻底删除不需要的应用程序&#xff0c;实时安装监视器。控制在系统启动时运行的应用。有效&#xff0c;强大的应用程序&#xff0c;具有简单而直观的界面。 删除不需要的软件&#…

马斯克都不懂的 GraphQL,API 网关又能对其如何理解?

作者&#xff0c;罗泽轩 上个月马斯克评论 Twitter App 滥用 RPC 后&#xff0c;与一些 Twitter 的技术主管发生了矛盾 —— 直言马斯克不懂技术。那这个马斯克都不懂的 GraphQL 到底是什么&#xff1f; 什么是 GraphQL&#xff1f;它有多流行&#xff1f; GraphQL 是一套由 F…

【javascript】值,类型,变量,函数,noi103题目,if语句,调试

❤️ Author&#xff1a; 老九 ☕️ 个人博客&#xff1a;老九的CSDN博客 &#x1f64f; 个人名言&#xff1a;不可控之事 乐观面对 &#x1f60d; 系列专栏&#xff1a; 文章目录基本类型NumberStringboolean例子变量求二元一次方程函数1.3题目if例子1.4题目调试下面我们会随着…

SpringMVC:SpringMVC五种类型参数传递(4)

请求参数1. 环境准备2. 参数传递2.1 GET请求2.1.1 GET发送一个参数2.1.2 GET发送多个参数2.1.3 GET请求中文乱码2.2 POST请求2.2.1 POST发送一个参数2.2.2 POST发送多个参数2.2.3 POST请求中文乱码问题3. 五种类型参数传递3.1 普通参数3.2 POJO类型参数3.3 嵌套POJO类型参数3.4…

HashMap(二)扩容

想要了解HashMap的扩容机制你要有这两个问题 1、什么时候才需要扩容 2、HashMap的扩容是什么 1、什么时候才需要扩容 当HashMap中的元素个数超过数组大小&#xff08;数组长度&#xff09;* loadFactor(负载因子)时&#xff0c;就会进行数组扩容&#xff0c;loadFactor的默认值…

Pytest框架运行常用参数解析

-s&#xff1a;表示输出调试信息&#xff0c;用于显示测试函数中print()打印的信息。我们在用例中加上一句 print(driver.title)&#xff0c;我们再运行一下我们的用例看看&#xff0c;调试信息输出-v&#xff1a;未加前只打印模块名&#xff0c;加v后--verbose打印类名、模块名…

Java中的八大包装类(Wrapper)

目录 一、八大包装类 1、八大包装类的体系图&#xff1a; 二、装箱和拆箱 三、intValue()和valueOf()方法 1、intValue() 2、valueOf() 四、包装类型和String类型的相互转换 五、包装类的常用方法&#xff08;以Integer和Character为例&#xff09; 一、八大包装类 包装…

Scala环境搭建

目录1&#xff09;安装步骤2&#xff09;测试3&#xff09;IDEA安装Scala 插件1&#xff09;安装步骤 1.首先确保 JDK1.8 安装成功 2.下载对应的 Scala 安装文件 scala-2.x.zip 3.解压 scala-2.12.11.zip&#xff0c;我这里解压到 F:\software 4.配置 Scala 的环境变量 …

使用Python和GDAL处理遥感影像数据超详细教程

提示&#xff1a;文章末尾有强化学习代码资源 : ) 前言 在本教程中&#xff0c;我们将学习使用 Python 和地理空间数据抽象库 GDAL 自动处理栅格数据的基本技术。 栅格文件通常用于存储地形模型和遥感数据及其衍生产品&#xff0c;例如植被指数和其他环境数据集。 栅格文件往往…

windows 连接蓝牙耳机失败 解决方法

windows 连接蓝牙耳机失败 解决方法 如果我们在windows7或windows10电脑中连接蓝牙却出现了连接失败的状况&#xff0c;这要怎么办呢&#xff0c;可能是我们没有打开电脑的蓝牙功能&#xff0c;这时我们点击打开蓝牙网络的属性&#xff0c;勾选Bluetooth设置的选项即可&#x…

安卓某通讯协议环境算法浅谈

所有的tlv组包都在 oicq.wlogin_sdk.tlv_type加密算法可以hook oicq.wlogin_sdk.tools Tlv144 是由5个tlv组成 然后用TGTkey进行 TEA加密 tlv_109 AndroidIDtlv_52d 系统内核信息tlv_124 平台网络信息tlv_128 手机设备信息tlv_16e 手机品牌TLV544 是设备id&#xff0…

MySQL-复合查询

文章目录复合查询基础查询多表查询自连接子查询单行子查询多行子查询多列子查询合并查询uion会自动去重union all就是不去重union all就是不去重复合查询 基础查询 查询工资高于500或者岗位为MANAGER的员工&#xff0c;同时名字首字母是J select * from emp where (sal>500…

ADI Blackfin DSP处理器-BF533的开发详解54:CVBS输出(含源码)

硬件准备 ADSP-EDU-BF533&#xff1a;BF533开发板 AD-HP530ICE&#xff1a;ADI DSP仿真器 软件准备 Visual DSP软件 硬件链接 CVBS OUT 视频输出 硬件实现原理 CVBS_OUT 子卡板连接在 ADSP-EDU-BF53x 开发板的扩展端口 PORT3 和 PORT4 上&#xff0c;板卡插入时&#xff0…