Redis数据结构

ps:如果这篇帖子对于还在找工作和找实习的你有所帮助,可以关注我,给本贴点赞、评论、收藏并订阅专栏;同时不要吝啬您的花花

Redis 作为高性能的键值对数据库,支持多种灵活的数据结构,每种结构都有其独特的底层实现和适用场景,熟练掌握这些数据结构及其底层原理是高效使用 Redis 的关键。以下是 Redis 核心基础数据结构、常用扩展数据结构的详细解析,重点明确每种结构底层使用的数据结构,覆盖 string、hash、list、set、zset、bitmap、geo、HyperLogLog、stream 所有指定类型。

一、基础核心数据结构(明确底层数据结构)

1. 字符串(String)

String 是 Redis 最基础、最常用的数据结构,也是其他数据结构的基础,它可以存储字符串、数字(整数/浮点数)、二进制数据(如图片、序列化对象),最大存储容量为 512MB。

底层实现:默认使用简单动态字符串(SDS) 实现,而非 C 语言原生字符串。SDS 具备动态扩容、减少内存碎片、二进制安全(可存储任意二进制数据,如图片、视频流)等优势,同时兼容 C 语言字符串操作函数,是 Redis 中 String 类型的核心底层支撑。

SDS 扩容与缩容时机

1. 扩容时机:当执行 append、set 等命令,需要向 SDS 追加内容,且 SDS 剩余空闲空间(free 字段)不足以容纳新增内容时,触发扩容。扩容规则遵循“空间预分配”策略,避免频繁扩容:

  • 若扩容后 SDS 总长度 < 1MB,扩容后的空闲空间与实际存储长度(len 字段)相等,即总容量 = len + free(free = len);
  • 若扩容后 SDS 总长度 ≥ 1MB,扩容后的空闲空间固定为 1MB,避免因频繁追加导致内存浪费;
  • SDS 最大容量为 512MB,达到该上限后无法继续扩容,会返回错误。

2. 缩容时机:SDS 不支持自动缩容,当执行 trim 等命令手动删除部分内容后,空闲空间会增加,但不会自动释放多余内存(即 len 减小,free 增大,总容量不变)。若需释放多余内存,需手动执行相关命令(如 redis-cli 的 memory purge 或通过代码调用 SDS 相关 API),主动回收未使用的空间,减少内存碎片。

SDS 与 C 语言字符串、Java 字符串的核心区别:SDS 是 Redis 为解决传统字符串缺陷设计的动态字符串,与 C 语言原生字符串、Java 字符串在底层结构、功能特性上差异显著,具体对比如下:

1. 底层结构差异

  • SDS:由 len(实际存储字符串长度)、free(空闲空间长度)、buf(字节数组,存储字符串内容)三部分组成,末尾自动添加 '\0' 以兼容 C 语言字符串操作,但 '\0' 不计算在 len 内。
  • C 语言字符串:仅由字节数组(buf)组成,无长度字段,以 '\0' 作为字符串结束标志,无法直接获取字符串长度。
  • Java 字符串:底层基于 char 数组(JDK 1.8 及之前)或 byte 数组(JDK 1.9 及之后,根据编码自适应),包含长度字段,且字符串对象不可变(底层数组被 final 修饰)。

2. 长度获取效率

  • SDS:直接读取 len 字段,时间复杂度 O(1),无需遍历字节数组。
  • C 语言字符串:需遍历字节数组,直到找到 '\0' 才停止,时间复杂度 O(n),n 为字符串长度。
  • Java 字符串:直接读取内部长度字段,时间复杂度 O(1),与 SDS 类似,但因字符串不可变,长度一旦确定无法修改。

3. 二进制安全性

  • SDS:二进制安全,允许存储任意二进制数据(如图片、视频流),因为其通过 len 字段判断字符串结束,而非 '\0',避免了 '\0' 被误判为结束标志的问题。
  • C 语言字符串:非二进制安全,若字符串中包含 '\0',会被误判为字符串结束,导致后续数据丢失,无法存储二进制数据。
  • Java 字符串:理论上支持二进制数据(通过 getBytes() 方法),但设计初衷是存储字符,且字符串不可变,频繁修改二进制数据会产生大量临时对象,效率较低。

4. 扩容与修改特性

  • SDS:支持动态扩容(遵循空间预分配策略)和手动缩容,修改字符串(如 append)时无需手动管理内存,且不会造成内存溢出。
  • C 语言字符串:固定长度,修改时需手动重新分配内存(如 strcat 函数),若内存分配不足会导致溢出,且频繁扩容会产生大量内存碎片。
  • Java 字符串:不可变,修改字符串(如 concat、replace)时不会修改原对象,而是创建一个新的字符串对象,频繁修改会占用大量内存,效率较低(需借助 StringBuilder/StringBuffer 优化)。

5. 内存管理

  • SDS:手动管理内存(Redis 内核负责分配与释放),支持空闲空间预分配和手动缩容,内存利用率较高。
  • C 语言字符串:完全由开发者手动分配和释放内存,容易出现内存泄漏、内存溢出等问题。
  • Java 字符串:由 JVM 垃圾回收机制自动管理内存,无需开发者手动操作,但不可变性导致内存利用效率在频繁修改场景下较低。

总结:SDS 兼顾了 C 语言字符串的兼容性和 Java 字符串的高效长度获取,同时解决了两者在二进制安全、内存管理、动态修改上的缺陷,是适配 Redis 高性能、多场景需求的专属字符串实现。

核心命令

  • set key value:设置键值对,覆盖已有键;
  • get key:获取指定键的值;
  • append key value:在指定键的字符串末尾追加内容;
  • incr key / decr key:对存储的整数进行自增/自减操作(原子性);
  • setex key seconds value:设置键值对并指定过期时间(秒)。

应用场景:缓存热点数据(如用户信息、商品详情)、计数器(文章阅读量、点赞数)、分布式锁(配合 setnx 命令)、存储二进制数据(如验证码图片)。

2. 哈希(Hash)

Hash 是一种键值对的集合,适合存储一个对象的多个属性(如用户信息:姓名、年龄、手机号),支持对单个属性的增删改查,无需操作整个对象,效率更高。

底层实现:采用双底层结构自适应,根据元素数量和大小动态切换:

  1. 哈希表中元素较少(默认少于512个)、且元素值较小时,使用 ziplist(压缩列表)存储,节省内存;
  2. 当元素数量或值大小超过阈值时,自动转为 hashtable(哈希表),保证查询、修改的高效性(时间复杂度接近 O(1))。

补充:ziplist(压缩列表)底层结构与存储方式:ziplist 是 Redis 设计的一种紧凑的顺序存储结构,核心目的是节省内存,适用于元素数量少、元素值小的场景(如 Hash、List 元素较少时)。

底层结构(从前往后):由 4 个固定头部 + 多个 entry(元素节点) + 1 个尾部标识组成,所有数据连续存储在一块内存中,无冗余指针,极大减少内存碎片:

  • zlbytes:4 字节,记录整个 ziplist 的总字节数,用于快速计算 ziplist 末尾位置;
  • zltail:4 字节,记录 ziplist 中最后一个 entry 的偏移量,无需遍历即可快速定位最后一个元素;
  • zllen:2 字节,记录 entry 的数量,若元素数量超过 65535,则该字段失效,需遍历整个 ziplist 统计数量;
  • entry:可变长度,存储具体元素,每个 entry 包含“前一个 entry 长度”“元素编码”“元素值”三部分,通过编码区分元素类型(字符串/整数)和长度,实现紧凑存储;
  • zlend:1 字节,固定值 0xFF,标记 ziplist 结束。

存储方式:采用连续内存块存储,元素紧密排列,无指针开销;通过“前一个 entry 长度”实现双向遍历(可从头部到尾部,也可从尾部到头部);元素值会根据类型和长度进行编码压缩,进一步节省内存。

ziplist 切换阈值(Hash场景):ziplist 仅适用于元素数量少、值小的场景,超过以下阈值会自动切换为对应结构,阈值支持通过配置文件自定义,默认值如下:

  • Hash 场景:当 Hash 中元素数量 ≥ 512 个(配置项 hash-max-ziplist-entries),或任意一个 field/value 字节数 ≥ 64 字节(配置项 hash-max-ziplist-value),触发 ziplist → hashtable 切换;

补充:hashtable(哈希表)底层结构与存储方式:hashtable 是 Redis 中用于快速查询的核心结构,适用于元素数量多、需高频查询/修改的场景(如 Hash、Set 元素较多时),Redis 中的 hashtable 采用“数组 + 链表/红黑树”的组合结构(Redis 7.0 后,链表长度超过阈值会转为红黑树)。

底层结构:由 hashtable 结构体和 dictEntry 结构体组成:

  • hashtable 结构体:包含一个 dictEntry 类型的数组(称为“桶”)、桶的数量(size)、已存储元素数量(used)、负载因子阈值等信息;
  • dictEntry 结构体:每个桶对应一个 dictEntry 链表(或红黑树),每个 dictEntry 存储一个键值对,包含“键指针”“值指针”“下一个 dictEntry 指针”(用于解决哈希冲突)。

存储方式:通过哈希函数将键(key)转换为哈希值,再对桶的数量取模,得到该键值对对应的桶索引;将 dictEntry 节点插入该桶的链表(或红黑树)中;若多个键哈希后落到同一个桶,形成哈希冲突,通过“链地址法”解决(将冲突节点串联成链表,Redis 7.0 后链表长度超过 8 转为红黑树,提升查询效率)。

hashtable 切换与 rehash 阈值:hashtable 作为切换后的目标结构,无反向切换逻辑,其自身会通过 rehash 动态调整容量,核心阈值基于负载因子(负载因子 = 已存储元素数量 / 哈希表桶数量):

  • 扩容阈值:负载因子 ≥ 1 且 Redis 未执行 BGSAVE/BGREWRITEAOF(后台持久化),或负载因子 ≥ 5(强制扩容),扩容后桶数量为原容量的最小2的幂倍数,且大于已存储元素数量×2;
  • 缩容阈值:负载因子 ≤ 0.1,缩容后桶数量为不小于已存储元素数量的最小2的幂倍数;
  • 补充:Redis 7.0 后,hashtable 中单个桶的链表长度超过 8 时,会转为红黑树,提升冲突元素的查询效率。

Hash 结构 rehash 操作详解:当 Hash 底层为 hashtable 时,为解决哈希冲突、平衡内存与效率,会通过 rehash(重新哈希)动态调整 hashtable 容量,Redis 采用渐进式 rehash 机制,避免一次性迁移数据导致的服务阻塞,具体操作流程如下:

  1. rehash 触发条件(基于负载因子判断,负载因子 = 已存储元素数量 / hashtable 容量):
  • 扩容触发:负载因子 ≥ 1 且 Redis 未执行 BGSAVE/BGREWRITEAOF(后台持久化操作),或负载因子 ≥ 5(强制扩容,无论是否处于持久化);扩容后新容量为原容量的最小2的幂倍数,且满足新容量 > 已存储元素数量 × 2;
  • 缩容触发:负载因子 ≤ 0.1 时,触发缩容;缩容后新容量为已存储元素数量的最小2的幂倍数,且保证新容量 ≥ 已存储元素数量,避免空间浪费。
  1. 渐进式 rehash 完整流程:
  • 初始化:Redis 的 hashtable 由两个哈希表(ht[0]、ht[1])组成,平时仅使用 ht[0];触发 rehash 后,为 ht[1] 分配新容量并初始化;
  • 渐进迁移:维护一个 rehashidx 计数器(初始为0),标记当前迁移进度;每次执行 Hash 相关命令(增删改查)时,除完成当前操作外,顺带将 ht[0] 中 rehashidx 对应索引桶的所有元素迁移到 ht[1],迁移完成后 rehashidx 自增;同时 Redis 会在定时任务中批量迁移部分元素,加速迁移进程;
  • 操作兼容:rehash 期间,查询操作会先查 ht[0],未找到再查 ht[1];插入操作直接写入 ht[1],避免 ht[0] 元素继续增加,加速迁移;删除、修改操作会根据元素所在的哈希表(ht[0] 或 ht[1])执行对应操作,保证数据一致性;
  • 完成切换:当 ht[0] 所有元素迁移至 ht[1] 后,释放 ht[0] 内存,将 ht[1] 赋值给 ht[0],重置 ht[1] 为空表,rehashidx 设为 -1,标记 rehash 结束。
  1. 核心优势:渐进式 rehash 将数据迁移压力分摊到多次命令操作和定时任务中,每次迁移耗时极短,避免了传统一次性 rehash 导致的服务阻塞,兼顾了效率与可用性。

核心命令

  • hset key field value:给指定哈希键设置一个属性和值;
  • hget key field:获取指定哈希键的某个属性值;
  • hgetall key:获取指定哈希键的所有属性和值;
  • hdel key field:删除指定哈希键的某个属性;
  • hexists key field:判断指定哈希键是否存在某个属性。

应用场景:存储对象类数据(用户信息、商品属性、订单详情),避免将对象序列化后存储为 String(减少序列化开销,支持单个属性修改)。

3. 列表(List)

List 是一个有序的字符串列表,支持从两端插入、删除元素,可实现先进先出(FIFO)或先进后出(LIFO)的队列/栈操作,元素可重复。

底层实现:同样采用 双底层结构自适应,兼顾内存和效率:

  1. 当列表中元素较少(默认少于512个)、元素值较小时,使用 ziplist(压缩列表) 存储;
  2. 当元素数量超过阈值时,自动转为 quicklist(快速列表)——由多个 ziplist 组成的双向链表,既减少内存碎片,又保证两端操作的高效性。

补充:quicklist(快速列表)底层结构与存储方式:quicklist 是 Redis 3.2 后引入的结构,用于替代 List 底层的“ziplist + 双向链表”组合,核心是“分段压缩 + 双向链表”,兼顾内存节省和操作效率。

底层结构:由 quicklist 结构体和 quicklistNode 结构体组成:

  • quicklist 结构体:维护整个快速列表的头部指针、尾部指针、元素总数量、quicklistNode 节点数量等信息;
  • quicklistNode 结构体:每个节点对应一个 ziplist(称为“片段”),包含“前一个节点指针”“后一个节点指针”“当前节点的 ziplist 指针”“ziplist 字节数”“ziplist 元素数量”等信息;每个 ziplist 片段的元素数量可配置(默认每个片段最多 8192 个元素)。

存储方式:将 List 中的元素分段存储到多个 ziplist 中,每个 ziplist 作为 quicklistNode 的一个片段;多个 quicklistNode 通过双向指针串联成链表,实现 List 的有序性;每个 ziplist 内部采用紧凑存储,节省内存,而 quicklistNode 之间的双向链表则保证了两端插入、删除的高效性(无需遍历所有元素)。

List 场景:当 List 中元素数量 ≥ 512 个(配置项 list-max-ziplist-entries),或任意一个元素字节数 ≥ 64 字节(配置项 list-max-ziplist-value),触发 ziplist → quicklist 切换;

quicklist 核心配置阈值(无切换,仅控制内部片段):quicklist 是 List 切换后的默认结构,无反向切换逻辑,其内部 ziplist 片段的大小可通过配置控制,默认值如下:

  • 配置项 list-max-ziplist-size:默认值 -2(对应 8KB),限制每个 quicklistNode 中 ziplist 片段的最大大小(负数表示固定字节数,正数表示最大元素数量);
  • 配置项 list-compress-depth:默认值 0,表示不压缩;正数表示从 quicklist 两端向中间跳过的节点数,剩余节点会进行压缩存储,进一步节省内存。

核心命令

  • lpush key value1 value2...:从列表左侧插入一个或多个元素;
  • rpush key value1 value2...:从列表右侧插入一个或多个元素;
  • lpop key:从列表左侧弹出一个元素;
  • rpop key:从列表右侧弹出一个元素;
  • lrange key start end:获取列表中从 start 到 end 索引的元素(0 表示第一个元素,-1 表示最后一个元素)。

应用场景:消息队列(简单的生产消费模型)、最新消息展示(如朋友圈动态、评论列表)、栈/队列实现(如任务队列)。

4. 集合(Set)

Set 是一个无序的字符串集合,元素不可重复,支持集合间的交集、并集、差集操作,判断元素是否存在的时间复杂度为 O(1)。

底层实现:根据元素类型和数量自适应,分为两种:

  1. 当集合中元素均为整数、且数量较少(默认少于512个)时,使用 intset(整数集合) 存储,极大节省内存
  2. 当元素包含非整数、或数量超过阈值时,使用 hashtable(哈希表) 存储,其中键是集合元素,值为 null(仅用于判断元素是否存在)。

补充:intset(整数集合)底层结构与存储方式:intset 是 Redis 专门为存储整数类型元素设计的紧凑结构,适用于 Set 中元素均为整数且数量较少的场景,核心是“有序、无重复、紧凑存储”。

底层结构:由 3 个固定头部 + 整数数组组成,所有数据连续存储在一块内存中,无冗余开销:

  • encoding:4 字节,记录整数的编码方式,分为三种(INTSET_ENC_INT16:存储 16 位整数,范围 -32768~32767;INTSET_ENC_INT32:32 位整数;INTSET_ENC_INT64:64 位整数);
  • length:4 字节,记录整数集合中元素的数量;
  • contents:可变长度的整数数组,存储具体的整数元素,数组内元素按升序排列,且无重复;数组的编码方式由 encoding 决定,所有元素使用同一编码。

存储方式:采用连续内存块存储整数数组,元素按升序排列,便于快速查找(二分查找,时间复杂度 O(log n));当插入新元素时,若新元素的编码比当前 encoding 更宽(如当前是 16 位,插入 32 位整数),会先对整个数组进行编码升级,再插入元素;元素无重复,插入时会先判断是否存在,避免重复存储。

intset 切换阈值(Set 场景):intset 仅适用于 Set 中元素均为整数且数量少的场景,满足以下条件之一会触发切换为 hashtable,默认阈值如下:

  • 数量阈值:Set 中整数元素数量 ≥ 512 个(配置项 set-max-intset-entries);
  • 类型阈值:向 intset 中插入非整数类型元素(如字符串、浮点数),无论数量多少,立即触发切换;
  • 切换特性:切换为一次性操作,切换后结构不可逆,原 intset 内存会被立即释放。

核心命令

  • sadd key member1 member2...:向集合中添加一个或多个元素(重复元素会自动去重);
  • smembers key:获取集合中的所有元素;
  • sismember key member:判断元素是否在集合中;
  • srem key member:从集合中删除指定元素;
  • sinter key1 key2:求两个集合的交集(共同元素);
  • sunion key1 key2:求两个集合的并集(所有元素,去重);
  • sdiff key1 key2:求两个集合的差集(key1 中有、key2 中没有的元素)。

应用场景:去重(如用户签到记录、商品标签去重)、好友关系(共同好友、好友推荐)、抽奖活动(随机抽取集合中的元素)。

5. 有序集合(Sorted Set / ZSet)

ZSet 是 Set 的增强版,元素不可重复,但每个元素会关联一个“分数(score)”,Redis 会根据分数对元素进行升序或降序排序,兼顾了集合的去重和有序性。

底层实现:采用 跳表(skiplist)+ 哈希表 组合实现,两者协同工作:

  1. 哈希表:用于存储元素与分数的映射关系,可快速查询指定元素的分数(时间复杂度 O(1));

跳表:用于根据分数对元素进行排序,支持快速获取排序后的元素、范围查询(时间复杂度 O(log n)),兼顾排序和查询效率。

补充:跳表(skiplist)底层结构与时间复杂度详解:跳表是一种有序的数据结构,核心是通过“分层索引”实现高效的查找、插入、删除操作,Redis 中的跳表是 ZSet 排序功能的核心支撑,结构设计简洁且性能优异。

1. 跳表底层结构:跳表由“表头 + 多层链表”组成,每层链表都是有序的(按 ZSet 元素分数升序排列),底层为原始链表(存储所有元素),上层为索引链表(用于快速定位),具体结构如下:

  • 表头(header):包含所有层级的索引指针,指向每一层链表的第一个节点;
  • 节点(node):每个节点包含“元素值(ZSet 中的 member)”“分数(score)”“向下一层的指针”“同一层的下一个节点指针”;
  • 层级(level):每个节点的层级是随机生成的(Redis 中默认最大层级为 32),层级越高,节点数量越少,索引效率越高;底层链表(层级 1)包含所有 ZSet 元素,上层链表(层级 ≥2)为索引,仅存储部分节点,用于快速跳过无效元素。

2. 核心操作与时间复杂度:跳表的所有操作均基于“分层查找”逻辑,时间复杂度均为 O(log n)(n 为跳表中元素数量),具体如下:

  • 查找操作:从表头的最高层级开始,依次与当前层级的下一个节点分数对比,若下一个节点分数小于目标分数,则继续前进;若大于目标分数,则下降一层继续查找,直至底层链表,找到目标元素。整个过程类似“二分查找”,通过上层索引快速缩小查找范围,时间复杂度 O(log n);
  • 插入操作:先通过查找操作确定插入位置,随机生成当前节点的层级,然后将节点插入到对应层级的链表中,同时更新各层级的指针(保证链表有序),时间复杂度 O(log n)(查找插入位置 O(log n),插入操作 O(1));
  • 删除操作:先通过查找操作定位到目标节点,然后删除该节点在所有层级的对应位置,更新各层级的指针,避免链表断裂,时间复杂度 O(log n)(查找目标节点 O(log n),删除操作 O(1));
  • 范围查询操作:如 ZSet 的 zrange、zrangebyscore 命令,通过跳表的分层索引快速定位到范围起始位置,然后沿底层链表依次遍历获取所有符合条件的元素,时间复杂度 O(log n + k)(k 为符合条件的元素数量),效率远高于普通链表的 O(n)。

3. 优势说明:跳表的时间复杂度与红黑树相当(均为 O(log n)),但跳表的插入、删除操作实现更简单,无需像红黑树那样进行复杂的旋转调整,且范围查询效率更优,这也是 Redis 选择跳表作为 ZSet 排序核心的关键原因。

核心命令

  • zadd key score1 member1 score2 member2...:向有序集合中添加元素及对应分数;
  • zrange key start end [withscores]:按分数升序获取从 start 到 end 的元素(可携带分数);
  • zrevrange key start end [withscores]:按分数降序获取元素;
  • zscore key member:获取指定元素的分数;
  • zrem key member:删除有序集合中的指定元素;
  • zrangebyscore key min max:获取分数在 [min, max] 范围内的元素。

应用场景:排行榜(如游戏战力榜、文章热度榜)、带权重的消息队列、范围查询(如查询分数在 80-100 之间的用户)。

二、常用扩展数据结构(明确底层基于的基本数据结构)

1. 位图(Bitmap)

Bitmap 并非独立的数据结构,底层基于 String 数据结构实现,本质是对 String 类型的二进制位进行操作。每个字节的 8 个比特位可表示一个布尔值(0 或 1),适合存储大量的布尔型数据,极大节省内存。

核心命令:setbit(设置指定位置的比特位)、getbit(获取指定位置的比特位)、bitcount(统计比特位为 1 的数量)、bitop(对多个 Bitmap 进行位运算)。

应用场景:用户签到(1 表示签到,0 表示未签到)、在线状态(1 在线,0 离线)、统计活跃用户、连续签到天数统计。

2. 基数统计(HyperLogLog)

HyperLogLog 用于统计一个集合中“不重复元素的个数”(基数统计),底层基于 String 数据结构实现,通过存储基数估算所需的比特串(固定约 12KB 内存),实现海量数据的基数统计,即使统计上亿条数据,内存占用也不会增加,牺牲少量精度(误差在 0.81% 以内)。

核心命令:pfadd(向 HyperLogLog 中添加元素)、pfcount(统计基数)、pfmerge(合并多个 HyperLogLog 实例,统计合并后的基数)。

应用场景:网站 UV 统计(独立访客数)、APP 日活/月活统计、海量数据去重计数(如用户浏览记录去重统计)。

3. 地理信息(Geo)

Geo 用于存储和操作地理坐标数据(如经纬度),支持距离计算、范围查询等功能,底层基于 ZSet 数据结构实现——将经纬度通过特定算法转换为 ZSet 的“分数(score)”,元素为地理位置标识,借助 ZSet 的排序特性实现地理相关操作。

核心命令

  • geoadd key longitude latitude member:添加地理坐标(经度、纬度、地理位置标识);
  • geodist key member1 member2 [unit]:计算两个地理位置之间的距离;
  • georadius key longitude latitude radius unit:查询指定坐标范围内的地理位置
  • geohash key member:获取地理位置的 geohash 编码(用于压缩存储、近似查询)。

应用场景:附近的人、外卖商家定位、地图导航相关功能、地理位置范围筛选。

4. 流(Stream)

Stream 是 Redis 5.0 新增的高级数据结构,用于实现高性能的消息队列,支持消息持久化、分组消费、重复消费控制等功能,底层基于 listpack(列表压缩包)数据结构实现(Redis 5.0+ 替代 ziplist,更高效的内存存储方式),每个 Stream 由多个消息组成,每个消息包含唯一 ID 和键值对内容。

核心命令

  • xadd key ID field value...:向 Stream 中添加一条消息(ID 可自动生成或手动指定);
  • xread [COUNT count] [BLOCK milliseconds] STREAMS key... ID...:读取 Stream 中的消息(支持阻塞读取);
  • xgroup create key groupname ID:创建消费组;
  • xreadgroup GROUP groupname consumername [COUNT count] [BLOCK milliseconds] STREAMS key... ID...:消费组读取消息;
  • xack key groupname ID...:确认消息已消费(避免重复消费)。

应用场景:高可靠消息队列(如订单消息、通知消息)、分布式系统中的事件流处理、日志收集与分发。

三、核心总结(底层数据结构汇总)

Redis 数据结构的设计核心是“内存高效 + 操作高效”,不同结构的底层实现均围绕这一核心,以下是所有指定数据结构的底层数据结构汇总,方便快速查阅:

  • String:底层为 简单动态字符串(SDS);
  • Hash:底层为 ziplist(元素少、值小时)、hashtable(超过阈值时);
  • List:底层为 ziplist(元素少、值小时)、quicklist(超过阈值时);
  • Set:底层为 intset(整数、数量少时)、hashtable(其他情况);
  • ZSet:底层为 跳表(skiplist)+ 哈希表;
  • Bitmap:底层基于 String 数据结构;
  • HyperLogLog:底层基于 String 数据结构;
  • Geo:底层基于 ZSet 数据结构;
  • Stream:底层为 listpack 数据结构。

选择合适的数据结构,本质是匹配其底层实现的优势与业务场景,从而最大化 Redis 的性能和内存利用率。

ps:如果这篇帖子对于还在找工作和找实习的你有所帮助,可以关注我,给本贴点赞、评论、收藏并订阅专栏;同时不要吝啬您的花花

Redis常用的数据结构 文章被收录于专栏

Redis 作为高性能键值数据库,核心在于丰富的数据结构。本专栏聚焦String、Hash、List、Set、ZSet、Bitmap、HyperLogLog 等常用类型,从底层原理、使用场景到实战示例,清晰讲解每种结构的优缺点与最佳实践。帮你快速掌握如何用对数据结构,提升缓存、限流、排行榜、消息队列等业务场景的开发效率,写出更稳定、高效的 Redis 应用。

全部评论

相关推荐

实习划水≠简历空白!双非生零核心项目,也能把摸鱼经历包装成HR亮眼加分项作为双非应届生,我太懂那种实习划水的焦虑了:每天到工位打卡,端茶倒水、整理文件、录入数据、打印复印、帮同事跑腿,偶尔摸鱼刷会儿招聘软件,一天就浑浑噩噩过去,没有核心项目、没有亮眼成果、没有拿得出手的业绩,眼看要写简历、准备面试,看着空白的实习经历,急得整夜睡不着。我曾一度觉得,这段划水实习就是简历上的“污点”,写上去怕HR嘲笑打杂摸鱼,不写又没有任何实习经历支撑,连面试门槛都够不着,对比身边同学满满当当的项目经历,自卑又无助,甚至觉得自己双非学历+划水实习,注定求职无路。但后来我才发现,没有真正划水的实习,只有不会包装的经历,哪怕每天都是琐碎杂活、摸鱼度日,只要找对方法,照样能把不起眼的日常,包装成HR认可的靠谱经历,尤其双非生,这段经历更是求职路上不可或缺的底气。那段划水实习的日子,我每天都在自我内耗:一边愧疚于没有好好实习,一边恐慌于简历无处下笔,直到求职季迫在眉睫,我逼着自己梳理每天的工作细节,摸索出一套划水实习包装心法,把看似毫无价值的摸鱼日常,转化为简历上的加分内容,靠着这份包装后的简历,顺利拿下面试机会,今天就把这份掏心窝的干货、共情满满的经验全部分享,帮所有和我一样实习划水、没项目没成果的双非同学,摆脱简历焦虑,逆袭出圈。先和自己和解:实习划水不是你的错,双非生更不必自我否定很多双非同学和我一样,找实习本就比名校生难,能拿到一份实习机会已经用尽心力,大多都是基础执行岗、辅助岗,根本接触不到核心项目,不是我们想划水,而是没有接触核心工作的机会,每天只能做流程化、琐碎化的杂活,这不是能力不行,更不是我们不努力,不必因为实习划水就贬低自己,双非生的每一份实习经历,都有可挖掘的价值。HR其实非常清楚,应届生、双非生的实习,本就以基础辅助为主,他们不会苛求你做出惊天成果,更看重的是你的工作态度、执行力、细节把控、学习能力,划水实习里的琐碎工作,恰恰能体现这些底层能力,我们要做的,不是隐瞒这段经历,而是学会“翻译”工作内容,把摸鱼打杂的大白话,换成HR爱看的专业表述,把被动划水,变成主动执行、高效辅助。划水实习包装核心公式:任何杂活都能套用,零经验也能上手我摸索出一个万能包装公式,不管是整理文件、录入数据、跑腿打杂,还是摸鱼式协助工作,都能直接套用,不用造假、不用夸大,只做专业转化,完全适配双非生简历,安全又靠谱:专业动词+工作内容+工具使用+价值成果(量化/软性)专业动词:摒弃“做、弄、整理、帮忙”等大白话,换成“协助、统筹、统计、归档、优化、对接、执行、维护、梳理”等职场专业词汇;工作内容:把模糊的打杂,细化成具体事项,不说“整理文件”,而说“部门日常文档分类、资料校对、台账梳理”;工具使用:突出办公软件熟练度,比如Excel数据统计、Word文档排版、PPT简易制作、飞书/企业微信信息同步,体现基础职场技能;价值成果:没有亮眼数据就写软性成果,比如“保障部门日常工作高效运转”“提升文件检索效率”“协助团队完成基础事务,释放核心成员精力”,有简单数据就量化,更有说服力。高频划水场景包装:从大白话到简历版,直接复制不踩雷这些都是我实习划水时每天做的事,也是绝大多数双非生的日常,手把手教你改写,看完就能用:场景1:日常整理文件、打印复印、归档杂活划水大白话:每天整理文件,打印资料,帮同事归档东西,没事就摸鱼。简历包装版:负责部门日常文档标准化整理、分类归档与资料校对,完成文件排版、打印输出等行政辅助工作,优化文档归档逻辑,提升团队文件检索效率,保障部门基础行政事务有序开展,全程零失误完成日常执行工作。场景2:录入数据、制作简单表格、统计信息划水大白话:录数据,做表格,没事就玩手机,没什么技术含量。简历包装版:协助团队完成业务数据收集、录入与核对工作,使用Excel完成基础表格制作、数据统计与简单清洗,保障数据录入准确性,为团队日常报表输出提供基础数据支持,高效完成基础数据类事务。场景3:协助同事、跑腿对接、会议打杂划水大白话:帮同事跑腿,开会记笔记,端茶倒水,没啥实际工作。简历包装版:协助团队完成跨岗位基础对接、会议前期准备与会议纪要简易梳理,负责日常事务跟进与信息同步,配合团队成员完成基础执行工作,保障团队日常协作顺畅,培养高效执行力与沟通意识。场景4:摸鱼式协助活动、简单物料准备划水大白话:帮忙准备活动物料,打打下手,大部分时间在摸鱼。简历包装版:协助完成小型活动执行与物料筹备工作,负责物资清点、现场基础布置与流程配合,助力活动顺利落地,积累基础活动执行与现场协作经验。双非生划水实习包装避坑干货:这些错误千万别犯1、绝对不虚构成果:双非生简历最忌讳造假,没有项目就不写项目,没有数据就写软性价值,真诚永远是加分项,一旦被面试官识破,直接失去机会;2、不堆砌无关内容:划水经历不用写太多,精选2-3条最能体现执行力、责任心的内容,控制在1-2行,简洁精炼,重点突出;3、贴合岗位JD包装:投递不同岗位,侧重不同经历,比如投运营岗,突出数据统计、活动协助;投行政/文职岗,突出文档整理、对接协同,让经历和岗位高度匹配;4、不暴露划水状态:绝不写“摸鱼、空闲、打杂”等词汇,把被动摸鱼,转化为主动完成基础工作,体现踏实靠谱的态度。从焦虑到逆袭:我的划水实习包装实战,双非生可照搬我最开始包装简历时,词穷又没思路,明明按照公式改写,还是显得生硬空洞,完全没有亮点,投出去的简历依旧石沉大海,焦虑到崩溃。后来身边同学推荐,我用泡泡小程序AiCV简历王优化划水实习经历,彻底解决了我的简历难题。这个小程序完全懂双非生的痛点,不用我费心琢磨专业词汇,只要输入我原本的大白话实习内容,就能自动匹配岗位JD,用专业话术重新梳理,量化隐性价值,把我那段划水打杂的经历,包装成“高效执行、团队辅助、细节把控”的靠谱经历,还精准植入HR看重的关键词,原本平平无奇的简历,瞬间变得专业亮眼,投递没几天就收到了面试邀约。它不会虚构任何内容,只是把我们忽略的工作价值提炼出来,让HR看到我们的踏实与潜力,对于双非生、实习划水的同学来说,不用熬夜苦思冥想,就能把摸鱼经历变成加分项,既省心又靠谱,是求职路上的刚需工具。靠着这份优化后的简历,面试时面试官主动询问我的实习经历,我按照包装后的逻辑从容回答,全程没有因为学历和实习经历露怯,最终顺利拿到offer,彻底摆脱了实习划水的求职焦虑。写在最后:双非生,你的每一段经历都不该被辜负实习划水不可怕,双非学历不可怕,可怕的是你自我否定,觉得自己一无是处,亲手放弃每一个机会。我们没有名校光环,没有优质实习资源,只能靠自己一点点挖掘价值、包装经历,把看似不起眼的日常,变成求职路上的筹码。这段划水实习,或许没有高光成果,但它教会我们职场基础、教会我们踏实做事、教会我们如何呈现自己,这些都是HR看重的宝贵品质。不必焦虑,不必自卑,找对包装方法,优化好简历,带着真诚与底气去面试,双非生照样能打破学历枷锁,逆袭拿到心仪offer。你的每一份努力,每一次对经历的打磨,都不会被辜负,那些看似划水的日子,终会成为你上岸的铺路石。
AI时代,哪个岗位还有“...
点赞 评论 收藏
分享
评论
点赞
1
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务