Redis数据结构
首先,我们要有一个概念,在Redis中,所有数据结构是通过对象来管理的,使用对象管理的好处:可以使用多态去调用方法,并且方便引入内存回收机制。
Redis创建一个对象的时候,是会创建一个键值对的,一个键会创建一个字符串对象,一个值会创建一个值对象,Redis的结构如下:
RedisObject{
///数据类型
unsigned type :4;
///数据编码
unsigned encoding :4;
///指向底层结构的指针
void *ptr;
}
对于代码中的type类型,Redis一共有五种:
Type | 名称 |
String | 字符串 |
List | 列表 |
Hash | 哈希 |
Set | 集合 |
ZSet | 有序集合 |
也就是说,对于Redis而言,只有五种数据类型:字符串,列表,哈希,集合,有序集合。
但是Redis的底层实现,并不仅仅这五种类型,换言之,这五种数据类型是由更加底层的数据编码类型实现的,也就是Redisobject里面的encoding数据编码类型。
1.那么Redis里面有几种encoding?
2.这些encoding是怎么组合形成这五种type呢?
首先,我们先回答第一个问题:
encoding | 类型 |
int | long类型的整数 |
embstr | embstr类型的简单字符串 |
raw | 简单字符串SNS |
ht | 字典 |
linkedlist | 双端列表 |
ziplist | 压缩列表 |
skiplist | 跳跃列表以及字典 |
intset | 整数集合 |
这里可以看到,即使是编码类型encoding,也并不代表Redis最底层的数据结构,在ziplist中,是由跳跃列表以及字典联合实现的。
现在我们回答第二个问题:
encoding跟type之间的关系是什么?
type | encoding |
String | int/embstr/sns |
List | ziplist/linkedlist |
set | intset/ht |
zet | skiplist/ziplist |
hash | ht/ziplist |
在我们回答了这两个问题之后,我们就可以考虑接下来的学习方向。即这两个表格,深究每种编码类型以及数据结构。
数据结构一:字符串String
字符串String是根据实际情况可能由三种编码类型组成:int,embstr,raw。
使用Get可以创建一个String对象。
首先来看int :在Redis中如果使用set 语句给一个变量赋值,如果该变量的值在long类型整数的范围内,那么Redis会在底层使用int保存。在此范围外的所有String 都是用embstr、raw这两种编码类型储存的。
embstr:是Redis开发用来存储小内存字符串的编码类型。当储存的字符串大小小于32字节时,使用embstr来储存。
raw:那么除去以上两种情况,所有其他情况的String类型都是使用raw来进行存储的。
1.int类型
使用语句创建一个值为10086的String对象。
set number 10086
其底层结构可以使用图来表示:
这里我们可以看到使用set方法创建的type为String的对象,因为其值10086在long整型数据范围内,因此底层使用encoding为int的编码方式来存储。其指针指向一个int 10086。
使用语句来创建一个值为hello wordl trytry tryrtry tryrtry …的字符串
2.raw类型
set msg “hello wordl trytry tryrtry tryrtry .....”
其底层结构可以表示为
因为该字符串使用的字节数已经超过了32位,所以其使用的底层编码结构是raw,则其type为String,encoding为raw,其ptr指向一个结构该结构为raw简单动态字符串的结构。
由该示意图大家可以看到raw是由三部分组成的free、len、buf。
free标记的是该简单字符串中空闲的部分,len标记的是该字符串中使用的部分。buf指的是一个数组,用来存储该字符串的各个字符。
1.但是有一点需要声明,buf数组的底层操作时并不是认定数据为char类型,而是使用了二进制操作,这保证了Redis在存储数据时的可靠性。
2.Raw类型因为加入了free、len这两个参数,让字符串的一些操作的时间复杂度降低。
3.因为free以及len的设定,让raw跟字符串扩充或者缩减时频繁的内存操作说了再见,让其可以更高效地修改字符串。
关于raw的详情介绍,请戳
todo
3.embstr类型
使用语句来创建一个值为hello的字符串
set msg “hello”
其底层结构可以表示为
上图可以看到embstr跟raw在结构上十分类似,那么区别在什么地方呢?
指针
raw中,使用指针指向sdshdr,然而在embstr中,sdshdr在内存上跟RedisObject是连续的。
那么这点会给我们带来怎么样的效果?
1.释放字符串对象时,raw对象的释放,是需要操作两次内存的,对于embstr只需要一次。
2.创建字符串对象时,raw对象需要操作两次,对于embstr只需要一次。
3.因为embstr在内存上是连续的,对于缓存来说,embstr有更高的效率。
数据结构二:列表List
列表List由两种编码类型组成:ziplist和Linkedlist两种编码结构。
使用Rpush可以创建一个list对象。
1.当创建对象的值保存的字符串对象大小小于64字节的时候;
2.当创建的对象元素个数大于512个 的时候。
同时满足这两个条件的时候,底层会使用ziplist存储List对象。
否则,使用Linkedlist存储list对象。
1.Ziplist
使用代码创建一个List对象。
Rpush number 1 , "three" , 5
这里因为创建对象的值满足了以上1,2条件,因此底层使用ziplist存储。
其结构图如下所示:
注意:这里number值的存放在底层是以entry的形式存放的,并不是我们理解的底层是以字符串实现的。entry可以存放整数或者字节数组。
关于ziplist的详情介绍,请戳
todo
2.LinkedList
使用代码创建一个List对象
Rpush number 1,2,3,4,5,6,7,8.........513
这时候使用的就是Linkedlist作为底层来实现,其结构图如下所示:
这里要注意Redisobject的指针指向的是一个Linkedlist,但是linkedlist的每个节点底层是一个StringObject,在五种数据结构里面,只有Stringobject是可以作为这样的形式存在于其他数据结构里面的。
关于Linkedlist的详情介绍,请戳
todo