字符串(String)
在 Redis 中,字符串(String)是最基本的数据类型,同时也是其他复杂数据类型的基础。为了高效地存储和处理字符串,Redis 的字符串数据类型在底层采用了多种不同的数据结构,根据字符串的长度和内容自动选择最合适的实现方式。主要包括以下几种:
- 简单动态字符串(SDS, Simple Dynamic String)
- 整数
1. 简单动态字符串(SDS)
SDS 是 Redis 为了替代 C 语言中的 C-string 而设计的一种动态字符串库,它具有更高的效率和灵活性。SDS 在底层通过一个结构体来管理字符串数据,并支持自动扩容和缩容。
特点:
- 动态扩展:SDS 可以根据需要自动扩展内存,以适应新的字符串长度,而无需手动重新分配内存。
- 预留空间:为了减少频繁的内存重分配操作,SDS 会在每次扩展时预留一部分额外的空间用于未来的增长。
- 二进制安全:与传统的 null 终止字符串不同,SDS 可以包含任何字节数据,包括 null 字符,因为 SDS 使用显式长度记录字符串长度。
- 低开销操作:SDS 支持常数时间复杂度的字符串长度获取操作,而不需要遍历整个字符串。
SDS 结构示例:
struct sdshdr {
int len; // 当前字符串长度
int free; // 剩余可用空间
char buf[]; // 实际存储字符串的缓冲区
};
2. 整数
当字符串内容为整数值且在一定范围内时,Redis 会将其以整数形式存储,而不是使用 SDS。这种优化可以减少内存占用,提高访问速度。
特点:
- 节省内存:整数直接存储在内存中,不需要分配额外的内存空间用于字符串表示。
- 快速访问:整数读取和写入操作非常快速,无需进行字符串解析和转换。
数据结构选择逻辑
Redis 根据字符串内容和长度,自动选择最合适的底层数据结构:
- 简单动态字符串(SDS) :用于存储一般的字符串数据,支持动态扩展和二进制安全。
- 整数:用于存储可以表示为整数的字符串数据,以提高存储效率和访问速度。
示例使用
以下是一些基本的字符串操作示例:
# 设置字符串键值
SET mykey "Hello, World!"
# 获取字符串值
GET mykey # 返回 "Hello, World!"
# 设置并获取旧值
GETSET mykey "New Value" # 返回 "Hello, World!" 并将 mykey 设置为 "New Value"
# 增加数值键
SET counter 10
INCR counter # 将 counter 加 1,现在 counter 为 11
# 追加字符串
APPEND mykey " Again" # 将 " Again" 追加到 mykey 后,现在 mykey 为 "New Value Again"
# 获取子字符串
SUBSTR mykey 0 3 # 返回 "New "
# 获取字符串长度
STRLEN mykey # 返回 15
总结
- 简单动态字符串(SDS) :用于存储一般的字符串数据,具有动态扩展、预留空间和二进制安全等特性。
- 整数:用于存储可以表示为整数的字符串数据,节省内存并提高访问速度。
列表(List)
在 Redis 中,列表(List)是一种常用的数据结构,其底层实现可以根据不同的使用场景选择不同的数据类型。具体来说,Redis 列表的数据结构包括:
- 压缩列表(ziplist)
- 双端链表(linked list)
压缩列表(Ziplist)
压缩列表是一种为节省内存而设计的紧凑型数据结构。它由一系列特殊编码的连续内存块组成,每个内存块可以存储一个字符串或者整数。
特点:
- 紧凑性:所有的元素都存储在一个连续的内存块中,没有分配额外的指针,因此非常节省内存。
- 适用于小列表:由于其紧凑性和线性存储结构,压缩列表适合存储元素较少的小列表。
使用条件:
- 当列表中的元素数量较少并且每个元素的长度较短时,Redis 会选择使用压缩列表来保存列表数据。这些条件可以通过配置参数进行调整,默认情况下,如果列表中的元素数量小于 512 个,并且每个元素的长度小于 64 字节,则使用压缩列表。
双端链表(Linked List)
双端链表是一种传统的数据结构,由多个节点组成,每个节点包含一个数据单元和两个指向前后节点的指针。Redis 使用自己的 quicklist 实现了一种优化版的双端链表。
特点:
- 灵活性:支持快速的插入和删除操作,因为插入和删除只涉及指针的修改。
- 适用于大列表:适合存储元素数量较多的大列表,因为即使列表变得很大,也能保持较高的操作性能。
使用条件:
- 当列表中的元素数量达到一定规模,或者元素较长时,Redis 会自动切换到使用双端链表来保存列表数据。
Quicklist
从 Redis 3.2 开始,引入了 quicklist 这种数据结构,它是对传统双端链表和压缩列表的结合和优化。quicklist 内部实际上是一个双端链表,其中每个节点都是一个压缩列表。
特点:
- 内存效率:通过将多个元素存储在一个压缩列表中,减少了指针的使用,从而提高内存利用率。
- 操作效率:在保留双端链表快速插入和删除操作的同时,通过压缩列表减少了内存碎片,提高了缓存局部性。
数据结构选择逻辑
Redis 会根据列表数据的大小和元素的长度,动态选择最合适的数据结构来存储列表:
- 压缩列表:当列表较小时,使用压缩列表以最大化内存效率。
- Quicklist:当列表变大或元素较长时,使用
quicklist以平衡操作性能和内存效率。
示例使用
以下是一个简单的示例,展示如何使用 Redis 的列表操作:
# 将元素推入列表
LPUSH mylist "element1"
LPUSH mylist "element2"
RPUSH mylist "element3"
# 获取列表中的所有元素
LRANGE mylist 0 -1 # 返回 ["element2", "element1", "element3"]
# 从列表中弹出元素
LPOP mylist # 返回 "element2"
RPOP mylist # 返回 "element3"
# 获取列表长度
LLEN mylist # 返回 1
通过上述示例,我们可以看到 Redis 提供了丰富的列表操作,而在底层,Redis 会根据列表的大小和元素情况,自动选择压缩列表或 quicklist 来存储数据,以实现最佳的性能和内存利用率。
总结
Redis 中的列表数据结构采用了压缩列表和双端链表(通过 quicklist 优化)的混合实现,根据实际数据情况选择最优的数据结构进行存储,在保证操作性能的同时,尽可能地节约内存。
集合(Set)
在 Redis 中,集合(Set)是一种无序的、元素唯一的数据结构。为了实现高效的集合操作,Redis 使用了两种不同的底层数据结构来存储集合数据:
- 整数集合(Intset)
- 哈希表(Hashtable)
1. 整数集合(Intset)
整数集合是一种为高效存储小范围整数而设计的紧凑型数据结构。当集合中的所有元素都是整数且数量较少时,Redis 会选择使用整数集合作为底层实现。
特点:
- 紧凑性:整数集合采用连续内存块存储整数,节省了大量内存。
- 自动升级:当插入的新整数超出当前范围时,整数集合会自动进行升级以支持更大范围的整数。
- 适用于小集合:尤其适合存储元素较少且全为整数的小集合。
使用条件:
- 当集合中的元素全为整数且数量较少(默认情况下小于 512 个,可通过配置调整),Redis 会使用整数集合。
2. 哈希表(Hashtable)
哈希表是 Redis 常用的数据结构之一,用于快速查找和存储数据。当集合中的元素类型复杂或数量较大时,Redis 会使用哈希表作为底层实现。
特点:
- 快速查找:利用哈希算法,实现常数时间复杂度的查找、插入和删除操作。
- 灵活性:能有效处理大规模数据和多样化的元素类型。
- 适用于大集合:适合存储大量元素,并且能保持高效的操作性能。
使用条件:
- 当集合中的元素数量较多(超过 512 个)或者包含非整数类型,Redis 自动切换到使用哈希表。
数据结构选择逻辑
Redis 根据集合的实际情况动态选择最合适的底层数据结构:
- 整数集合:用于存储元素较少且都是整数的小集合,以提高内存利用率和操作效率。
- 哈希表:用于存储大量元素或包含复杂类型的大集合,以确保操作的高效性。
示例使用
以下是一些基本的集合操作示例:
# 添加元素到集合中
SADD myset "element1"
SADD myset "element2"
SADD myset "element3"
# 检查元素是否存在于集合中
SISMEMBER myset "element2" # 返回 1 表示存在
# 获取集合中的所有元素
SMEMBERS myset # 返回 ["element1", "element2", "element3"]
# 删除集合中的某个元素
SREM myset "element2"
# 获取集合的大小
SCARD myset # 返回 2
# 集合的交集运算
SADD set1 "a" "b" "c"
SADD set2 "b" "c" "d"
SINTER set1 set2 # 返回 ["b", "c"]
在这些示例操作中,Redis 会根据集合的大小和元素内容,在整数集合和哈希表之间自动选择最合适的底层实现。
总结
- 整数集合(Intset) :适用于存储小范围整数的小集合,具有高内存效率。
- 哈希表(Hashtable) :适用于存储大量元素或复杂类型元素的大集合,具有高操作效率。
排序集合(ZSet)
在 Redis 中,排序集合(Sorted Set,简称 ZSet)是一种带有分数的集合,每个元素都关联一个浮点数值作为分数,通过分数进行排序。为了实现高效的排序和查找操作,Redis 使用了一种组合数据结构来存储排序集合:
- 跳表(Skiplist)
- 哈希表(Hashtable)
跳表(Skiplist)
跳表是一种支持快速查找的多级链表结构,通过在多个层次之间进行跳跃,使得查找、插入和删除操作的平均时间复杂度达到 O(log N)。
特点:
- 有序性:跳表中的元素按照分数排序,便于按范围查询和排序操作。
- 高效性:在最坏情况下,操作效率与红黑树相当,但实现更简单,常数因子较小。
- 动态平衡:通过随机化算法,跳表能够自动维持动态平衡,确保高效操作。
哈希表(Hashtable)
哈希表用于将成员映射到其对应的分数上,以便实现快速的分数查找和更新。
特点:
- 快速查找:利用哈希算法,能在 O(1) 时间内查找到具体成员及其分数。
- 灵活性:结合跳表的有序性,哈希表提供了对成员的直接访问能力,提高了整体操作效率。
两者结合
在 Redis 的 ZSet 实现中,跳表和哈希表共同作用,使得排序集既可以高效地执行范围查询、排序等操作,又能够快速地进行成员查找和分数更新。具体来说:
- 跳表:维护排序集的顺序,用于按分数进行排序和范围查找。
- 哈希表:维护成员到分数的映射,用于快速查找和更新成员的分数。
这种组合的数据结构使得 ZSet 能够在各种复杂操作(如插入、删除、按分数排序、按字典序排序、按范围查询等)中都保持较高的性能。
示例使用
以下是一些基本的排序集合操作示例:
# 添加或更新成员及其分数
ZADD myzset 1.0 "element1"
ZADD myzset 2.0 "element2"
ZADD myzset 3.0 "element3"
# 获取成员的分数
ZSCORE myzset "element2" # 返回 "2.0"
# 获取集合中的所有成员按分数排序
ZRANGE myzset 0 -1 # 返回 ["element1", "element2", "element3"]
# 获取集合中的所有成员按分数逆序排序
ZREVRANGE myzset 0 -1 # 返回 ["element3", "element2", "element1"]
# 按分数范围获取成员
ZRANGEBYSCORE myzset 1.0 2.0 # 返回 ["element1", "element2"]
# 删除成员
ZREM myzset "element2"
# 获取集合的大小
ZCARD myzset # 返回 2
在这些示例操作中,Redis 会使用跳表和哈希表的组合数据结构来高效地实现排序集合的功能。
总结
- 跳表(Skiplist) :用于维护有序的分数列表,支持高效的范围查询和排序操作。
- 哈希表(Hashtable) :用于快速查找和更新成员及其分数,实现高效的成员访问。
哈希(Hash)
在 Redis 中,哈希(Hash)是一种用于存储键值对集合的数据结构,可以将其看作是一个小型的键值数据库。为了高效地实现哈希数据类型,Redis 使用了以下两种底层数据结构:
- 压缩列表(Ziplist)
- 哈希表(Hashtable)
1. 压缩列表(Ziplist)
压缩列表是一种为节省内存而设计的紧凑型数据结构,适用于存储数量较少且数据较短的键值对。
特点:
- 紧凑性:所有的键值对都存储在一个连续的内存块中,没有额外的指针开销,因此非常节省内存。
- 顺序存储:键和值以顺序方式紧密排列在一起,便于快速遍历。
使用条件:
- 当哈希中的键值对数量较少(默认情况下小于 512 个,可以通过配置调整),并且每个键值对的长度较短(每个字符串小于 64 字节时),Redis 会选择使用压缩列表作为哈希的底层实现。
2. 哈希表(Hashtable)
哈希表是一种常见的、高效的键值对存储结构,通过哈希算法实现键值对的快速查找和插入。
特点:
- 高效查找:利用哈希算法,实现 O(1) 时间复杂度的查找、插入和删除操作。
- 灵活性:能够处理大量数据和较长字符串的键值对,有很好的伸缩性。
- 冲突解决:采用链地址法或开放定址法来处理哈希冲突。
使用条件:
- 当哈希中的键值对数量较多(超过 512 个)或者键值对较长时,Redis 会自动切换到使用哈希表作为底层实现,以确保操作的高效率。
数据结构选择逻辑
Redis 根据哈希数据的大小和内容动态选择最合适的底层数据结构:
- 压缩列表:用于存储数量较少且较短的键值对,以最大化内存效率。
- 哈希表:用于存储大量或较长的键值对,以保证高效的操作性能。
示例使用
以下是一些基本的哈希操作示例:
# 设置哈希字段
HSET myhash field1 "value1"
HSET myhash field2 "value2"
HSET myhash field3 "value3"
# 获取哈希字段的值
HGET myhash field2 # 返回 "value2"
# 获取哈希所有字段和值
HGETALL myhash # 返回 ["field1", "value1", "field2", "value2", "field3", "value3"]
# 删除哈希字段
HDEL myhash field2
# 获取哈希字段数量
HLEN myhash # 返回 2
# 检查哈希字段是否存在
HEXISTS myhash field1 # 返回 1 表示存在
# 增加哈希字段值(数值类型)
HINCRBY myhash field4 5 # 如果 field4 不存在,则初始值为 0,然后增加 5
在这些示例操作中,Redis 会根据哈希大小和键值对长度,在压缩列表和哈希表之间自动选择最合适的底层实现。
总结
- 压缩列表(Ziplist) :适用于存储数量较少且较短键值对的小型哈希,具有高内存效率。
- 哈希表(Hashtable) :适用于存储大量或较长键值对的大型哈希,具有高操作效率。