Redis底层数据结构和value值的类型

本文介绍了Redis中的主要数据结构,包括简单动态字符串(SDS)、链表、字典、跳跃表、整数集合、压缩列表及对象系统。深入探讨了每种数据结构的特点及其应用场景。

看到好多文章直接写Redis有5种数据结构,这种表达不准确,我根据《Redis设计与实现》中的内容进行了简单的整理。

Redis数据库里面的每个键值对都是有对象组成的,其中:

  • 数据库键总是一个字符串对象
  • 数据库值则可以使字符串对象、列表对象、哈希对象、集合对象、有序集合对象这5种对象中的其中一种

——《Redis设计与实现》


Redis 主要有7种数据结构: SDS(简单动态字符串)、链表、字典、跳跃表、整数集合、压缩列表 、对象
Redis 对象系统包含5种对象:字符串对象、列表对象、哈希对象、集合对象、有序集合对象

底层数据结构:

SDS:

Redis没有直接使用C语言中传统字符串表示,而是实现了SDS(simple dynamic string)作为默认字符串表示。C字符串只会作为字符串字面量由于无需修改的地方,比如日志打印。
SDS的结构如下所示,该图展示的是一个代表“Redis”字符串的SDS。其中len和free的“5”单位是Byte。虽然SDS保留了C中的空字符‘/0’但是它不会被计算到len中。
SDS数据结构
比起C字符串,SDS具有以下优点:

  • 常数复杂度获取字符串长度(由于len字段的存在)
  • 杜绝缓冲区溢出(C在追加字符串时,会默认已经分配了足够的内存,所以如果没有分配足够的内存就会造成溢出;而SDS在执行修改的时候API会先检查SDS空间是否满足需求)
  • 减少修改字符串长度时所需的内存重分配次数(空间预分配:当API对SDS进行修改,不仅会分配必要空间,还会分配额外空间,小于1MB时会分配和len大小相同的free,大于1MB会分配1MB的未使用空间;惰性空间释放:程序不立即使用内存重分配来回收缩短后多出来的字节,而是使用free属性记录)
  • 二进制安全(C字符串使用‘/0’空字符来判断字符串是否结束,所以C字符串无法存储图像、音频等这样的二进制,而SDS使用len来判断字符串是否结束)
  • 兼容部分C字符串函数

链表:

链表
(这里dup、free、match都是函数)
链表特性:双端(每个节点都有前后指针)、无环(表头的prev和表尾next指向NULL)、带表头和表尾指针、带链表长度计数器、多态(链表可以保存各种不同类型的值)

字典:

字典的实现和hashMap很像。
字典

rehash:

可以看到图中的ht[1]的这个哈希表是空的,当ht[0]中保存的键值对过大或过小时,就会为ht[1]分配空间进行rehash,如果是扩展操作,那么ht[1]的大小为第一个大于等于ht[0].used * 22^n,如果是收缩操作,那么ht[1]的大小为第一个大于等于ht[0].used的2^n。rehash之后,ht[0]和ht[1]会互相调换。
字典的rehash是渐进式的,在进行渐进式rehash的过程中,字典会同时使用ht[0]和ht[1]两个哈希表,所以在渐进式rehash期间,字典的增删改查等操作会在两个哈希表上进行,例如查找一个键,会先去ht[0]查找,找不到就去ht[1]查找,新添加的键一律都是存到ht[1]中,ht[0]包含的键值只减不增。

跳跃表:

大部分情况下,跳跃表效率和平衡树媲美,实现又比平衡树简单,所以许多情况下会用跳跃表来代替平衡树。
跳跃表
每创建个跳跃表节点时,程序会根据幂次定律(越大的数出现概率越小)随机生成一个1~32之间的值作为层的高度大小,每个层都有前进指针和跨度。每个节点包含层、后退指针、分值、成员对象。

整数集合:

整数集合

升级:

每当一个新元素添加到整数集合里面,并且新元素类型比整数集合现在所有元素的类型都要长时,整数集合需要先进行升级,然后才能将新元素添加到整数集合里面。升级需要扩展底层数组空间大小,为新元素分配空间,并将所有元素转换成与新元素相同的类型。整数集合不支持降级。

压缩列表:

压缩列表是Redis为了节约内存而开发的,是由一系列特殊编码的连续内存块组成的顺序型数据结构。
压缩列表
压缩列表节点
压缩列表可能存在连锁更新的问题,但是发生的概率不高,所以对性能的影响几率很低。

对象:

Redis没有直接使用上述的这些底层数据结构来实现键值对数据库,而是基于这些数据结构创建了一个对象系统,这个系统包含**字符串对象、列表对象、哈希对象、集合对象、有序集合对象**5种,每种对象都用到了至少一种上述的数据结构。Redis使用了基于引用计数的方式来实现内存回收;同时基于引用计数实现了对象共享机制。
Redis对象系统
type:一共有5种,REDIS_STRING、REDIS_LIST、REDIS_HASH、REDIS_SET、REDIS_ZSET
ptr:ptr指针指向对象的底层实现数据结构
encoding:ptr指向哪种数据结构由encoding属性决定
下图展示的是type和encoding的对应关系,即是在type确定时可以使用那些encoding配合:
type和encoding对应关系

参考链接:

Redis 提供了五种基础数据类型:String(字符串)、Hash(哈希)、List(列表)、Set(集合) ZSet(有序集合)。每种数据类型底层都有多种实现方式,以兼顾性能内存效率。 ### String(字符串) String 是 Redis 中最基本的数据类型,一个 key 对应一个 value。它支持存储文本、数字、二进制数据等,最大可存储 512MB 的数据。String 类型底层实现主要有以下几种: - **int**:用于存储整数,使用 long 类型表示。 - **embstr**:用于存储短字符串,内部使用简单动态字符串(SDS)结构,但分配释放效率更高。 - **raw**:用于存储较长的字符串,同样基于 SDS 实现,但 embstr 相比,raw 更适合处理大字符串。 ### Hash(哈希) Hash 是一个键对集合,适用于存储对象。它的底层实现主要依赖于以下结构: - **ziplist**:当哈希中的对数量较少且键内容较小时,Redis 使用 ziplist 作为底层实现,以节省内存[^4]。 - **hashtable**:当键对数量增加或键内容变大时,Redis 会自动切换到 hashtable 实现,以提升访问效率[^3]。 ### List(列表) List 是一个有序的字符串集合,支持双向操作。它的底层实现主要包括: - **ziplist**:当列表中的元素数量较少且每个元素内容较小时,使用 ziplist 存储。 - **linkedlist**:当列表元素较多或单个元素较大时,切换为双端链表实现[^3]。 ### Set(集合) Set 是一个无序的字符串集合,不允许重复元素。其底层实现包括: - **intset**:当集合中所有元素都是整数且数量较少时,使用 intset 存储,节省内存且效率高。 - **hashtable**:当集合包含非整数元素或元素数量较多时,使用字典实现[^3]。 ### ZSet(有序集合) ZSet 是一个有序的集合,每个元素都关联一个分数(score),用于排序。它的底层实现较为复杂,主要包括: - **ziplist**:当有序集合中的元素数量较少且每个元素内容较小时,使用 ziplist 实现。 - **skiplist + hashtable**:当元素数量较多或内容较大时,Redis 使用跳跃表(skiplist)字典结合的方式实现。跳跃表用于高效排序,字典用于快速查找元素。 ### 总结 Redis 的每种基础数据类型都有多种底层实现,具体选择哪种结构取决于数据的大小、数量以及操作需求。这种灵活的设计使得 Redis 在不同场景下都能保持较高的性能存储效率。 ```python # 示例代码:Redis 常见数据类型及其底层结构的映射 data_types = { "String": ["int", "embstr", "raw"], "Hash": ["ziplist", "hashtable"], "List": ["ziplist", "linkedlist"], "Set": ["intset", "hashtable"], "ZSet": ["ziplist", "skiplist + hashtable"] } for data_type, encodings in data_types.items(): print(f"{data_type} 的底层实现包括:{', '.join(encodings)}") ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值