👉本篇速览
早在最开始学Redis的时候,我们就学到了这九种数据类型:String、Hash、List、Set、Zset、BitMap、HyperLogLog、GEO、Stream,但其实在学的时候并不了解它的底层是怎么存储这些数据,而不同的数据类型又有哪些应用的场景,本文将分别讲述这九种数据类型的底层实现和应用场景,但是不会去具体讲解这九种数据类型的使用或者说是指令
Tips:文章很长,建议大家静心攻读,相信会有不错的收获!也可以收藏起来,重复去看和理解(骗收藏hhh)
1️⃣ String
我们不难发现Redis的数据类型中没有char,int,double……这种数据类型,因为Redis的String不仅可以是字符串,还能是数字(整数或者浮点数),就让我们来一起看看是如何做到兼容的:
🍕 SDS-简单动态字符串
String 类型的底层的数据结构实现主要是靠 int 和 SDS,SDS:简单动态字符串
🍔 C语言字符数组
在讲解SDS之前我们先谈谈为什么不使用char型数组(Redis使用C语言写的,此处的char型数组是指char*):
-
char型数组判断字符串长度有局限性且获取字符串长度的时间复杂度高 C语言使用strlen判断字符串长度的函数是对字符数组进行遍历,找到\0,那么对于字符串中有\0的,就会遍历提前结束,得到的长度并不准确 刚刚也提到,是对字符数组进行遍历,那么得到长度的时间复杂度就是O(N)
-
char型数组容易缓冲区溢出:C语言的库提供的字符串操作函数并不安全,因为这些函数的底层并不会判断缓冲区大小是否够用,因此会出现缓冲区溢出进而程序崩溃
🍟 SDS数据结构
其实SDS的实现就是把字符数组封装成了一个能够成熟使用的对象,用C语言的话来说,就是一个结构体,我们来看看结构的中的成员变量
该结构体中的字段有:
-
len:记录字符串的长度,这样就不需要全表遍历去获得字符串的长度,获取字符串长度的时间复杂度就:O(N) -> O(1)
-
alloc:分配给字符数组的空间长度,通过alloc - len可以计算出剩余的空间大小,方便后续的扩容,也就是说在做操作的时候会判断剩余的空间是否够用,这样也就防止了缓冲区溢出的问题
-
flags:表示SDS的类型,可以理解为type
-
buf[]:字节数组,也就是真正存储数据的数组,除了可以保存字符串还能保存二进制数据
🌭 存储数据的流程
对于存入的key-value的键值对,会把value的信息封装成一个RedisObject,然后编码为SDS存入内存:
如果字符串是一个数值,数值可以使用long类型存储,例如666.66,那么它的RedisObject就为:
首先是字符串形式,因此Type:String,然后encoding有三种类型:
-
int:用于编码数值类型的数据
-
raw:用于编码数据量大于指定字节的字符串数据
-
embstr:用于编码数据量小于指定字节的字符串数据
-
这个指定的字节,与redis的版本有关系: redis 2.+ 是 32 字节 redis 3.0 - 4.0 是 39 字节 redis 5.0 是 44 字节
那我们再来讨论字符串长度小于指定字节,也就是说使用embstr编码的字符串时,它的RedisObject就为:
编码得到SDS,并把这个数据结构存到内存中:
那么,既然分出了不同字节不同编码,两种编码方式有什么区别呢?
我们先看看内存图,我们可以发现:embstr的RedisObject和SDS空间是连在一起的,而raw编码的二者是两块不连续的内存
两种编码方式各有千秋,经比较&#