文章目录

  • 1. 基础结构和编码类型
  • 2. 编码类型和数据结构实现
    • 2.1 字符串(String)
    • 2.2 压缩列表(listpack)
    • 2.3 哈希表(hashtable)
    • 2.4 快速列表(quicklist)
    • 2.5 整数集合(intset)
    • 2.6 跳表(skiplist)

1. 基础结构和编码类型

Redis所有的值对象都是用RedisObject进行存储的,其结构如下:

typedef struct redisObject {unsigned type:4;        // 数据类型(String, Hash, List, Set, ZSet)unsigned encoding:4;    // 当前使用的底层编码(如 int, embstr, hashtable 等)unsigned lru:24;        // LRU 时间戳或 LFU 计数int refcount;           // 引用计数,用于内存回收void *ptr;              // 指向实际数据的指针
} robj;

非int数值一个RedisObject固定开销16字节

  • type:4位 = 0.5字节
  • encoding:4位 = 0.5字节
  • lru:24位 = 3字节
  • refcount:32位 = 4字节
  • ptr:64位 = 8字节(OBJ_ENCODING_INT直接存的是整数值,少了指针消耗)

type(数据逻辑类型):对应五大基础类型:

  1. OBJ_STRING:字符串String,基础数据类型
  2. OBJ_HASH:哈希表Hash,存储键值对,在紧凑性和性能间权衡
  3. OBJ_LIST:列表List,存储有序的字符串元素
  4. OBJ_SET:集合Set,存储唯一、无序的字符串元素
  5. OBJ_ZSET:有序集合ZSet,存储有序且唯一的对象,权衡有序性、性能和内存

encoding(编码方式):标识ptr指针引用的实际底层实现数据结构,和type对应关系:

  1. OBJ_STRING(字符串):保存最基础的字符串、整数和浮点数
    1. OBJ_ENCODING_INT
      • 条件:整数值小于LONG_MAX,即64位符号整数,-263~263-1
      • 数据结构和特点:整数值类型int,ptr直接存储整数值
    2. OBJ_ENCODING_EMBSTR
      • 条件:字符串长度≤44字节
      • 数据结构和特点:短字符串类型embstr(SDS),RedisObject和SDS结构在一块连续内存中
    3. OBJ_ENCODING_RAW
      • 条件:字符串长度>44字节
      • 数据结构和特点:长字串类型raw(SDS),RedisObject和SDS分别在两块内存中分配
  2. OBJ_HASH(哈希表):用于存储多个字段的映射关系,如同一个key下id=XX,name=XX
    1. OBJ_ENCODING_LISTPACK
      • 条件:字段和值的字符串长度≤hash-max-listpack-value(默认64)且字段数量<=hash-max-listpack-entries(默认512)
      • 数据结构和特点:底层使用listpack,仅适用于少量键值对的情况
    2. OBJ_ENCODING_HT
      • 条件:不满足listpack
      • 数据结构和特点:使用字典Dict,标准的哈希结,增删改查为O(1),较耗内存
  3. OBJ_LIST(列表):保存存入数据顺序的简单列表,可用于简单的队列保存
    1. OBJ_ENCODING_QUICKLIST
      • 条件:默认
      • 数据结构和特点:使用quickList,双向链表和listpack的混合结构,每个链表节点指向一个listpack
  4. OBJ_SET(集合):无需且唯一的集合,可用于快速搜索或获取交集等
    1. OBJ_ENCODING_INTSET
      • 条件:集合中元素都是整数值且元素数量<=set-max-intset-entries(默认512)
      • 数据结构和特点:底层使用intset,内存效率极高,支持二分查找
    2. OBJ_ENCODING_HT
      • 条件:不满足intset
      • 数据结构和特点:使用字典Dict,标准的哈希结,增删改查为O(1),较耗内存
  5. OBJ_ZSET(有序集合):带有权重值的排序且唯一集合,常用于排行榜等场景
    1. OBJ_ENCODING_LISTPACK
      • 条件:元素数量≤zset-max-listpack-entries(默认128)且所有成员长度 ≤ zset-max-listpack-value(默认64字节)
      • 数据结构和特点:底层使用listpack,仅适用于少量键值对的情况
    2. OBJ_ENCODING_SKIPLIST
      • 条件:不满足listpack
      • 数据结构和特点:使用跳跃表skiplist+字典dict的混合结构,兼具排查+查找性能,内存开销最大

2. 编码类型和数据结构实现

编码类型一共有8种:

  1. OBJ_ENCODING_INT:字符整数
  2. OBJ_ENCODING_EMBSTR:embstr字符串
  3. OBJ_ENCODING_RAW:raw字符串
  4. OBJ_ENCODING_LISTPACK:listpack压缩列表
  5. OBJ_ENCODING_HT:hashtable哈希表
  6. OBJ_ENCODING_QUICKLIST:quicklist快速列表
  7. OBJ_ENCODING_INTSET:intset整数集合
  8. OBJ_ENCODING_SKIPLIST:skiplist跳表

2.1 字符串(String)

对应编码类型:

  1. OBJ_ENCODING_INT
  2. OBJ_ENCODING_EMBSTR
  3. OBJ_ENCODING_RAW

SDS(Simple Dynamic String)结构体如下:

struct __attribute__ ((__packed__)) sdshdr8 {uint8_t len;        /* 已使用的字节数 */uint8_t alloc;       /* 申请的总字节数(不包括头结构和结尾空字符) */unsigned char flags;/* 类型标志(3位表示类型,5位未使用) */char buf[];         /* 柔性数组,存放实际的字符串内容(以'\0'结尾) */
};

SDS类型

  1. sdshdr5
    • flags:SDS_TYPE_5
    • 最大len:25 = 32
    • alloc:未使用,已分配长度记录在flags的高5位
  2. sdshdr8
    • flags:SDS_TYPE_8
    • len:0 ~ 28 - 1 = 0 ~ 255
    • alloc:28 - 1 = 255
  3. sdshdr16
    • flags:SDS_TYPE_16
    • len:0 ~ 216 - 1
    • alloc:216 - 1
  4. sdshdr32
    • flags:SDS_TYPE_32
    • len:0 ~ 232 - 1
    • alloc:232 - 1
  5. sdshdr64
    • flags:SDS_TYPE_64
    • len:0 ~ 264 - 1
    • alloc:264 - 1

在Redis 7.2.4中,sdshdr5被弃用,被sdshdr8代替

embstr和RedisObject分配在一个内存块,通常一个内存块为64字节,最大长度=RedisObject(16字节) - sdshdr8(结构体3字节)- ‘\0’(1字节)=64-16-3-1=44字节

SDS可以向上扩容,但无法向下降级,如sdshdr8可以变成sdshdr16,但sdshdr32无法变成sdshdr16

2.2 压缩列表(listpack)

适用于少量数据的哈希表和有序集合

listpack本质上是一个字节数组,常说的listpack总大小、entry数量和entry数组只是逻辑化的概念,在listpack中都存储在同一个字节数组中。结构如下:

listpack结构示意:
|total_bytes|num_elements|entry1|entry2|...|entryN|LP_EOF|entry结构示意:
|encoding_type|entry_data|entry_len|

listpack结构

  • total_bytes:总大小,占4字节
  • num_elements:entry数量,占2字节
  • entry:逻辑元素节点
  • LP_EOF:结束符,占1字节

entry结构:

  • encoding_type:编码类型,根据第一个字节决定数据类型和存储方式
  • entry_data:不同编码类型对应存储的内容
  • entry_len
    • 存储内容:存储了encoding_type和entry_data两部分的总字节数
    • 未结束标识:字节最高位是1
    • 结束标识:字节最高位是0
    • 有效数据位:字节的低七位

当做有序集合使用时,会使用二分搜索查找合适的位置进行插入,再将后面的元素往后挪

2.3 哈希表(hashtable)

适用于哈希表和集合

数据结构底层是Dict,使用链表解决哈希冲突,结构如下:

typedef struct dict {dictType *type;  // 指向一系列特定于类型函数的指针(哈希函数、键比较函数等)void *privdata;  // 传递给上述函数的可选私有数据dictht ht[2];    // 两个哈希表(dictht结构)long rehashidx;  // rehash 进度索引,-1 表示未在进行 rehashint16_t pauserehash; // 如果 >0,则 rehash 被暂停
} dict;
typedef struct dictht {dictEntry **table;      // 指向桶数组的指针(每个元素是 dictEntry 链表的头指针)unsigned long size;     // 桶数组的大小(总是 2 的幂次)unsigned long sizemask; // 用于计算索引的掩码,等于 size-1unsigned long used;     // 哈希表中已有的键值对数量
} dictht;
typedef struct dictEntry {void *key;               // 键(通常为 sds 字符串)union {void *val;           // 值(可以指向任何类型,如另一个 sds、redisObject 等)} v;                     // 值的联合体struct dictEntry *next;  // 指向下一个 dictEntry,形成链表
} dictEntry;

未进行扩容缩容时默认使用的是ht[0]

dictht中sizemask=size-1,额外保存sizemask是为了空间换时间,计算key时减少一次相减运算

查找哈希桶过程:

  1. 计算key哈希值
  2. 使用sizemask位运算计算桶位置
  3. 若桶有链表则遍历链表key是否相同

哈希冲突时,桶节点插入使用的是头插法,即新增的节点会靠前

哈希表新增达到阈值后将会自动扩容,而删除桶数量到达阈值后会触发缩容,扩容和缩容会使用渐进式rehash来完成

渐进式Rehash

  • 触发条件
    • 负载因子:used / size
    • 扩容
      • 未执行BGSAVE/BGREWRITEAOF:负载因子≥1,正常扩容
      • 执行BGSAVE/BGREWRITEAOF:负载因子≥5,Redis持久化时避免扩容消耗内存
    • 缩容:负载因子<0.1,避免表空间浪费
  • 扩容大小计算:已使用桶数量*2的最小2次幂
    • 示例:used=5,5*2=10,10的下个2次幂=16,扩容后大小=16
  • 缩容大小结算:已使用桶数量的最小2次幂
    • 示例:used=5,5的下个2次幂=8,缩容大小=8
  • 处理流程
    1. 初始化:为ht[1]根据扩容缩容分配空间,设置rehashidx=0
    2. 分步迁移:将ht[0]的key迁移到ht[1]
      • 增删改查触发:执行操作外,还会将ht[0]中rehashidx的桶迁移到ht[1]中,并将rehashidx加一
      • 定时任务:Redis的定时任务隔段时间检测迁移一批桶
      • key的重哈希:key做迁移时,会使用ht[1]的容量大小重新做一次哈希再迁入
    3. 双表操作
      • 查询:先后ht[0]和ht[1],ht[0]查询到返回,否则接着查询ht[1]
      • 更新:增删改操作只会操作ht[1]
    4. 完成切换
      • 条件:rehashidx=ht[0].size,即全部迁移完毕
      • 桶操作:释放ht[0],将ht[1]设置为新的ht[0],ht[1]再分配空的哈希表
      • 其它参数:设置rehashidx=-1
  • 相比一次性迁移优势
    1. 避免阻塞:不会因为扩缩容导致服务不可用
    2. 平滑过渡:对客户端透明,无影响
    3. 高校内存管理:扩容和缩容代价较小,可以兼顾性能和内存使用效率

2.4 快速列表(quicklist)

quicklist在Redis7.0之前使用的是ziplist,后续使用的是listpack

双向链表+listpack的混合,其结构如下:

typedef struct quicklist {quicklistNode *head;       // 指向链表头节点的指针quicklistNode *tail;       // 指向链表尾节点的指针unsigned long count;       // 所有 listpack 中元素条目(entry)的总和unsigned long len;         // 链表中 quicklistNode 节点的数量int fill : 16;             // 单个节点 listpack 的容量控制策略unsigned int compress : 16; // 压缩深度,表示链表两端不被压缩的节点个数
} quicklist;typedef struct quicklistNode {struct quicklistNode *prev; // 指向前一个节点的指针struct quicklistNode *next; // 指向后一个节点的指针unsigned char *lp;          // 指向该节点内存储的 listpack 的指针unsigned int sz;            // 该 listpack 占用的内存字节大小unsigned int count : 16;    // 该 listpack 中包含的元素条目(entry)数量unsigned int encoding : 2;  // 编码方式,表示节点数据是否被压缩unsigned int container : 2;  // 数据容器类型,标识 *lp 指向的是 listpack
} quicklistNode;

通过list-max-listpack-size属性来限制每个节点中listpack中的元素数量:

  • 正值:每个listpack存储的元素(entry)数量
  • 负值:每个listpack的最大内存字节数,属于枚举值:
    • -1:大小不超过4KB
    • -2:大小不超过8KB(默认值)
    • -3:大小不超过16KB
    • -4:大小不超过32KB
    • -5:大小不超过64KB

通过list-compress-depth属性配置compress压缩深度,内部使用LZF压缩算法对listpack进行无损压缩:

  • 0:不压缩任何节点
  • 1:quicklist两端各有1个节点不压缩,中间节点压缩
  • 2:quicklist两端各有2个节点不压缩,中间节点压缩
  • 3:quicklist两端各有3个节点不压缩,中间节点压缩

2.5 整数集合(intset)

仅适用于整个集都是整数且数量较少的场景,其结构体如下:

typedef struct intset {uint32_t encoding;   // 编码方式,决定contents数组的实际类型uint32_t length;     // 集合中元素的数量int8_t contents[];   // 柔性数组,用于保存实际的整数值(按升序排列)
} intset;

encoding编码方式

  • INTSET_ENC_INT16:表示contents数组是int16_t类型的数组,每个元素占2字节
  • INTSET_ENC_INT32:表示contents数组是int32_t类型的数组,每个元素占4字节
  • INTSET_ENC_INT64:表示contents数组是int64_t类型的数组,每个元素占8字节

contents:柔性数组,实际类型由encoding决定。数组中元素按大小升序排序,且不重复

添加元素过程

  1. 检查编码和升级判断:判断当前编码是否满足新增元素范围
    1. 计算新编码空间:若是INT16升级到INT32,根据新类型确定总空间大小
    2. 重新分配内存:为整个intset重新分配内存
    3. 倒序迁移数据:从后往前遍历原contents数组元素,并把原数组元素迁移到新数组
  2. 查找插入位置
    • 编码升级:新插入的元素不符合原范围,即一定比原来的元素大或小,插入头或尾即可
    • 使用原编码:使用二分查找确定新元素位置,若已存在则退出
  3. 移动元素并插入:将所有元素向后移动一位并在当前位置放入新元素
  4. 更新长度:length字段加一

删除元素过程

  1. 查找插入位置:使用二分查找确定新元素位置
  2. 移动元素:将需要删除元素的后面元素统一往前挪一位
  3. 更新长度:length字段减一

intset的空间利用

  • 特点:重空间轻性能
  • 正常扩容:每次加一元素容量
  • 编码升级:原数组数量*新编码大小+新编码元素大小
  • 缩容:删除时减一元素容量

intset只有新增和删除,没有更新操作

intset每次新增或删除都会进行扩容或缩容,即使客户端一次性操作批量数据也一样

2.6 跳表(skiplist)

适用于ZSet有序集合,当数据量大于配置阈值时将会使用跳表skiplist+哈希字典表dict的组合方式来实现,其结构如下:

typedef struct zset {dict *dict;        /* 字典:维护 member -> score 的映射,用于 O(1) 查找分值 */zskiplist *zsl;    /* 跳跃表:按分值排序存储所有元素,支持高效的范围操作 */
} zset;/* 跳跃表整体结构 */
typedef struct zskiplist {struct zskiplistNode *header, *tail; /* 指向头节点和尾节点的指针 */unsigned long length;     /* 跳跃表中节点的数量(不包括头节点) */int level;                /* 当前跳跃表中除头节点外,节点的最大层级 */
} zskiplist;/* 跳跃表节点 */
typedef struct zskiplistNode {sds ele;                /* 成员(member) */double score;           /* 分值(score) */struct zskiplistNode *backward; /* 后退指针(指向前一个节点) */struct zskiplistLevel {struct zskiplistNode *forward;  /* 前进指针(指向该层下一个节点) */unsigned long span;  /* 跨度(记录该层中前进指针指向的节点与当前节点的距离) */} level[];               /* 多层索引数组(层级在节点创建时根据幂次定律随机生成) */
} zskiplistNode;

使用跳表为基础数据结构的有序集合ZSet可分为两部分:

  1. 跳跃表:实现了数据按分数排序的功能
  2. 哈希字典表:提供数据或分数的映射查询

关于ZSet中跳表的构建

  • 基本概念
    • 最大层数:整个跳表允许的最高层数,Redis7固定为32,Redis6固定为64
    • 概率因子:固定0.25,插入时节点层数+1的概率
    • 链表节点
      • 头尾节点:跳表固定存的节点指针,其中头节点固定有32层最大层数
      • 数据节点:每个数据对应的跳表节点,保存了数据+分数
    • 节点层数:各个节点所拥有的的最大层数,最少为1,通过概率因子随机增长层数
    • 对应层数跨度:若当前节点为N,代表N层中当前节点到下个节点之间在L0层所跨越的节点数量
  • 核心机制
    • 基础链表:每个跳表都有一条包含所有数据节点的基础链表,即L0层链表,其它链表指针都是指向该链表的节点
    • 最大层数:跳表支持维护的最大链表数量
    • 节点层数:数据节点最大支持在多少条链表中记录同层的顺序关系
    • 遍历方式:从最高层往下,同层从左往右遍历
    • 节点插入删除:基本的单向链表插入删除步骤
    • L0的backward指针:指向在L0层的上个节点,用于反向遍历

假设需要往跳表中依次插入12 -> 7 -> 15 -> 5 -> 10 -> 1 -> 3七个节点,提前假设各个节点随机的层数关系如下:

  • 12:2层
  • 7:3层
  • 15:1层
  • 5:2层
  • 10:4层
  • 1:1层
  • 3:2层

构建数据示例如下:

  1. 新增12节点:12节点有2层,维护L0和L1链表
L1: Header -> 12 -> NULL
L0: Header -> 12 -> NULL
  1. 新增7节点:7节点有3层,最大层数更新为3,维护L0、L1和L2链表
L2: Header -> 7 -> NULL
L1: Header -> 7 -> 12 -> NULL
L0: Header -> 7 -> 12 -> NULL
  1. 新增15节点:15节点只有1层,维护L0链表
L2: Header -> 7 -> NULL
L1: Header -> 7 -> 12 -> NULL
L0: Header -> 7 -> 12 -> 15 -> NULL
  1. 新增5节点:5节点有2层,维护L0和L1链表
L2: Header -> 7 -> NULL
L1: Header -> 5 -> 7 -> 12 -> NULL
L0: Header -> 5 -> 7 -> 12 -> 15 -> NULL
  1. 新增10节点:10节点有4层,最大层数更新为4,维护L0、L1、L2和L3链表
L3: Header -> 10 -> NULL
L2: Header -> 7 -> 10 -> NULL
L1: Header -> 5 -> 7 -> 10 -> 12 -> NULL
L0: Header -> 5 -> 7 -> 10 -> 12 -> 15 -> NULL
  1. 新增1节点:1节点只有1层,维护L0链表
L3: Header -> 10 -> NULL
L2: Header -> 7 -> 10 -> NULL
L1: Header -> 5 -> 7 -> 10 -> 12 -> NULL   (注意:Header在L1指向的是5,不是1)
L0: Header -> 1 -> 5 -> 7 -> 10 -> 12 -> 15 -> NULL
  1. 新增3节点:5节点有2层,维护L0和L1链表
L3: Header -> 10 -> NULL
L2: Header -> 7 -> 10 -> NULL
L1: Header -> 3 -> 5 -> 7 -> 10 -> 12 -> NULL
L0: Header -> 1 -> 3 -> 5 -> 7 -> 10 -> 12 -> 15 -> NULL

最终链表如下,很形容的诠释了跳表二字:

L3
L2
L1
L1
L1
L1
L0
L0
L0
L0
L0
L0
1
3
5
7
10
12
15

链表中查找3的步骤:

  1. L3链表:3<10,下降到L2链表
  2. L2链表:3<7,下降到L1链表
  3. L1链表:3<5 -> 3=3,返回结果

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

火山引擎数据智能体DataAgent总结分享

数据的冰山:看得见的资产与看不见的鸿沟 这张图片用“冰山”类比的方式展示了数据资产管理中的可见与不可见问题,并突出了数据利用的核心挑战与潜在陷阱。 1. 冰山之上的“看得见的资产” 内容:数据库、报表、指标等结构化、显性的数据资源。 核心挑战: 需要从“采集存储”…

100种高级数据结构 (速查表)

一、 基础结构的扩展与组合 (Advanced Linear Structures) 这些结构在数组、链表、队列、栈等基础结构上增加了特定功能或约束。双端队列 (Deque - Double-Ended Queue) 介绍&#xff1a;允许在队列的前后两端都进行插入和删除操作的线性结构。应用场景&#xff1a;工作窃取算法…

一个开源的企业官网简介

简介一个完美的企业官网系统,支持手机端和电脑端展示企业风采,还可以展示企业产品/企业新闻资讯等等.普通用户PC端展示普通用户手机端展示管理后台

TCP实现线程池竞争任务

服务端&#xff1a;#include<stdio.h> #include<sys/types.h> #include<sys/socket.h> #include<netinet/in.h> #include<netinet/ip.h> #include<strings.h> #include<unistd.h> #include<ctype.h> #include<arpa/inet.h&…

Redis C++ 实现笔记(F篇)

Implementing Redis in C : F Redis C 实现笔记&#xff08;F篇&#xff09; 前言 本章代码及思路均来自Build Your Own Redis with C/C 本文章只阐述我的理解想法&#xff0c;以及需要注意的地方。 本文章为续<<Implementing Redis in C : E>>所以本文章不再…

finally 与 return的执行顺序

一、第一次试验public static void main(String[] args) throws InterruptedException {System.out.println(aaa(null));}private static StringBuilder aaa(Integer i) throws InterruptedException {StringBuilder sb new StringBuilder();try {i.toString();return sb;} ca…

Git安装教程

简介 Git 是目前全球最流行的分布式版本控制系统&#xff08;Distributed Version Control System, DVCS&#xff09;&#xff0c;核心作用是追踪文件修改历史、支持多人协同开发&#xff0c;并能高效管理代码&#xff08;或任何文本类文件&#xff09;的版本迭代。它由 Linux…

Linux安装RTL8821CE无线网卡驱动

1. 查看网卡芯片$ lspci | grep Net 01:00.0 Network controller: Realtek Semiconductor Co., Ltd. RTL8821CE 802.11ac PCIe Wireless Network Adapter2. 预备配套sudo apt install -y dkms git3. 下载驱动并安装git clone https://github.com/tomaspinho/rtl8821ce.git cd r…

vue3存储/获取本地或会话存储,封装存储工具,结合pina使用存储

目录 一、基本用法&#xff08;原生 API&#xff09; 1. 存储数据 2. 获取数据 3. 删除数据 二、Vue3 中封装成工具函数&#xff08;推荐&#xff09; 三、以上工具函数在 Vue3 组件中使用 1. 在选项式 API 中使用 2. 在组合式 API&#xff08;setup 语法糖&#xff09;…

【Flink】DataStream API:基本转换算子、聚合算子

目录基本转换算子映射&#xff08;map&#xff09;过滤&#xff08;filter&#xff09;扁平映射聚合算子按键分区&#xff08;keyBy&#xff09;简单聚合&#xff08;sum/min/max/minBy/maxBy&#xff09;规约聚合&#xff08;reduce&#xff09;基本转换算子 有如下POJO类用来…

从淘宝推荐到微信搜索:查找算法如何支撑亿级用户——动画可视化

本篇技术博文摘要 &#x1f31f; 本文通过动画可视化深入解析数据结构中的核心查找算法&#xff0c;从基础概念到高阶应用&#xff0c;全面覆盖顺序查找、折半查找、分块查找、B树/B树及散列查找的核心原理与实现细节。文章以动态演示为核心工具&#xff0c;直观展现算法执行过…

图像正向扭曲反向扭曲

在图像处理领域&#xff0c;正向扭曲&#xff08;Forward Warping&#xff09;和反向扭曲&#xff08;Backward Warping&#xff09;是两种核心的图像坐标映射与像素重采样技术&#xff0c;核心区别在于“像素映射的方向”——是从“原始图像”到“目标图像”&#xff0c;还是从…

【C语言】 第三课 函数与栈帧机制详解

1 函数的基本概念 在C语言中&#xff0c;函数是程序的基本执行单元。一个函数的定义包括返回类型、函数名、参数列表和函数体。例如&#xff1a; int add(int x, int y) { // 函数定义int z x y;return z; }在使用函数前&#xff0c;通常需要声明&#xff08; declaration&am…

多个大体积PDF文件怎么按数量批量拆分成多个单独文件

在现代社会中&#xff0c;电子文档在我们的身边无所不在&#xff0c;而PDF文件时我们日常接触非常多的文档类型之一。PDF由于格式稳定、兼容性好&#xff0c;因此经常被用于各行各业。但是&#xff0c;我们平时在制作或搜集PDF文件时&#xff0c;文件太大&#xff0c;传输和分享…

ansible-角色

角色 一、利用角色构造ansible playbook 随着开发更多的playbook&#xff0c;会发现有很多机会重复利用以前编写的playbook中的代码。或许&#xff0c;一个用于为某一应用配置MySQL数据库的play可以改变用途。通过利用不同的主机名、密码和用户来为另一个应用配置MySQL数据库。…

git命令行打patch

在 Git 里打 patch&#xff08;补丁&#xff09;其实就是把某些提交的改动导出来&#xff0c;生成一个 .patch 文件&#xff0c;方便别人用 git apply 或 git am 打进代码里。&#x1f539; 常用方式1. 基于提交导出 patch导出最近一次提交&#xff1a;git format-patch -1 HEA…

文华财经多空提示指标公式 变色K线多空明确指标 文华wh6赢顺多空买卖提示指标

XX:240C;YY:MA(C,1);A1:POW(XX,2)/360-POW(YY,2)/260;A5:EMA2(EMA2(A1,20),5),LINETHICK2;A6:A5*0.9999,COLORSTICK;A20:EMA2(EMA2(A5,20),5),LINETHICK2;A60:EMA2(EMA2(A20,20),5),LINETHICK2;支撑:HHV(A5,30),COLORRED;天数:BARSSINCE(A5HHV(A5,0));YL:REF(A5,1)2.79-天数*0.…

记录一个防重Toast

当我们已经对某个按钮做了防暴力点击&#xff0c;但是依然在业务上有些复杂交互的情况&#xff0c;需要我们封装一个防重Toast。针对这类情况&#xff0c;可以直接使用下面的showDebouncedToastdata class ToastInfo(val id: Any? null,val command: MediaCommandDebouncer.M…

在线测评系统---第n天

主要完成了退出登录前后的代码的实现&#xff0c;以及题目列表的查询1.退出登录前端引入了全局前置守卫&#xff0c;如果cookie里面没有token则直接跳转到login页面&#xff1b;有则直接跳转到layout页面&#xff0c;无需重新登录后端接收到退出登录&#xff0c;将token置为无效…

机器学习从入门到精通 - 卷积神经网络(CNN)实战:图像识别模型搭建指南

机器学习从入门到精通 - 卷积神经网络(CNN)实战&#xff1a;图像识别模型搭建指南 各位&#xff0c;是不是觉得那些能认出照片里是猫还是狗、是停车标志还是绿灯的AI酷毙了&#xff1f;今天咱们就撸起袖子&#xff0c;亲手搭建一个这样的图像识别模型&#xff01;别担心不需要你…