目录
- [Redis 数据类型概览](#Redis 数据类型概览)
- [String 类型及底层实现](#String 类型及底层实现)
- 2.1 简单动态字符串(SDS)
- [Hash 类型及底层实现](#Hash 类型及底层实现)
- 3.1 哈希表(Hash Table)
- 3.2 压缩列表(Ziplist)
- [List 类型及底层实现](#List 类型及底层实现)
- 4.1 双向链表(LinkedList)
- 4.2 压缩列表(Ziplist)
- [Set 类型及底层实现](#Set 类型及底层实现)
- 5.1 哈希表
- 5.2 整数集合(Intset)
- [Sorted Set 类型及底层实现](#Sorted Set 类型及底层实现)
- 6.1 跳表(SkipList)
- 6.2 压缩列表(Ziplist)
- 总结
Redis 数据类型概览
Redis 提供的五种主要数据类型,每一种都有其特定的用途:
- String:最基础的键值对形式,可以存储字符串、整数、浮点数。
- Hash:适合存储对象,可以用字段-值的形式存储数据。
- List:链表结构,支持从两端操作,非常适合实现队列或栈。
- Set:无序的唯一集合,适合存储不重复的数据。
- Sorted Set:有序的集合,每个元素都会关联一个分数,支持按分数排序。
这些类型的底层实现并不完全相同,Redis 会根据存储的元素数量和数据结构自动选择最合适的存储方式。
String 类型及底层实现
String 是 Redis 中最基础的数据类型,一个键对应一个值。String 类型的值可以是普通字符串、数字、二进制数据等。其主要应用场景包括:
- 缓存字符串数据。
- 作为计数器存储整数或浮点数。
- 存储用户信息等简单数据。
2.1 简单动态字符串(SDS)
Redis 的 String 类型底层使用的是 SDS(Simple Dynamic String) 来实现。SDS 是 Redis 为了替代 C 语言中的 char
类型字符串而设计的一种动态字符串结构,具有更高的灵活性和安全性。
SDS 的主要特点:
- 动态扩展:当字符串长度增加时,SDS 可以自动扩展其容量,避免频繁的内存分配。
- 预分配空间:SDS 为了减少内存重分配的次数,会预留一部分额外空间,尤其是在增长字符串时。
- 惰性空间释放:当缩短 SDS 字符串时,并不会立即释放多余的内存,而是保留这些空间,等待未来使用。
- 二进制安全:SDS 可以存储任意二进制数据,而不仅仅是文本数据。
SDS 的结构如下:
c
struct sdshdr {
int len; // 已使用的长度
int free; // 剩余的可用空间
char buf[]; // 实际保存字符串的地方
};
这种结构设计使得 SDS 在处理字符串时具备高效性和灵活性。
Hash 类型及底层实现
Hash 类型在 Redis 中用于存储对象数据,类似于传统编程语言中的字典或 Map 结构。每个键对应一个字段和字段值的集合,非常适合存储用户信息等对象数据。
3.1 哈希表(Hash Table)
当 Hash 类型的数据量较大时,Redis 使用经典的 哈希表(Hash Table) 来存储。哈希表通过计算键的哈希值将字段-值对映射到不同的桶中,从而实现快速的 O(1) 复杂度的查找、插入和删除操作。
Redis 使用的哈希表结构为 dict,其主要结构如下:
c
typedef struct dict {
dictEntry **table; // 哈希桶数组
unsigned long size; // 哈希表的大小
unsigned long sizemask; // 用于计算索引的掩码
unsigned long used; // 已使用的哈希桶数量
} dict;
Redis 为了防止哈希冲突,使用了链地址法来解决,当多个键映射到同一个桶时,这些键值对会以链表的形式存储在该桶中。
3.2 压缩列表(Ziplist)
当 Hash 中的字段数量较少且每个字段和字段值都很小的时候,Redis 会使用更节省内存的 压缩列表(Ziplist) 结构来存储数据。
压缩列表是一种紧凑的双向链表,所有元素紧密排列在一起,不会为每个元素单独分配内存。这种结构极大地减少了内存的使用,但在数据量较大时,性能会有所下降。
List 类型及底层实现
List 是 Redis 中的链表结构,支持从两端进行高效的插入和删除操作,适合用于实现队列、栈等数据结构。List 常用于社交网络的消息流、任务队列等场景。
4.1 双向链表(LinkedList)
当 List 中存储的元素较多时,Redis 使用 双向链表(LinkedList) 结构来实现。双向链表允许我们在 O(1) 时间内从头部和尾部进行插入和删除操作。
Redis 使用的链表结构如下:
c
typedef struct listNode {
struct listNode *prev; // 前一个节点
struct listNode *next; // 后一个节点
void *value; // 节点的值
} listNode;
typedef struct list {
listNode *head; // 头节点
listNode *tail; // 尾节点
} list;
双向链表的设计使得 List 操作非常高效,尤其是在实现先进先出的队列时。
4.2 压缩列表(Ziplist)
当 List 中的元素较少时,Redis 仍会选择使用 压缩列表(Ziplist) 以节省内存。与 Hash 的情况类似,Ziplist 在节省内存的同时也会牺牲一定的性能。
Set 类型及底层实现
Set 是无序的集合,集合中的元素是唯一的,且不允许重复。Set 非常适合存储不重复的数据,如标签系统、好友关系等。
5.1 哈希表
当 Set 中的元素数量较多时,Redis 使用 哈希表 来存储集合。由于 Set 不需要存储值,只需要确保元素的唯一性,哈希表在这里仅使用键来保证数据的唯一性。
5.2 整数集合(Intset)
当 Set 中存储的元素是整数且数量较少时,Redis 会使用 整数集合(Intset) 来存储。这是一种紧凑型的数据结构,专门用来存储小范围的整数集合。
整数集合的结构如下:
c
typedef struct intset {
uint32_t encoding; // 编码方式
uint32_t length; // 集合中的元素个数
int8_t contents[]; // 存储整数的数组
} intset;
整数集合根据元素大小自动选择不同的编码方式,以提高存储效率。
Sorted Set 类型及底层实现
Sorted Set 是 Redis 中的有序集合,集合中的每个元素都有一个分数,元素按照分数进行排序。Sorted Set 常用于排行榜系统、按权重排序的数据等场景。
6.1 跳表(SkipList)
当 Sorted Set 的元素较多时,Redis 使用 跳表(SkipList) 结构来实现。跳表是一种可以在 O(log N) 时间复杂度内进行插入、删除和查找的有序数据结构。
Redis 中的跳表结构如下:
c
typedef struct zskiplist {
struct zskiplistNode *header; // 跳表头节点
struct zskiplistNode *tail; // 跳表尾节点
unsigned long length; // 跳表长度
int level; // 跳表层数
} zskiplist;
跳表通过多层级链表来加快数据的查找速度,是一种内存友好的排序结构。
6.2 压缩列表(Ziplist)
当 Sorted Set 中的元素较少时,Redis 也可以使用 压缩列表(Ziplist) 来存储。这与 Hash 和 List 的实现类似,用以节省内存。
总结
Redis 提供了五种主要的数据类型,每种类型都有其特定的应用场景和底层数据结构。在实际使用中,选择合适的数据类型和理解其底层实现有助于优化 Redis 的性能。通过合理地设计和运用 Redis 的数据结构,我们可以在保证性能的前提下,最大化地利用 Redis 的优势。