一文吃透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 的底层数据结构是int 和SDS。
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 可以满足消息队列的三个需求:
- 消息保序:
LPUSH + RPOP(或RPUSH + LPOP)实现先进先出 - 处理重复消息:生产者为每条消息生成全局唯一 ID,消费者记录已处理的 ID 进行去重
- 保证消息可靠性:使用
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= 用户 IDfield= 商品 IDvalue= 商品数量- 用
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只能是 0 和 1
SETBIT key offset value
# 获取值
GETBIT key offset
# 获取指定范围内值为 1 的个数
# start 和 end 以字节为单位
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 异步)和内存积压问题,更适合业务简单、对数据丢失不敏感的场景。