一、前言
Redis 是一款高性能的开源键值(Key-Value)型内存数据库,凭借读写速度快、支持高并发、数据类型丰富、功能强大等优势,被广泛应用于缓存、分布式锁、限流、消息队列、排行榜、计数器等各类业务场景。
与传统关系型数据库不同,Redis 并非只支持简单的字符串键值对,它内置了多种基础与扩展数据类型,能够适配多样化的业务存储需求。其中 String、Hash、List、Set、ZSet 是 Redis 最核心的五种基础数据类型,也是日常开发中使用频率最高的数据结构。
想要用好 Redis,不仅要掌握各类数据类型的使用命令,更要理解其底层存储结构、设计思想、特性与适用场景。本文将全面讲解 Redis 五大基础数据类型,从核心特性、底层实现、原生命令,再到 Spring Boot + Spring Data Redis 代码实战,由浅入深进行剖析,帮助大家彻底掌握 Redis 基础核心知识。
二、Redis String 字符串类型
String 是 Redis 中最基础、使用最广泛的数据类型,也是所有 Key 默认的存储类型。Redis 中所有的键(Key)本质上都是 String 类型,Value 同样基于字符串实现,可灵活存储文本、数字、二进制数据等内容。
2.1 核心特性
- 二进制安全 String 不会对存储内容做任何解析、转义、截断处理,能够完整保存普通字符串、数字、JSON 字符串、图片字节流、序列化对象、音频视频二进制数据等,不存在 C 语言字符串
\0截断的问题。 - 容量限制 单个 String 类型的 Value 最大可存储 512MB 数据,足以满足绝大多数业务场景。
- 支持数字原子操作 当字符串内容为整型数字时,Redis 提供自增、自减等命令,所有数字操作均为单命令原子性,在高并发场景下无需额外加锁,天然保证线程安全。
2.2 底层实现:SDS 简单动态字符串
Redis 的 String 类型底层并不是直接使用 C 语言原生字符串,而是自研了 SDS(Simple Dynamic String,简单动态字符串),这也是 Redis 高性能、二进制安全的核心原因。
2.2.1 SDS 结构体分类
Redis 定义了五种 SDS 结构体:sdshdr5、sdshdr8、sdshdr16、sdshdr32、sdshdr64。除 sdshdr5 为极简结构外,其余四种结构设计一致,仅 len 和 alloc 字段的位宽不同(8/16/32/64 位),Redis 会根据字符串长度自动选择最小结构体,最大化节省内存。
- 标准 SDS(sdshdr8/16/32/64)内存布局
len:实际数据字节长度,不包含末尾\0,读取长度时间复杂度为 O (1);alloc:已分配的内存总大小,记录 buf 数组可用空间,用于内存预分配优化;flags:占用 1 字节,低 3 位标识 SDS 类型,高 5 位预留未使用;buf[]:字符数组,存储真实数据,末尾主动追加\0,兼容 C 语言标准库函数。
- 极简 SDS(sdshdr5) 仅由
flags和buf组成,无len和alloc字段。flags高 5 位存储字符串长度,仅用于长度小于 32的内部短字符串,极致压缩内存。
2.2.2 SDS 对比 C 语言原生字符串
C 语言字符串存在三大缺陷,也是 Redis 设计 SDS 的根本原因:
- 非二进制安全 :以
\0作为字符串结束标记,若数据中包含\0会被直接截断,无法存储二进制文件; - 获取长度效率低 :需要遍历整个字符数组直到
\0,时间复杂度 O (n); - 内存操作低效:每次修改字符串都需要手动重新分配内存,频繁分配容易产生内存碎片,且无空间预分配机制。
针对以上问题,SDS 做了全面优化:
- 依靠
len字段标记数据长度,不再依赖\0,实现二进制安全; - 直接读取
len获取长度,时间复杂度优化为 O (1); - 内存预分配 + 惰性缩容:字符串扩容时额外分配空闲空间,减少频繁内存重分配;缩容时不立即释放多余内存,留作后续复用,大幅提升性能。
2.3 String 三种上层编码
Redis 在 RedisObject 层面针对 String 做了三层编码优化,区分不同场景下的内存布局:
- int 编码 触发条件:Value 为纯整数。底层不创建 SDS 结构,直接将数字存入 RedisObject,无额外内存开销。
- embstr 编码 触发条件:字符串长度 ≤44 字节 。使用
sdshdr8,RedisObject 与 SDS 合并为一块连续内存,仅一次内存分配,指针开销小、CPU 缓存友好。该编码为只读模式,一旦执行修改操作,会自动转为 raw 编码。 - raw 编码 触发条件:字符串长度 >44 字节、embstr 被修改、int 转为字符串。RedisObject 与 SDS 分为两块独立内存,通过指针关联,支持动态扩容、长字符串存储与修改操作。
2.4 常用原生命令
| 命令 | 作用 |
|---|---|
SET key value |
新增 / 覆盖键值对 |
GET key |
根据 Key 获取 Value,键不存在返回空 |
APPEND key value |
向字符串尾部追加内容,键不存在则等效 SET |
STRLEN key |
获取字符串长度,键不存在返回 0 |
MSET k1 v1 k2 v2... |
批量设置多个键值对 |
MGET k1 k2... |
批量获取多个 Key 的值 |
2.5 Spring Boot 实战代码
在 Spring Data Redis 中,通过 ValueOperations 操作 String 类型数据,RedisTemplate 会自动管理连接池,无需手动关闭连接。
package com.qcby.springbootTest;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@SpringBootTest
@RunWith(SpringRunner.class)
public class StringRedisTest {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Test
public void testBaseOperate() {
// 获取String类型操作工具类
ValueOperations<String, Object> valueOps = redisTemplate.opsForValue();
// 基础 set / get
valueOps.set("name", "minxr");
String name = (String) valueOps.get("name");
System.out.println("查询结果:" + name);
// 字符串追加
String oldValue = (String) valueOps.get("name");
valueOps.set("name", oldValue + "jintao");
System.out.println("追加后:" + valueOps.get("name"));
// 覆盖数据
valueOps.set("name", "jintao");
System.out.println("覆盖后:" + valueOps.get("name"));
// 删除Key
redisTemplate.delete("name");
System.out.println("删除后查询:" + valueOps.get("name"));
// 批量存储
Map<String, Object> multiMap = new HashMap<>();
multiMap.put("name", "minxr");
multiMap.put("jarorwar", "aaa");
redisTemplate.opsForValue().multiSet(multiMap);
// 批量获取
List<Object> mgetResult = redisTemplate.opsForValue()
.multiGet(Arrays.asList("name", "jarorwar"));
System.out.println("批量查询:" + mgetResult);
}
@Test
public void testKeyOperate() {
// 清空当前数据库
redisTemplate.getConnectionFactory().getConnection().flushDb();
System.out.println("清空数据库成功");
// 判断Key是否存在
boolean exists = redisTemplate.hasKey("foo");
System.out.println("foo是否存在:" + exists);
// 存储数据
redisTemplate.opsForValue().set("key", "values");
exists = redisTemplate.hasKey("key");
System.out.println("key是否存在:" + exists);
}
@Test
public void testStringAll() throws InterruptedException {
ValueOperations<String, Object> valueOps = redisTemplate.opsForValue();
try {
// 基础赋值取值
valueOps.set("key", "Hello World!");
System.out.println(valueOps.get("key"));
redisTemplate.getConnectionFactory().getConnection().flushDb();
// setIfAbsent 对应 SETNX 不存在则赋值
valueOps.set("foo", "bar");
Boolean setNx = valueOps.setIfAbsent("foo", "foo not exits");
System.out.println("SETNX执行结果:" + setNx + ",当前值:" + valueOps.get("foo"));
// 覆盖数据
valueOps.set("foo", "foo update");
System.out.println("覆盖后:" + valueOps.get("foo"));
// 设置过期时间(2秒)
valueOps.set("foo", "foo not exits", 2, java.util.concurrent.TimeUnit.SECONDS);
System.out.println("设置过期后:" + valueOps.get("foo"));
Thread.sleep(3000);
System.out.println("3秒后查询:" + valueOps.get("foo"));
// getAndSet 取值并覆盖
valueOps.set("foo", "foo update");
Object oldVal = valueOps.getAndSet("foo", "foo modify");
System.out.println("原值:" + oldVal + ",新值:" + valueOps.get("foo"));
// 批量操作
Map<String, Object> msetMap = new HashMap<>();
msetMap.put("mset1", "mvalue1");
msetMap.put("mset2", "mvalue2");
redisTemplate.opsForValue().multiSet(msetMap);
List<Object> mgetList = valueOps.multiGet(Arrays.asList("mset1", "mset2"));
System.out.println("批量查询:" + mgetList);
// 批量删除
Long delCount = redisTemplate.delete(Arrays.asList("foo", "foo1"));
System.out.println("删除个数:" + delCount);
} catch (Exception e) {
e.printStackTrace();
}
}
}
代码说明:
redisTemplate.opsForValue():获取 String 类型专属操作对象ValueOperations;setIfAbsent:对应 RedisSETNX命令,仅 Key 不存在时赋值,可用于分布式锁;- 所有方法均封装了原生 Redis 命令,连接池由框架自动管理。
三、Redis Hash 哈希类型
3.1 应用场景引入
在业务中,我们经常需要存储用户、商品、订单 等结构化数据。如果使用 String 存储 JSON 字符串,会存在明显缺陷:修改单个字段时,需要全量序列化、反序列化、网络传输,资源消耗大;多线程并发更新时还会出现更新丢失问题。
Redis Hash 类型正是为结构化对象存储 设计的数据结构,它是 Key-Field-Value 三级映射关系,一个 Redis Key 下可以维护多个 Field-Value 键值对,完美适配对象存储场景。
3.2 核心特性
- 结构:单 Key 对应多个 Field,Field 在当前 Hash 内唯一,Value 基于 SDS 实现,二进制安全;
- 容量:单个 Hash 最大支持
2^32 - 1个字段; - 原子性:单个字段的增删改查均为原子操作,天然规避并发更新问题;
- 内存优势:相比 JSON 字符串,无多余符号冗余,内存利用率更高;
- 时间复杂度:单字段操作 O (1),批量字段操作 O (N)(N 为操作字段数)。
3.3 底层实现
Hash 类型采用双结构自适应切换:
- 压缩列表(ziplist) 默认优先使用,当 Hash 字段数量 ≤512 且单个 Value 长度 ≤64 字节时生效。采用连续内存存储,无指针开销,内存极度紧凑。
- 哈希表(hashtable) 当字段数或 Value 长度超过阈值,自动切换为哈希表。读写效率稳定 O (1),适合大数据量、高并发场景。
3.4 常用原生命令
| 命令 | 作用 |
|---|---|
HSET key field value |
设置单个字段,新增返回 1,更新返回 0 |
HGET key field |
获取单个字段值 |
HMSET / HMGET |
批量设置 / 获取字段 |
HGETALL key |
获取 Hash 所有字段与值 |
HSETNX key field value |
字段不存在时才赋值 |
HDEL key field |
删除指定字段 |
HLEN key |
获取字段总数量 |
3.5 Spring Boot 实战代码
通过 HashOperations 操作 Hash 类型数据:
@Test
public void testHash() {
// 获取Hash操作对象
HashOperations<String, String, String> hashOps = redisTemplate.opsForHash();
// 批量设置字段
Map<String, String> pairs = new HashMap<>();
pairs.put("name", "Akshi");
pairs.put("age", "2");
pairs.put("sex", "Female");
hashOps.putAll("kid", pairs);
// 获取单个字段
List<String> name = hashOps.multiGet("kid", Collections.singletonList("name"));
System.out.println("姓名:" + name);
// 删除字段
hashOps.delete("kid", "age");
System.out.println("pwd字段值:" + hashOps.multiGet("kid", Collections.singletonList("pwd")));
// 获取字段数量
System.out.println("字段总数:" + hashOps.size("kid"));
// 获取所有字段名、字段值
System.out.println("所有字段:" + hashOps.keys("kid"));
System.out.println("所有值:" + hashOps.values("kid"));
// 获取完整Hash数据
Map<String, String> allPairs = hashOps.entries("kid");
System.out.println("完整数据:" + allPairs);
// 清空数据库
redisTemplate.getConnectionFactory().getConnection().flushDb();
// 新增Hash数据
hashOps.put("hashs", "entryKey", "entryValue");
hashOps.put("hashs", "entryKey1", "entryValue1");
// 判断字段是否存在
System.out.println("字段是否存在:" + hashOps.hasKey("hashs", "entryKey"));
// 字段值自增
Long incr = hashOps.increment("hashs", "entryKey", 123L);
System.out.println("自增结果:" + incr);
}
代码说明:
HashOperations专门用于操作 Hash 结构,泛型依次对应:RedisKey、Field、Value;Collections.singletonList:创建不可变单元素集合,常用于单字段查询;increment支持字段数字自增,原子性操作。
四、Redis List 列表类型
4.1 核心特性
Redis List 是有序、可重复 的字符串序列,基于双向链表语义实现,元素按照插入顺序排序,支持正向索引、反向索引。List 的优势集中在头尾增删元素,常用来实现消息队列、栈、时间线、分页列表等场景。
4.2 底层实现
Redis 版本不同,底层结构有差异:
- Redis 3.2 之前 双结构自适应:数据量小时使用
ziplist压缩列表;数据量超标切换为双向链表。 - Redis 3.2 及以上 统一使用 quicklist(快速列表),是双向链表 + 压缩列表的结合体。链表的每个节点都封装一个 ziplist,兼顾了链表头尾操作高效、ziplist 内存紧凑两大优点,是目前最优实现方案。
4.3 常用原生命令
表格
| 命令 | 作用 |
|---|---|
LPUSH / RPUSH |
头部 / 尾部插入元素 |
LPOP / RPOP |
头部 / 尾部弹出元素 |
LLEN key |
获取列表长度 |
LRANGE key start stop |
区间查询元素,0 -1 代表查询全部 |
LREM key count value |
删除指定个数的指定元素 |
LSET key index value |
修改指定索引元素 |
RPOPLPUSH src dst |
元素从源列表尾部转移到目标列表头部 |
4.4 Spring Boot 实战代码
通过 ListOperations 操作 List 类型:
@Test
public void testList() {
String messagesKey = "messages";
String listsKey = "lists";
redisTemplate.delete(messagesKey);
// 尾部插入元素
redisTemplate.opsForList().rightPush(messagesKey, "Hello how are you?");
redisTemplate.opsForList().rightPush(messagesKey, "Fine thanks.");
List<Object> messages = redisTemplate.opsForList().range(messagesKey, 0, -1);
System.out.println("消息列表:" + messages);
// 清空数据库
redisTemplate.getConnectionFactory().getConnection().flushDb();
// 头部插入元素
redisTemplate.opsForList().leftPush(listsKey, "vector");
redisTemplate.opsForList().leftPush(listsKey, "ArrayList");
redisTemplate.opsForList().leftPush(listsKey, "LinkedList");
// 获取列表长度
Long length = redisTemplate.opsForList().size(listsKey);
System.out.println("列表长度:" + length);
// 区间查询
List<Object> rangeList = redisTemplate.opsForList().range(listsKey, 0, 3);
System.out.println("0-3索引元素:" + rangeList);
// 修改指定索引元素
redisTemplate.opsForList().set(listsKey, 0, "hello list!");
// 根据索引查询
Object indexVal = redisTemplate.opsForList().index(listsKey, 1);
System.out.println("索引1元素:" + indexVal);
// 删除元素
Long remove = redisTemplate.opsForList().remove(listsKey, 1, "vector");
System.out.println("删除个数:" + remove);
// 列表裁剪
redisTemplate.opsForList().trim(listsKey, 0, 1);
// 头部弹出
Object pop = redisTemplate.opsForList().leftPop(listsKey);
System.out.println("弹出元素:" + pop);
List<Object> finalList = redisTemplate.opsForList().range(listsKey, 0, -1);
System.out.println("最终列表:" + finalList);
}
五、Redis Set 集合类型
5.1 核心特性
Redis Set 是无序、元素唯一的字符串集合,天然去重,同时支持交集、并集、差集等数学集合运算。适用于点赞、收藏、好友列表、黑名单、去重统计等场景。
核心特点:元素无序、不可重复、所有操作原子性、支持集合运算。
5.2 底层实现
采用双结构自适应切换:
- 整数集合(intset) 集合元素全为 64 位整数,且元素数量 ≤512 时使用。连续内存结构,二分查找,内存占用极低。
- 哈希表(hashtable) 包含非整数元素,或元素数量超标时切换。元素作为哈希表 Key,Value 为空占位,保证增删查 O (1)。
注意:intset 转 hashtable 是单向不可逆的。
5.3 常用原生命令
SADD 添加元素、SREM 删除元素、SMEMBERS 获取所有元素、SISMEMBER 判断元素是否存在、SINTER/SUNION/SDIFF 集合交并差运算。
六、Redis ZSet 有序集合类型
6.1 核心特性
ZSet(Sorted Set)有序集合,结合了 Set 唯一性 和 排序能力。每个成员(member)关联一个浮点型分值(score),根据 score 自动排序,是 Redis 中功能强大的排序结构,常用于排行榜、延时队列、带权重的排序场景。
排序规则:优先按 score 升序排列;score 相同时,按 member 字典序排序。 时间复杂度:核心操作 O (logN),范围查询 O (logN + K)。
6.2 常用原生命令
ZADD 添加元素、ZSCORE 查询分值、ZRANGE/ZREVRANGE 升序 / 降序查询、ZINCRBY 分值自增、ZRANK/ZREVRANK 查询排名。
6.3 Spring Boot 实战代码
@Test
public void sortedSet() {
String hackersKey = "hackers";
String zsetKey = "zset";
// 添加有序集合元素
redisTemplate.opsForZSet().add(hackersKey, "Alan Kay", 1940);
redisTemplate.opsForZSet().add(hackersKey, "Richard Stallman", 1953);
redisTemplate.opsForZSet().add(hackersKey, "Alan Turing", 1912);
// 升序查询
Set<Object> ascSet = redisTemplate.opsForZSet().range(hackersKey, 0, -1);
System.out.println("升序排行:" + ascSet);
// 降序查询
Set<Object> descSet = redisTemplate.opsForZSet().reverseRange(hackersKey, 0, -1);
System.out.println("降序排行:" + descSet);
// 清空数据库
redisTemplate.getConnectionFactory().getConnection().flushDb();
// 新增元素
redisTemplate.opsForZSet().add(zsetKey, "hello", 10.1);
redisTemplate.opsForZSet().add(zsetKey, "zset", 9.0);
// 元素个数
Long count = redisTemplate.opsForZSet().size(zsetKey);
System.out.println("元素总数:" + count);
// 查询分值
Double score = redisTemplate.opsForZSet().score(zsetKey, "zset");
System.out.println("zset分值:" + score);
}
七、Redis 通用 Key 操作命令
除五大数据类型专属命令外,Redis 所有 Key 都支持通用操作,日常使用频率极高:
EXPIRE key seconds:设置 Key 过期时间(秒);TTL key:查询剩余过期时间;PERSIST key:移除过期时间,转为永久 Key;DEL key:删除 Key;EXISTS key:判断 Key 是否存在;TYPE key:查看 Key 对应的数据类型;KEYS pattern:通配符查询 Key(生产环境慎用)。
通用操作实战:
@Test
public void testKeyCommon() throws InterruptedException {
Set<String> allKeys = redisTemplate.keys("*");
System.out.println("所有Key:" + allKeys);
// 设置过期时间
redisTemplate.opsForValue().set("timekey", "min", 10, TimeUnit.SECONDS);
Long ttl = redisTemplate.getExpire("timekey");
System.out.println("剩余过期时间:" + ttl);
Thread.sleep(5000);
System.out.println("休眠后过期时间:" + redisTemplate.getExpire("timekey"));
// 重命名Key
if (redisTemplate.hasKey("timekey")) {
redisTemplate.rename("timekey", "time");
}
System.out.println("原Key值:" + redisTemplate.opsForValue().get("timekey"));
System.out.println("新Key值:" + redisTemplate.opsForValue().get("time"));
}
八、五大基础数据类型总结与场景选型
- String:最简单通用,缓存普通文本、数字、计数器、简单对象;
- Hash:存储结构化对象(用户、商品),频繁修改单个字段的场景;
- List:有序可重复,消息队列、时间线、分页、栈结构;
- Set:无序唯一,去重、点赞、好友、集合运算;
- ZSet:有序唯一,各类排行榜、权重排序、延时队列。
五大类型底层均基于 ziplist、hashtable、quicklist、intset、SDS 等基础结构组合实现,Redis 根据数据特征自动切换存储结构,在内存占用和运行性能之间做到极致平衡。
九、结尾
Redis 五大基础数据类型是学习和使用 Redis 的基石,不仅要熟练掌握命令与代码调用,更要理解底层设计原理。在实际开发中,根据业务场景合理选择数据类型,能够大幅提升系统性能、简化代码逻辑。本文从原理、命令、实战三个维度完整讲解了五大类型,可作为日常开发、面试复习的参考资料。