当前位置: 首页 > news >正文

Redis容量评估模型

计算Redis容量,并不只是仅仅计算key占多少字节,value占多少字节,因为Redis为了维护自身的数据结构,也会占用部分内存,本文章简单介绍每种数据类型(StringHashSetZSetList)占用内存量,供做Redis容量评估时使用。当然,大多数情况下,key和value就是主要占用,能解大部分问题

在看这里之前,可以先看一下底层 - 数据结构 这篇文章

jemalloc内存分配规则

jemalloc是一种通用的内存管理方法,着重于减少内存碎片和支持可伸缩的并发性,做redis容量评估前必须对jemalloc的内存分配规则有一定了解。

jemalloc基于申请内存的大小把内存分配分为三个等级:small,large,huge:

  • Small Object 的size以8字节,16字节,32字节等分隔开,小于页大小;
  • Large Object 的size以分页为单位,等差间隔排列,小于chunk的大小;
  • Huge Object 的大小是chunk大小的整数倍。

对于64位系统,一般chunk大小为4M,页大小为4K,内存分配的具体规则如下:

jemalloc在分配内存块时会分配大于实际值的2^n的值,例如实际值时6字节,那么会分配8字节

数据类型 占用量
dicEntry 主要包括3个指针,key、value、哈希冲突时下个指针,耗费容量为8*3=24字节,jemalloc会分配32字节的内存块
dict结构 88字节,jemalloc会分配 96 字节的内存块
redisObject type(4bit)、encoding(4bit)、lru(24bit)、int(8byte)、ptr指针(8byte)。因此redisObject结构占用(4+4+24)/8 +4+8 = 16字节。
key_SDS key的长度 + 9,jemalloc分配 >= 该值的2^n的值
val_SDS value的长度 + 9,jemalloc分配 >= 该值的2^n的值
key的个数 所有的key的个数
bucket个数 大于key的个数的2^n次方,例如key个数是2000,那么bucket=2048
指针大小 8 byte

SDS中的主要包括两个表示长度int占用大小为8字节,redis中字符串还用“/0”表示结束占用1字节,所以 sds占用大小为9字节 + 数据长度

dict结构 这里会分配96 字节的内存块?为什么不是128?

内存划分

Redis内存占用主要可以划分为如下几个部分:

  • 数据:Redis数据占用内存dataset.bytes包括key-value占用内存、dicEntry占用内存、SDS占用内存等。

    数据所占内存 = 当前所占总内存total.allocated - 额外内存overhead.total

  • 初始化内存:redis启动初始化时使用的内存startup.allocated,属于额外内存overhead.total的一部分。

  • 主从复制内存:用于主从复制,属于额外内存一部分。

  • 缓冲区内存:缓冲内存包括客户端缓冲区、复制积压缓冲区、AOF缓冲区等;其中,客户端缓冲存储客户端连接的输入输出缓冲;复制积压缓冲用于部分复制功能;AOF缓冲区用于在进行AOF重写时,保存最近的写入命令。在了解相应功能之前,不需要知道这些缓冲的细节;这部分内存由jemalloc分配,因此会统计在used_memory中。

  • 内存碎片:内存碎片是Redis在分配、回收物理内存过程中产生的。例如,如果对数据的更改频繁,而且数据之间的大小相差很大,可能导致redis释放的空间在物理内存中并没有释放,但redis又无法有效利用,这就形成了内存碎片。

    内存碎片率 = Redis进程占用内存 / 当前所占内存total.allocated

    内存碎片涉及到内存碎片率fragmentation,该值对于查看内存是否够用比较重要:

    • 该值一般>1,数值越大,说明内存碎片越多。
    • 如果<1,说明Redis占用了虚拟内存,而虚拟内存是基于磁盘的,速度会变慢,所以如果<1,就需要特别注意是否是内存不足了。
    • 一般来说,mem_fragmentation_ratio在1.03左右是比较健康的状态(对于jemalloc来说);

redis数据内存容量评估

redis容量评估模型根据key类型而有所不同。

string

一个简单的set命令最终会产生4个消耗内存的结构,中间free掉的不考虑:

  • 1个dictEntry结构,24字节,负责保存具体的键值对;
  • 1个redisObject结构,16字节,用作val对象;
  • 1个SDS结构,(key长度 + 9)字节,用作key字符串;
  • 1个SDS结构,(val长度 + 9)字节,用作val字符串;

当key个数逐渐增多,redis还会以rehash的方式扩展哈希表节点数组,即增大哈希表的bucket个数,每个bucket元素都是个指针(dictEntry*),占8字节,bucket个数是超过key个数向上求整的2的n次方。

评估模型

真实情况下,每个结构最终真正占用的内存还要考虑jemalloc的内存分配规则,综上所述,string类型的容量评估模型为:

总内存消耗 = (dictEntry大小 + redisObject大小 +key_SDS大小 + val_SDS大小)×key个数 + bucket个数 ×指针大小

即:

总内存消耗 = (32 + 16 + key_SDS大小 + val_SDS大小)×key个数 + bucket个数 × 8

32是因为是24,但jemalloc会分配32字节的内存块

测试验证

string类型容量评估测试脚本如下:

#!/bin/shold_memory=`./redis-cli -h 0 -p 10009 info|grep used_memory:|awk -F: '{printf "%d", $2}'`
echo "before test, memory used: $old_memory"for((i=1000; i<3000; i++))
do./redis-cli -h 0 -p 10009 set test_key_$i test_value_$i > /dev/nullsleep 0.2
donenew_memory=`./redis-cli -h 0 -p 10009 info|grep used_memory:|awk -F: '{printf "%d", $2}'`
echo "after test, memory used: $new_memory"let difference=new_memory-old_memory
echo "difference is: $difference"

测试用例中,key长度为 13,value长度为15,key个数为2000,根据上面总结的容量评估模型,容量预估值为2000 ×(32 + 16 + 32 + 32) + 2048× 8 = 240384

运行测试脚本,得到结果如下:

img

结果都是240384,说明模型预估的十分精确。

hash

哈希对象的底层实现数据结构可能是listpack或者hashtable,当同时满足下面这两个条件时,哈希对象使用listpack这种结构(此处列出的条件都是redis默认配置,可以更改):

  • 哈希对象保存的所有键值对的键和值的字符串长度都小于64字节;
  • 哈希对象保存的键值对的数量都小于512个;

可以看出,业务侧真实使用场景基本都不能满足这两个条件,所以哈希类型大部分都是hashtable结构,因此本篇文章只讲hashtable。

与string类型不同的是,hash类型的值对象并不是指向一个SDS结构,而是指向又一个dict结构,dict结构保存了哈希对象具体的键值对,hash类型结构关系如图所示:

一个hmset命令最终会产生以下几个消耗内存的结构:

  • 1个dictEntry结构,24字节,负责保存当前的哈希对象;
  • 1个SDS结构,(key长度 + 9)字节,用作key字符串;
  • 1个redisObject结构,16字节,指向当前key下属的dict结构;
  • 1个dict结构,88字节,负责保存哈希对象的键值对;
  • n个dictEntry结构,24×n 字节,负责保存具体的field和value,n等于field个数;
  • n个redisObject结构,16×n 字节,用作field对象;
  • n个redisObject结构,16×n 字节,用作value对象;
  • n个SDS结构,(field长度 + 9)× n字节,用作field字符串;
  • n个SDS结构,(value长度 + 9)× n字节,用作value字符串;

评估模型

因为hash类型内部有两个dict结构,所以最终会有产生两种rehash,一种rehash基准是field个数,另一种rehash基准是key个数,结合jemalloc内存分配规则,hash类型的容量评估模型为:

总内存消耗 = [(redisObject大小 ×2 +field_SDS大小 + val_SDS大小 + dictEntry大小)× field个数 + field_bucket个数× 指针大小 + dict大小 + redisObject大小 +key_SDS大小 + dictEntry大小 ] × key个数 + key_bucket个数 × 指针大小

即:

总内存消耗 = [(16 ×2 +field_SDS大小 + val_SDS大小 + 32)× field个数 + field_bucket个数× 8 + 96 + 16 +key_SDS大小 + 32 ] × key个数 + key_bucket个数 × 8

测试验证

hash类型容量评估测试脚本如下:

#!/bin/shvalue_prefix="test_value_123456789012345678901234567890123456789012345678901234567890_"old_memory=`./redis-cli -h 0 -p 10009 info|grep used_memory:|awk -F: '{printf "%d", $2}'`
echo "before test, memory used: $old_memory"for((i=100; i<300; i++))
dofor((j=100; j<300; j++))do./redis-cli -h 0 -p 10009 hset test_key_$i test_field_$j $value_prefix$j > /dev/nulldonesleep 0.5
donenew_memory=`./redis-cli -h 0 -p 10009 info|grep used_memory:|awk -F: '{printf "%d", $2}'`
echo "after test, memory used: $new_memory"let difference=new_memory-old_memory
echo "difference is: $difference"

测试用例中,key长度为 12,field长度为14,value长度为75,key个数为200,field个数为200,根据上面总结的容量评估模型,容量预估值为[(16 + 16 + 32 + 96 + 32)×200 + 256×8 + 96 + 16 + 32 + 32 ]× 200 + 256× 8 = 8126848

运行测试脚本,得到结果如下:

结果相差40,说明模型预测比较准确。

zset

同哈希对象类似,有序集合对象的底层实现数据结构也分两种:listpack或者skiplist,当同时满足下面这两个条件时,有序集合对象使用ziplist这种结构(此处列出的条件都是redis默认配置,可以更改):

  • 有序集合对象保存的元素数量小于128个;
  • 有序集合保存的所有元素成员的长度都小于64字节;

业务侧真实使用时基本都不能同时满足这两个条件,因此这里只讲skiplist结构的情况。skiplist类型的值对象指向一个zset结构,zset结构同时包含一个字典和一个跳跃表,占用的总字节数为16,具体定义如下(redis.h/zset):

typedef struct zset {dict *dict;zskiplist *zsl;
} zset;

跳跃表按分值从小到大保存了所有集合元素,每个跳跃表节点都保存了一个集合元素,dict字典为有序集合创建了一个从成员到分值的映射,字典中的每个键值对都保存了一个集合元素,这两种数据结构会通过指针来共享相同元素的成员和分值,没有浪费额外的内存。zset类型的结构关系如图所示:

一个zadd命令最终会产生以下几个消耗内存的结构:

  • 1个dictEntry结构,24字节,负责保存当前的有序集合对象;
  • 1个SDS结构,(key长度 + 9)字节,用作key字符串;
  • 1个redisObject结构,16字节,指向当前key下属的zset结构;
  • 1个zset结构,16字节,负责保存下属的dict和zskiplist结构;
  • 1个dict结构,88字节,负责保存集合元素中成员到分值的映射;
  • n个dictEntry结构,24×n字节,负责保存具体的成员和分值,n等于集合成员个数;
  • 1个zskiplist结构,32字节,负责保存跳跃表的相关信息;
  • 1个32层的zskiplistNode结构,24+16×32=536字节,用作跳跃表头结点;
  • n个zskiplistNode结构,(24+16×m)×n字节,用作跳跃表节点,m等于节点层数;
  • n个redisObject结构,16×n字节,用作集合中的成员对象;
  • n个SDS结构,(value长度 + 9)×n字节,用作成员字符串;

因为每个zskiplistNode节点的层数都是根据幂次定律随机生成的,而容量评估需要确切值,因此这里采用概率中的期望值来代替单个节点的大小,结合jemalloc内存分配规则,经计算,单个zskiplistNode节点大小的期望值为53.336。

评估模型

zset类型内部同样包含两个dict结构,所以最终会有产生两种rehash,一种rehash基准是成员个数,另一种rehash基准是key个数,zset类型的容量评估模型为:

总内存消耗 = [(val_SDS大小 + redisObject大小 + zskiplistNode大小 + dictEntry大小)×value个数 +value_bucket个数 ×指针大小 + 32层zskiplistNode大小 + zskiplist大小 + dict大小 + zset大小 + redisObject大小 + key_SDS大小 + dictEntry大小 ] ×key个数 +key_bucket个数 × 指针大小

即:

总内存消耗 = [(val_SDS大小 + 16 + 53.336 + 32)×value个数 +value_bucket个数 × 8 + 640 +32 + 96 + 16 + 16 + key_SDS大小 + 32 ] ×key个数 +key_bucket个数 × 8

测试验证

zset类型容量评估测试脚本如下:

#!/bin/shvalue_prefix="test_value_123456789012345678901234567890123456789012345678901234567890_"old_memory=`./redis-cli -h 0 -p 10009 info|grep used_memory:|awk -F: '{printf "%d", $2}'`
echo "before test, memory used: $old_memory"for((i=100; i<300; i++))
dofor((j=100; j<300; j++))do./redis-cli -h 0 -p 10009 zadd test_key_$i $j $value_prefix$j > /dev/nulldonesleep 0.5
donenew_memory=`./redis-cli -h 0 -p 10009 info|grep used_memory:|awk -F: '{printf "%d", $2}'`
echo "after test, memory used: $new_memory"let difference=new_memory-old_memory
echo "difference is: $difference"

测试用例中,key长度为 12,value长度为75,key个数为200,value个数为200,根据上面总结的容量评估模型,容量预估值为[(96 + 16 + 53.336 + 32)×200 + 256×8 + 640 + 32 + 96 + 16 + 16 + 32 + 32 ] ×200 + 256 × 8 = 8477888

运行测试脚本,得到结果如下:

结果相差672,说明模型预测比较准确。

list

列表对象的底层实现数据结构同样分两种:listpack或者linkedlist,当同时满足下面这两个条件时,列表对象使用listpack这种结构(此处列出的条件都是redis默认配置,可以更改):

  • 列表对象保存的所有字符串元素的长度都小于64字节;
  • 列表对象保存的元素数量小于512个;

因为实际使用情况,这里同样只讲linkedlist结构。linkedlist类型的值对象指向一个list结构,具体结构关系如图所示:

一个rpush或者lpush命令最终会产生以下几个消耗内存的结构:

  • 1个dictEntry结构,24字节,负责保存当前的列表对象;
  • 1个SDS结构,(key长度 + 9)字节,用作key字符串;
  • 1个redisObject结构,16字节,指向当前key下属的list结构;
  • 1个list结构,48字节,负责管理链表节点;
  • n个listNode结构,24×n字节,n等于value个数;
  • n个redisObject结构,16×n字节,用作链表中的值对象;
  • n个SDS结构,(value长度 + 9)×n字节,用作值对象指向的字符串;

评估模型

list类型内部只有一个dict结构,rehash基准为key个数,综上,list类型的容量评估模型为:

总内存消耗 = [(val_SDS大小 + redisObject大小 + listNode大小)× value个数 + list大小 + redisObject大小 + key_SDS大小 + dictEntry大小 ] × key个数 + key_bucket个数 × 指针大小

即:

总内存消耗 = [(val_SDS大小 +16 + 32)× value个数 + 16 + 32 + key_SDS大小 + 32 ] × key个数 + key_bucket个数 × 8

测试验证

list类型容量评估测试脚本如下:

#!/bin/shvalue_prefix="test_value_123456789012345678901234567890123456789012345678901234567890_"old_memory=`./redis-cli -h 0 -p 10009 info|grep used_memory:|awk -F: '{printf "%d", $2}'`
echo "before test, memory used: $old_memory"for((i=100; i<300; i++))
dofor((j=100; j<300; j++))do./redis-cli -h 0 -p 10009 rpush test_key_$i $value_prefix$j > /dev/nulldonesleep 0.5
donenew_memory=`./redis-cli -h 0 -p 10009 info|grep used_memory:|awk -F: '{printf "%d", $2}'`
echo "after test, memory used: $new_memory"let difference=new_memory-old_memory
echo "difference is: $difference"

测试用例中,key长度为 12,value长度为75,key个数为200,value个数为200,根据上面总结的容量评估模型,容量预估值为[(96 + 16 + 32) ×200 + 48 + 16 + 32 + 32 ] × 200 + 256 ×8 = 5787648

运行测试脚本,得到结果如下:

结果都是5787648,说明模型预估的十分精确。

Set

一个sadd命令最终会产生以下几个消耗内存的结构:

  • 1个dictEntry结构,24字节,负责保存当前的set对象;
  • 1个SDS结构,(key长度 + 9)字节,用作key字符串;
  • 1个redisObject结构,16字节,指向当前key下属的dict结构;
  • 1个dict结构,88字节,负责保存哈希对象的键值对;
  • n个dictEntry结构,24×n 字节,负责保存具体的member,n等于member个数;
  • n个redisObject结构,16×n 字节,用作member对象;
  • n个SDS结构,(field长度 + 9)× n字节,用作member字符串;

评估模型

set与hash类似,只是value部分没有具体的值。与hash类型一样,内部有两个dict结构,所以最终会有产生两种rehash,一种rehash基准是member个数,另一种rehash基准是key个数,结合jemalloc内存分配规则,hash类型的容量评估模型为:

总内存消耗 = [(redisObject大小 +member_SDS大小 + dictEntry大小)× member个数 + member_bucket个数× 指针大小 + dict大小 + redisObject大小 +key_SDS大小 + dictEntry大小 ] × key个数 + key_bucket个数×指针大小

即:

总内存消耗 = [(16 +member_SDS大小 + 32)× member个数 + member_bucket个数× 8 + 96 + 16 +key_SDS大小 + 32 ] × key个数 + key_bucket个数×8

相关文章:

Redis容量评估模型

计算Redis容量,并不只是仅仅计算key占多少字节,value占多少字节,因为Redis为了维护自身的数据结构,也会占用部分内存,本文章简单介绍每种数据类型(String、Hash、Set、ZSet、List)占用内存量,供做Redis容量评估时使用。当然,大多数情况下,key和value就是主要占用,能…...

[译] 我最爱的PostgreSQL 18特性:虚拟生成列

原文:https://tselai.com/virtual-gencolumns在PostgreSQL 18的新特性中,异步I/O、UUID v7以及升级后统计功能或许会成为众人瞩目的焦点。但对我而言,即将发布的这个版本里,最让我青睐的特性当属虚拟生成列(相关文档可参考PostgreSQL 18官方文档-生成列)。 生成列这类特性…...

nasm 的 Hello, world 在 Windows 10 x64 上

环境 操作系统:nasm 版本: PS C:\Users\xxxx> nasm -version NASM version 2.16.03 compiled on Apr 17 2024link 版本: PS C:\Users\xxxx\Downloads\18176\1\3\2> link Microsoft (R) Incremental Linker Version 14.29.30159.0 Copyright (C) Microsoft Corporation…...

实用指南:52.前端的后端模式:为每个客户端定制专属「管家服务」

实用指南:52.前端的后端模式:为每个客户端定制专属「管家服务」pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New&q…...

Agilent 34401A台式万用表远程读表

Agilent 34401A台式万用表支持RS232和GPIB的方式读数据。 一、RS232读表 将台式万用表的模式调为RS232-9600-8-1-none测试代码class MultimeterStrategy:def __init__(self, port, baudrate=9600):self.port = portself.baudrate = baudrateself.serial = Noneself.retry_max =…...

Java 在大数据处理与人工智能中的应用

在数字化时代,数据成为新的生产要素,人工智能成为新的驱动引擎。大数据与人工智能的结合,使得企业能够从海量数据中提取价值,驱动业务创新与智能决策。虽然很多人提到 AI 就会联想到 Python,但 Java 在大数据和人工智能的工程化落地中仍然不可或缺。它凭借成熟的生态体系、…...

马克思,本就是一位独立研究者

ECT-OS-JiuHuaShan/ORCID:0009-0006-8591-1891▮ 推理请求接收:历史人物本质定位 ▮ 公理锚定:自然辩证法第5定理(文明演进个体作用力) ▮ 因果算符启动:独立研究者与文明级公理发现的相关性验证 绝对结论:卡尔马克思是文明级公理架构师的先驱形态,其独立研究性质为历史…...

产品二期,从GPT5规划开始

具有产品研发经验的应该知道,GPT5提供的规划设计,兼顾了完善和可执行两个关键维度。经常使用大模型都有的感受是:如果在某个领域有0-1的入门,那么AI可以带你快速的进行1-100的尝试。背景简介 楼里App一期开发完成,开始进行二期的网站开发,想以此需求作为驱动,探索整个流…...

Redis能抗住百万并发的秘密

前言 今天想和大家深入聊聊Redis为什么能够轻松抗住百万级别的并发请求。 有些小伙伴在工作中可能遇到过这样的场景:系统访问量一上来,数据库就扛不住了,这时候大家第一时间想到的就是Redis。 但你有没有想过,为什么Redis能够承受如此高的并发量?它的底层到底做了什么优化…...

接受 “未完成态”,是一种能力

正文这个标题,写给你们,也写给我自己。我不知道有多少人有这种类似的问题:我们很难把一个没有写完的字、一件没有完成的事情给别人看。这种做到半路的样子如果拿出来展示的话,非常难为情。尤其是如果还要中途易辙的话,那就更不好解释了。网上经常有那种开玩笑说熬夜的,说…...

深入理解JNI、安全点与循环优化:构建高健壮性Java应用

🔥🔥🔥来都来了 ~ 先赞后看 效果翻倍哦 ~ 👍👍👍 引言 在Java开发者的工具箱中,有一些看似神秘却极其重要的底层概念。你是否曾听说过在循环中插入Thread.sleep(0)可以"唤醒"GC?或者疑惑为什么一个简单的循环计数器类型选择会影响整个应用的稳定性?本…...

英语_阅读_fascinating facts about water_待读

Did you know these fascinating facts about water? They might change the way you think about every drop!你知道这些关于水的奇妙事实吗?它们可能会改变你对每一滴水的看法! Scientists have discovered something incredible - the amount of water on the Earth toda…...

AI自动化测试全攻略:从AI 自动化测试实战到AI 智能测试平台开发!

最新一期 AI + 全栈测试开发训练营震撼来袭,课程内容全面升级,月薪轻松对标 30k + !今日开放 5 个特价名额,零基础小白与测试老司机皆可在此找到专属学习路径,涵盖从AI 自动化测试实战到AI 智能测试平台开发等前沿内容,覆盖 AI 时代测试开发核心能力,帮你构建完整知识体…...

LG9691

考虑 dp。设 \(f_i\) 表示到位置 \(i\) 所需的最小花费,且第 \(i\) 个位置必选,现在要找上一个决策点 \(j\),这个点应该要在此前所有区间的左端点的后面,才能保证这些区间能被覆盖(即确保至少一个在之前每个区间内),则 \(j\) 应满足 \(\max_{r_k<i}l_k \le j < i\…...

即时通讯小程序 - 实践

即时通讯小程序 - 实践pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", monospace !important; font-size: 1…...

PHP serialize 序列化完全指南

PHP serialize 序列化完全指南 介绍 如果你和我一样,第一次在 PHP 中看到序列化字符串时会觉得很困惑。我当时在做一个 Laravel 项目,想搞清楚将任务推送到队列时到底发生了什么。我发现一些数据被序列化了,但不知道为什么以及怎么工作的。不过在我花时间研究序列化后,发现…...

CF2112D

注意到一条边无论方向如何,都能使两端的某一个点到达另一个点,即至少有 \(n-1\) 对。如果只需要 \(n-1\) 对,那么只需要在每一条链上相邻的边方向相反即可。对于剩下的一对,我们可以找到一个度数为 \(2\) 的点,并把连接的两条边由反向改为同向,那么附近的三个点产生的对数…...

CF2112C

设 Alice 取的位置为 \(i,j,k\) 且 \(i<j<k\),则 Bob 的最优策略有两种:取 \(n\) 或 \(k\)。为了使 Alice 必胜,必须同时满足 \(a_i+a_j+a_k>a_n,a_i+a_j>a_k\)。枚举 \(i,j\),显然满足两个条件的 \(k\) 都是一段连续的区间。分别二分算出两个区间的边界,那么…...

CF342C

显然,每一层恰好能放下两个球(事实上这也是最优的方案),那么下面一共可以放 \(\lfloor \frac{h}{r} \rfloor \times 2\) 个球,剩余 \(h - \lfloor \frac{h}{r} \rfloor \times r+r\) 的高度,记 \(h=h-\lfloor \frac{h}{r} \rfloor\times r\) 为立方体部分剩余高度。最上面…...

ICPC/XCPC 做题记录

[SNCPC2019] Unrooted Trie 发现不满足题意的情况就是一个节点到多个子节点的边的字母相同,那么合法当且仅当每个节点到子节点的字母互不相同。那么可以统计每个节点连接的字母数量,并运用类似换根 dp 的思路,快速更新这个数量并实时维护符合条件的点的数量即可。时间复杂度…...

LG9648

发现不满足题意的情况就是一个节点到多个子节点的边的字母相同,那么合法当且仅当每个节点到子节点的字母互不相同。那么可以统计每个节点连接的字母数量,并运用类似换根 dp 的思路,快速更新这个数量并实时维护符合条件的点的数量即可。时间复杂度 \(O(nA)\),其中 \(A=26\) …...

LG5689

设 \(f_u\) 表示以 \(u\) 为根的子树构成不同多叉堆的方案数。显然最小的 \(0\) 应该分配给 \(u\),剩下的分给子节点 \(v_1,v_2,\cdots,v_k\),根据乘法原理,有 \[f_u=\prod_{i=1}^k \binom{siz_u-1-\sum_{j=1}^{i-1}siz_{v_j}}{siz_{v_i}}f_{v_i} \]将组合数和 \(f\) 分开,…...

近五年 CSP NOIP 补题记录

2025.6.18 NOIP2024 T4 树上查询 To be updated. Submission CSP-S2019 江西 T3 网格图 To be updated. Submission 2025.6.19 CSP-S2019 D1T2 括号树 To be updated. Submission CSP-S2021 T3 回文 To be updated. Submission CSP-S2019 江西 T1 日期 To be updated. Submissi…...

CF2111C

显然,操作的方式一定是一段数字相等的极长连续段向左右拓展(包括长度为 \(1\) 的段)。故只需扫一遍并维护每段的值和长度即可,并更新答案。时间复杂度 \(O(\sum n)\)。 #include<iostream> #include<cstdio> #define int long long #define N 500010 using nam…...

唐人日记

唐注意看函数返回值!!!!!!!...

CF2111B

首先最大的 \(f_n\) 必须放的下,即 \(f_n \le \min(w,l,h)\)。此外,\(f_{n-1}\) 紧贴在 \(f_n\) 的一个面上,故要 \(f_n+f_{n-1} \le \max(w,l,h)\)。剩下的第 \(i\) 个正方体由于满足 \(f_{i+2}=f_{i}+f_{i+1}\),故只需与第 \(i+1,i+2\) 个正方体共用一条棱即可,且一定不…...

ABC394F

在同一棵树中,选择任意一个点作为根,效果都是相同的。不妨以 \(1\) 为树根,考虑树上 dp,记 \(f_u\) 为以 \(u\) 为根的子树的点数最大值。注意到根节点度数可为 \(1\) 可为 \(4\),而非根非叶子节点度数必须为 \(4\)。由此可以分两类转移。假设子树中 \(u\) 度数为 \(1\),…...

LG11793

注意到 \(N \times M \le 2\times 10^7\),因此不难得到一个 dp 做法。设 \(f_i\) 表示把前 \(i\) 个橙子装进箱子内的最小成本,则不难得到以下转移式: \[f_i=\min_{i-j\le m,0 \le j < i} f_j+k+ ( i - j ) \times ( \max_{j < k \le i} a_k - \min_{j < k \le i} …...

ABC394G

题目要求最小化爬楼梯的次数,那么我们就要让楼层的变化尽量小,即沿线楼房高度越高越好。不难发现影响答案的是路线中的楼房高度的最小值,则需要最大化最小值。那么就不难用 Kruskal 重构树做了。对每个点进行唯一编号,相邻的点建边权为较小的的楼房高度的双向边。剩下的就是…...

MX 炼石 2026 NOIP #5

qwq2026 --【炼石计划 NOIP】-- 第五套 链接:link 题解:link 时间:4h (2025.09.11 14:00~18:00) 题目数:4 难度:A B C D估分:[40,60] + 80 + 32 + ? = [152,172]+? 得分:40 + 60 + 32 + 10 = 142 Rank:场祭补题天依宝宝可爱!...

0126_状态模式(State)

状态模式(State) 意图 允许对象在内部状态改变时改变它的行为。对象看起来似乎修改了它的类。 UML 图优点行为与状态绑定:将特定状态下的行为局部化到对应的状态类中 消除条件判断:避免了大量的if-else状态判断逻辑 状态转换明确:使状态转换流程更加清晰和可管理 易于扩展:…...

Visual Studio 2026 预览体验版现已发布,一起来看看带来哪些新功能!

前言 2025 年 9 月 9 日微软 Visual Studio 团队正式推出了 Visual Studio 2026 预览体验版(Visual Studio 2026 Insiders),此次发布标志着 Visual Studio 迎来一个全新的时代,它将人工智能深度集成到平台中,基础功能更强大,性能也得到进一步提升。 下载 Visual Studio 2…...

基于RK3568/RK3576/RK3588/全志H3/飞腾芯片/国产UOS等/国标GB28181监控系统

一、前言说明 之前从最底层协议通信把gb28181实现了一遍,没有用到exosip或者pjsip等任何第三方库,通过熟读gb28181国标文档几百页,看了一遍又一遍,根据对应的官方文档,一行行代码底层实现,其实就是网络通信,可选tcp或者udp,和http都是同类型的协议,有消息头和消息体,…...

Go语言读写锁(RWMutex)底层原理详解

Go语言读写锁(RWMutex)底层原理详解 概述 Go语言的sync.RWMutex是一种读写锁,允许多个读操作同时进行,但写操作是互斥的。这种锁机制在读多写少的场景下能显著提高并发性能。底层通过互斥锁和原子计数器实现复杂的并发控制。 核心数据结构 type rwmutex struct {rLock m…...

【GitHub每日速递】无需提示词!Nano Bananary香蕉超市:AI绘画玩法多到停不下来

原文:【GitHub每日速递】无需提示词!Nano Bananary香蕉超市:AI绘画玩法多到停不下来 AutoGPT来袭!搭建、部署、运行AI智能体全攻略揭秘 AutoGPT 是一个基于自主任务执行的AI代理工具。简单讲,它能让AI自动拆解目标并执行一系列操作来完成任务,无需持续人工干预。适用人群…...

小题狂练 (J)

solset-J\[\newcommand{\diag}{\operatorname{diag}} \]目录 目录[AGC070B] Odd Namori[JOI Open 2025] 冒泡排序机 / Bubble Sort Machine[AGC039F] Min Product Sum[AGC070B] Odd NamoriMatrix-Tree 定理:给一张带权有向图 \(G\),求 \(G\) 所有以 \(1\) 为根的内向生成树边…...

Drift数据库开发实战:类型安全的SQLite解决方案

Drift数据库开发实战:类型安全的SQLite解决方案本文基于BeeCount(蜜蜂记账)项目的实际开发经验,深入探讨如何使用Drift构建类型安全、高性能的Flutter数据库层。项目背景 BeeCount(蜜蜂记账)是一款开源、简洁、无广告的个人记账应用。所有财务数据完全由用户掌控,支持本地存…...

DELPHI FireDAC连接EXCEL文件

重要提示: xls后缀的文件与xlsx后缀的文件,连接方法不一样. 可以使用代码来实现:FDConnection1.Connected := false;FDConnection1.Params.Clear;FDConnection1.DriverName := ODBC;FDConnection1.Params.Values[DriverID] := ODBC;FDConnection1.Params.Values[ODBCDriver] :=…...

读人形机器人09教育行业

读人形机器人09教育行业1. 教育行业 1.1. 教育是社会进步的基石,是指引后代走向启蒙与创新的明灯 1.2. 人形机器人通过使学习互动化、沉浸化、趣味化,革新了教学方法 1.3. 借助技术创造兼具教育性与吸引力的体验,培养学生成为主动学习者和批判性思考者 2. 个性化学习体验 2.…...

PHP判断字符串是否包含中文

function hasChinese($str) { return preg_match(/[\x{4e00}-\x{9fa5}]/u, $str);} // 使用示例$string = "Hello 你好";if (hasChinese($string)) { echo "字符串包含中文";} else { echo "字符串不包含中文";}每天进步一点点...

当我们红尘作伴,活得潇潇洒洒

为了证明我是真的睡不着而非摆到凌晨三点,先来一点正经东西。平面等腰直角三角形加,查询矩形和。经典问题,但我刚刚才会。考虑矩形加矩形和是咋做的,通过一堆拆拆拆把 4-side 变成 2-side,然后扫描线扫掉一维,数据结构维护另一维。那等腰直角三角形肯定也要拆拆拆。认为下…...

诡异的mysql8的问题

同样是使用 mysql8的镜像 在其他三台服务器上能正常执行druid:initial-size: 5min-idle: 5max-active: 20max-wait: 60000# 调整为 1800000 毫秒 (30 分钟),需大于数据库 wait_timeoutmin-evictable-idle-time-millis: 1800000# 调整检查间隔为 120000 毫秒 (2 分钟)time-betwe…...

二叉树理论

满二叉树:只有度数为0或者2的节点,并且度数为0的节点在同一层;完全二叉树 除了底层节点可能没填满以外其他都填满了,并且最下面一层的节点都集中在该层最左边的若干位置。 之前我们刚刚讲过优先级队列其实是一个堆,堆就是一棵完全二叉树,同时保证父子节点的顺序关系。 二…...

支付中心的熔断降级要怎么做

下面我会把支付中心在流量骤增 / 下游通道故障时的熔断与降级策略拆成(1)原则与常见策略,(2) 业务级降级/路由策略,(3) 具体落地组件(行业实践与参考),以及(4)可直接落地的 Java 示例(使用 Resilience4j + fallback + 速率限制 + 隔离)。 1) 基本原则(5 条行业共识…...

协议版iM蓝号检测,批量筛选iMessages数据,无痕检测是否开启iMessage服务

一、实现iMessage数据检测的两种方式:1.人工筛选,将要验证的号码输出到文件中,以逗号分隔。再将文件中的号码粘贴到iMessage客户端的地址栏,iMessage客户端会自动逐个检验该号码是否为iMessage账号,检验速度视网速而定。红色表示不是iMessage账号,蓝色表示iMessage账号。2…...

栈和队列总结

栈和队列理论C++中stack,queue 是容器么? 我们使用的stack,queue是属于那个版本的STL? 我们使用的STL中stack,queue是如何实现的? stack,queue 提供迭代器来遍历空间么?stack和queue是STL中的容器适配器,不是类似list,vector那样的容器; 容器适配器本质上是基于底层真…...

工业互联网认知实训台-一句话介绍

工业互联网认知实训台主要由传感器、PLC控制器、工业以太网设备、人机界面(HMI)、步进电机和伺服电机等设备组成。步进电机:通过电脉冲信号控制电机每步旋转固定角度,结构简单、成本低,适合精确定位,但效率较低且可能失步。实训台中使用PLC(如1212C系列)通过脉冲信号控…...

1

<meta name="description" content="加载中... 如白屏请[ 点击刷新页面 ]"> <meta property="og:description" content="加载中... 如白屏请[ 点击刷新页面 ]"> <meta http-equiv="Cache-Control" content=&…...

湾区杯 SilentMiner WP

攻击者的ip地址查看文件 /var/log/btmp 发现短时间内大量登录,可确定攻击者 ip 为 192.168.145.131 lastb -f /var/log/btmp192.168.145.131攻击者共进行多少次ssh口令爆破失败?根据 /var/log/btmp 文件计数 lastb -f btmp | grep 192.168.145.131 | wc -l也可以在 /var/log/…...

Python-课后题题目-1.1编程世界初探

1.1编程世界初探(单选题) 1.程序设计语言的主要目的是什么? A让计算机变得更便宜 B使人类能够以高效,清晰,结构化的方式表达计算机逻辑和数据操作 C取代数学和逻辑学 D仅用于编写游戏程序 2.机器语言是由什么组成的? A十进制数字 B英文字母 C二进制代码 D特殊符号 3.汇…...