一文吃透Redis 10种数据类型[面试](String、List、Hash、Set、ZSet...Bitmap/Stream)

3 阅读15分钟

一文吃透Redis 10种数据类型[面试]

目录

  • 一、Redis 数据类型的本质
  • 二、String 类型!
  • 三、List 类型!
  • 四、Hash 类型!
  • 五、Set 类型!
  • 六、ZSet(Sorted Set)类型!
  • 七、Bitmap 类型
  • 八、HyperLogLog:省空间的 “基数统计器”
  • 九、GEO:地理位置的 “LBS 服务工具”
  • 十、Stream类型!

一、Redis 数据类型的本质

Redis 对外提供的 5 种基础数据类型(String、List、Hash、Set、ZSet),本质上是对底层数据结构的封装,目的是:

  • 让上层使用更简单
  • 让底层存储和操作更高效

其他数据类型:BinMAP,Geo,hyperloglog,stream。

二、String 类型!

1. 对外表现

  • 是 Redis 最基础的 key-value 结构
  • value 不仅可以是字符串,还可以是数字(整数 / 浮点数)、JSON、二进制数据(图片、音频)
  • 最大可存储 512MB

2. 内部实现(重点)

String 的底层数据结构是intSDS

SDS(简单动态字符串)是 Redis 自己实现的字符串结构,相比 C 原生字符串的优势:

  • 可以保存文本数据,保存二进制数据:用 len 属性判断长度,而不是 \0,可以存图片等二进制数据
  • 获取长度的时间复杂度 O (1) :C 字符串是 O (n),SDS 直接读 len
  • 自动扩容:拼接字符串时自动检查空间,避免缓冲区溢出

字符串对象的 encoding(编码)有三种:

  • int:当 value 是可以用 long 表示的整数值时,直接把数值存在 ptr 里,节省内存
  • embstr:当字符串长度 ≤ 44 字节(Redis 5.0+)时,用一次内存分配,把 redisObject 和 SDS 存在连续内存空间里,性能更好
  • raw:当字符串长度 > 44 字节时,用两次内存分配,redisObject 和 SDS 分开存储

embstr的缺点:在字符串长度增加时需要重新分配内存,整个redisobject和SDS都需要重新分配空间。所以embstr的字符串对象是只读的,如果要对embstr编码进行修改,程序就会先把这个对象编码转换为raw,然后再执行修改命令。

3. 常用命令

  • 基础操作:SET/GET/EXISTS/STRLEN/DEL
# 设置 key-value 类型的值
>SET name lin
OK
# 根据 key 获得对应的 value
>GET name
"lin"
# 判断某个 key 是否存在
>EXISTS name
(integer) 1
# 返回 key 所储存的字符串值的长度
> STRLEN name
(integer) 3
# 删除某个 key 对应的值
> DEL name
(integer) 1
  • 批量操作:MSET/MGET
# 批量设置 key-value 类型的值
> MSET key1 value1 key2 value2 
OK
# 批量获取多个 key 对应的 value
> MGET key1 key2 
1) "value1"
2) "value2"
  • 计数器:INCR/INCRBY/DECR/DECRBY(原子操作,适合计数场景)
# 设置 key-value 类型的值
> SET number 0
OK
# 将 key 中储存的数字值增一
>INCR number
(integer) 1
# 将key中存储的数字值加 10
> INCRBY number 10
(integer) 11
# 将 key 中储存的数字值减一
> DECR number
(integer) 10
# 将key中存储的数字值键 10
> DECRBY number 10
(integer) 0
  • 过期与不存在插入:EXPIRE/TTL/SETNX/ SETEX
# 设置 key-value 类型的值
> SET number 0
OK
# 将 key 中储存的数字值增一
> INCR number
(integer) 1
# 将key中存储的数字值加 10
> INCRBY number 10
(integer) 11
# 将 key 中储存的数字值减一
> DECR number
(integer) 10
# 将key中存储的数字值键 10
> DECRBY number 10
(integer) 0
​
# 不存在就插入(not exists)
>SETNX key value
(integer) 1

4. 典型应用场景

  • 缓存对象:直接存 JSON(SET user:1 '{"name":"xiaolin","age":18}'),或用 MSET 拆分字段
  • 常规计数:计算文章阅读量、点赞数、库存(利用 INCR 的原子性)
  • 分布式锁SET lock:key unique_value NX PX 10000,配合 Lua 脚本保证解锁原子性
  • • lock_key 就是 key 键;
    • unique_value 是客户端生成的唯一的标识;
    • NX 代表只在 lock_key 不存在时,才对 lock_key 进行设置操作;
    • PX 10000 表示设置 lock_key 的过期时间为 10s,这是为了避免客户端发生异常而无法释放锁。
    
  • 共享 Session信息:把用户 Session 存在 Redis,解决分布式系统下 Session 共享问题

三、List 类型!

1. 对外表现

  • 是简单的字符串列表,按插入顺序排序
  • 可以从头部(LPUSH)或尾部(RPUSH)添加元素
  • 最大长度为 2³² - 1(约 40 亿)

2. 内部实现(重点)

List 类型的底层数据结构是由双向链表或压缩列表实现的:

  • Redis 3.2 之前:元素少且小用 ziplist(压缩列表),否则用双向链表
  • Redis 3.2 之后:统一用 quicklist(双向链表 + ziplist 的混合结构),兼顾内存和性能

3. 常用命令

  • 插入:LPUSH/RPUSH
  • 弹出:LPOP/RPOP/BLPOP/BRPOP(阻塞式弹出,避免 CPU 空转)
  • 范围查询:LRANGE key start stop
  • # 将一个或多个值value插入到key列表的表头(最左边),最后的值在最前面
    LPUSH key value 
    [value ...] 
    # 将一个或多个值value插入到key列表的表尾(最右边)
    RPUSH key value 
    [value ...]
    # 移除并返回key列表的头元素
    LPOP key     
    # 移除并返回key列表的尾元素
    RPOP key 
    ​
    # 返回列表key中指定区间内的元素,区间以偏移量start和stop指定,从0开始
    LRANGE key start stop
    ​
    # 从key列表表头弹出一个元素,没有就阻塞timeout秒,如果timeout=0则一直阻塞
    BLPOP key 
    [key ...] timeout
    # 从key列表表尾弹出一个元素,没有就阻塞timeout秒,如果timeout=0则一直阻塞
    BRPOP key 
    [key ...] timeout
    

4. 典型应用场景:消息队列

List 可以满足消息队列的三个需求:

  1. 消息保序LPUSH + RPOP(或 RPUSH + LPOP)实现先进先出
  2. 处理重复消息:生产者为每条消息生成全局唯一 ID,消费者记录已处理的 ID 进行去重
  3. 保证消息可靠性:使用 BRPOPLPUSH,把消息从主队列弹出后,先插入到备份队列,消费者处理完再删除备份

List 作为消息队列的缺陷

  • 不支持多个消费者消费同一条消息(消息被拉取后就从队列中删除)
  • 不支持消费组,这也是 Redis 5.0 推出 Stream 类型的原因.

四、Hash 类型!

1. 对外表现

  • 是一个键值对集合,形式为 value=[{field1, value1}, ..., {fieldN, valueN}]
  • 非常适合存储对象(如用户信息、商品信息)

2. 内部实现(重点)

Hash 类型的底层数据结构是由压缩列表或哈希表实现的:

  • 当哈希表元素个数 ≤ 512 且所有值 ≤ 64 字节时,用 ziplist(或 Redis 7.0+ 的 listpack)存储,节省内存
  • 不满足条件时,用 hashtable(字典)存储,查询性能 O (1)

3. 常用命令

  • 存储:HSET/HMSET
  • 获取:HGET/HMGET/HGETALL
  • 删除:HDEL
  • 计数:HLEN(返回 field 数量)/HINCRBY(对某个 field 的值原子递增)
# 存储一个哈希表key的键值
HSET key field value   
# 获取哈希表key对应的field键值
HGET key field
​
# 在一个哈希表key中存储多个键值对
HMSET key field value 
[field value...] 
# 批量获取哈希表key中多个field键值
HMGET key field 
[field ...]       
# 删除哈希表key中的field键值
HDEL key field 
[field ...]    
​
# 返回哈希表key中field的数量
HLEN key       
# 返回哈希表key中所有的键值
HGETALL key 
​
# 为哈希表key中field键的值加上增量n
HINCRBY key field n  

4. 典型应用场景

  • 缓存对象:相比 String+JSON,Hash 可以单独修改某个字段(如只修改用户的 age,而不用重写整个 JSON), 一般对象用 String + Json 存储,对象中某些频繁变化的属性可以考虑抽出来用 Hash 类型存储。

  • 购物车:

    • key = 用户 ID
    • field = 商品 ID
    • value = 商品数量
    • HINCRBY 修改数量,HLEN 统计商品总数,HGETALL 获取所有商品

五、Set 类型:无序、去重的 “集合”!

1. 对外表现

  • 是一个无序且唯一的键值集合,元素不重复、不排序
  • 支持单个集合的增删改查,也支持多个集合的交集、并集、差集运算
  • 最大可存储 2³² - 1 个元素
  • Set 类型和 List 类型的区别如下: • List 可以存储重复元素,Set 只能存储非重复元素; • List 是按照元素的先后顺序存储元素的,而 Set 则是无序方式存储元素的。

2. 内部实现

Set 类型的底层数据结构是由哈希表或整数集合实现的:

  • 当集合中元素都是整数且个数 ≤ 512 时,用 整数集合(intset) 存储,节省内存
  • 不满足条件时,用 哈希表(hashtable) 存储,查询和增删都是 O (1)

3. 常用命令

  • 基础操作:SADD/SREM/SMEMBERS/SCARD/SISMEMBER
  • 随机操作:SRANDMEMBER(随机取,不删除)/SPOP(随机取,删除)
  • 集合运算:SINTER/SUNION/SDIFF(及对应的 STORE 版本)
# 往集合key中存入元素,元素存在则忽略,若key不存在则新建
SADD key member 
[member ...]
# 从集合key中删除元素
SREM key member 
[member ...] 
# 获取集合key中所有元素
SMEMBERS key
# 获取集合key中的元素个数
SCARD key
​
# 判断member元素是否存在于集合key中
SISMEMBER key member
​
# 从集合key中随机选出count个元素,元素不从key中删除
SRANDMEMBER key 
[count]
# 从集合key中随机选出count个元素,元素从key中删除
SPOP key 
[count]
​
# 交集运算
SINTER key 
[key ...]
# 将交集结果存入新集合destination中
SINTERSTORE destination key 
[key ...]
​
# 并集运算
SUNION key 
[key ...]
# 将并集结果存入新集合destination中
SUNIONSTORE destination key 
[key ...]
​
# 差集运算
SDIFF key 
[key ...]
# 将差集结果存入新集合destination中
SDIFFSTORE destination key 
[key ...]

4. 典型应用场景

  • 点赞 / 收藏:用文章 ID 做 key,用户 ID 做 member,保证一个用户只能点一次赞
  • 共同关注 / 好友推荐:用 SINTER 求共同关注,用 SDIFF 做推荐
  • 抽奖活动:用 SPOP 实现不重复抽奖,用 SRANDMEMBER 实现可重复抽奖

六、ZSet(Sorted Set)类型!

1. 对外表现

  • 是一个有序集合,每个元素有一个 score(分值)用于排序
  • 元素唯一,但 score 可以重复
  • 支持按 score 或按字典序(ZRANGEBYLEX)排序和分页

2. 内部实现

Zset 类型的底层数据结构是由压缩列表或跳表实现的:

  • 当元素个数 ≤ 128 且每个元素值 ≤ 64 字节时,用 压缩列表(ziplist /listpack) 存储
  • 不满足条件时,用 跳表(skiplist)+ 哈希表 存储,兼顾排序和查询性能

3. 常用命令

  • 基础操作:ZADD/ZREM/ZSCORE/ZCARD/ZINCRBY
  • 范围查询:ZRANGE/ZREVRANGE(按 score 排序)/ZRANGEBYSCORE(按 score 区间)/ZRANGEBYLEX(按字典序)
# 往有序集合key中加入带分值元素
ZADD key score member 
[[score member]...]   
# 往有序集合key中删除元素
ZREM key member 
[member...]                 
# 返回有序集合key中元素member的分值
ZSCORE key member
# 返回有序集合key中元素个数
ZCARD key 

# 为有序集合key中元素member的分值加上increment
ZINCRBY key increment member 

# 正序获取有序集合key从start下标到stop下标的元素
ZRANGE key start stop 
[WITHSCORES]
# 倒序获取有序集合key从start下标到stop下标的元素
ZREVRANGE key start stop 
[WITHSCORES]

# 返回有序集合中指定分数区间内的成员,分数由低到高排序。
ZRANGEBYSCORE key min max 
[WITHSCORES] [LIMIT offset count]

# 返回指定成员区间内的成员,按字典正序排列, 分数必须相同。
ZRANGEBYLEX key min max 
[LIMIT offset count]
# 返回指定成员区间内的成员,按字典倒序排列, 分数必须相同
ZREVRANGEBYLEX key max min 
[LIMIT offset count]
  • 聚合运算:ZUNIONSTORE/ZINTERSTORE(并集 / 交集,可按权重聚合 score)
# 并集计算(相同元素分值相加),numberkeys一共多少个key,WEIGHTS每个key对应的分值乘积
ZUNIONSTORE destkey numberkeys key 
[key...] 
# 交集计算(相同元素分值相加),numberkeys一共多少个key,WEIGHTS每个key对应的分值乘积
ZINTERSTORE destkey numberkeys key 
[key...]

4. 典型应用场景

  • 排行榜:学生成绩、游戏积分、视频播放量、商品销量
  • 排序展示:最新列表、热门列表(用时间戳或权重做 score)
  • 范围筛选:按手机号段、姓名首字母等字典序筛选

七、Bitmap 类型

1. 对外表现

  • 本质是 String 类型的特殊用法,把字符串的每个 bit 位当作一个独立的开关(0/1)
  • 时间复杂度 O (1),空间占用极小,特别适合海量二值状态统计

2. 内部实现

它的底层数据结构是 String 的 SDS 结构的数据类型,Redis 把字节数组的每个 bit 位利用起来,形成一个 “bit 数组”

  • 每个 bit 只存 0 或 1,1 个字节就能存 8 个状态

3. 常用命令

  • 基础操作:SETBIT/GETBIT/BITCOUNT(统计 1 的个数)
# 设置值,其中value只能是 01
SETBIT key offset value
​
# 获取值
GETBIT key offset
​
# 获取指定范围内值为 1 的个数
# startend 以字节为单位
BITCOUNT key start end
  • 位运算:BITOP(AND/OR/XOR/NOT,用于多日连续统计)
  • 定位:BITPOS(找到第一个 0 或 1 的位置,用于首次打卡统计)
# BitMap间的运算
# operations 位移操作符,枚举值
  AND 与运算 &
  OR 或运算 |
  XOR 异或 ^
  NOT 取反 ~
# result 计算的结果,会存储在该key中
# key1 … keyn 参与运算的key,可以有多个,空格分割,not运算只能一个key
# 当 BITOP 处理不同长度的字符串时,较短的那个字符串所缺少的部分会被看作 0。返回值是保存到 destkey 的字符串的长度(以字节byte为单位),和输入 key 中最长的字符串长度相等。
BITOP [operations] [result] [key1] [keyn…]# 返回指定key中第一次出现指定value(0/1)的位置
BITPOS [key] [value]

4. 典型应用场景

  • 签到统计:用用户 ID 做 key,日期做 offset,1 表示签到,0 表示未签到
  • 用户登录状态:用一个 key 存所有用户的在线状态,offset 是用户 ID,1 表示在线
  • 连续打卡统计:对多天的 Bitmap 做 AND 运算,统计连续打卡的用户数

八、HyperLogLog:省空间的 “基数统计器”

1. 对外表现

  • 是一种基数统计类型,用于统计集合中不重复元素的个数(去重计数)。
  • 统计规则基于概率算法,不提供精确值,标准误差率为 0.81%
  • 最大优势:无论统计多少元素,内存占用固定为 12 KB,可统计近 2^64 个不同元素。

2. 内部实现

  • 核心是用极小的固定空间换取近似统计结果。

3. 常用命令

  • PFADD key element [element ...]:向 HyperLogLog 中添加元素。
  • PFCOUNT key [key ...]:返回基数的估算值。
  • PFMERGE destkey sourcekey [sourcekey ...]:合并多个 HyperLogLog。
一、HyperLogLog:省空间的 “基数统计器”
1. 对外表现
是一种基数统计类型,用于统计集合中不重复元素的个数(去重计数)。
统计规则基于概率算法,不提供精确值,标准误差率为 0.81%。
最大优势:无论统计多少元素,内存占用固定为 12 KB,可统计近 2^64 个不同元素。
2. 内部实现
底层涉及复杂的概率数学,无需深究细节,核心是用极小的固定空间换取近似统计结果。
3. 常用命令
PFADD key element [element ...]:向 HyperLogLog 中添加元素。
PFCOUNT key [key ...]:返回基数的估算值。
PFMERGE destkey sourcekey [sourcekey ...]:合并多个 HyperLogLog。
4. 典型应用场景
百万级网页 UV 统计:
传统 Set/Hash 在大数据量下内存消耗极大,而 HyperLogLog 仅用 12 KB 即可完成统计。
注意:若业务需要绝对精确的统计结果,仍需使用 Set 或 Hash。

4. 典型应用场景

  • 百万级网页 UV 统计:

    • 传统 Set/Hash 在大数据量下内存消耗极大,而 HyperLogLog 仅用 12 KB 即可完成统计。
    • 注意:若业务需要绝对精确的统计结果,仍需使用 Set 或 Hash。

九、GEO:地理位置的 “LBS 服务工具”

1. 对外表现

  • 用于存储和操作地理位置信息(经纬度),适用于 “附近的人”“附近的车” 等 LBS 场景。
  • 支持添加位置、查询位置、计算距离、查找指定范围内的位置等操作。

2. 内部实现

它的底层是直接复用 Sorted Set (ZSet) 数据结构。

  • 通过 GeoHash 算法将二维经纬度转换为一维的 score 值,利用 ZSet 的有序性实现高效的范围查找

3. 常用命令

  • GEOADD key longitude latitude member [...]:添加地理位置。
  • GEOPOS key member [member ...]:获取指定成员的经纬度。
  • GEODIST key member1 member2 [unit]:计算两个位置之间的距离。
  • GEORADIUS key longitude latitude radius [unit] [...]:查找指定经纬度周围半径内的位置。

4. 典型应用场景

  • 滴滴叫车 / 外卖配送:

    • GEOADD 存储所有车辆 / 骑手的实时经纬度。
    • GEORADIUS 根据用户位置,快速查找 5 公里内的可用车辆。

十、Stream类型!

1. 对外表现

  • Redis 5.0 新增,专门为解决消息队列问题设计的数据类型,弥补了 List 和发布订阅模式的缺陷。
  • 核心特性:支持消息持久化、自动生成全局唯一 ID、支持 消费组 模式、消息确认(ack)机制。

2. 内部实现

  • 内部使用新的数据结构,核心是维护消息日志和 Pending List(待确认消息列表),以支持消费组和消息可靠性。

3. 常用命令

  • 基础操作:XADD(插入消息)、XREAD(读取消息)、XLEN(查询长度)。
  • 消费组:XGROUP(创建消费组)、XREADGROUP(消费组读取)、XACK(确认消息)、XPENDING(查询待确认消息)。

4. 典型应用场景

  • 消息队列:

    • 对比 List:支持消息持久化、多消费组消费同一条消息、故障后可重新处理未确认消息。
    • 对比专业 MQ(Kafka/RabbitMQ):轻量、集成简单,但存在数据丢失风险(AOF 异步)和内存积压问题,更适合业务简单、对数据丢失不敏感的场景。