众所周知,redis有String、List、Hash、Set、Sorted Set这五大基本数据类型,不同的数据类型适用不同的场景。不过相信大多数程序员用得最多的还是String,看起来String像是万能的,但你以为String就是简单的字符串吗?其实不然,redis每个数据类型的底层结构都大有文章。
给大家丢个图就明白了,上面是基本类型,下面是底层结构。像有序集合Sorted Set就用到了压缩列表和跳表。
现在知道面试官为啥喜欢问redis底层数据结构跳表之类的了吧,原来知识点都在这呢,还不赶紧来复习一下。
在介绍SDS之前,得先对redis有个基本认知,即redis是一个kv键值数据库,由一张大的哈希表组成,存储的每个字典条目(dictEntry)都是一组kv键值对,dictEntry结构中有三个8字节的指针,分别指向key、value 以及下一个dictEntry,三个指针共 24 字节。key和value都是redis对象(redisObject)。
一个redisObject会包含一个8B的元数据信息及一个8B的指针。具体来讲,8字节的元数据可能包括如下信息:
8字节指针指的是一个指向实际数据结构的指针,比如指向SDS的指针或者是其他复杂数据结构的指针。
String数据类型背后使用的是自定义的动态字符串类型,也就是我们常说的SDS(Simple Dynamic String),它有int、embstr和raw这三种编码模式。
当列表、哈希、有序集合存储的数据量较少时,redis就会考虑用ziplist来存储。表结构如下:
压缩列表实际上类似于一个数组,数组中的每一个元素都对应保存一个数据。和数组不同的是,ziplist每个元素长度可以不同,并且在表头有三个字段 zlbytes、zltail 和 zllen,分别表示列表长度、列表尾的偏移量和列表中的 entry 个数;压缩列表在表尾还有一个 zlend,表示列表结束。
在压缩列表中,如果我们要查找第一个元素和最后一个元素,可以通过表头三个字段的长度直接定位,复杂度是 O(1)。而查找其他元素时,就没有这么高效了,只能逐个查找,此时的复杂度就是 O(N) 。
跳表(skiplist)是在有序链表的基础上,增加了多级索引,通过索引位置的几个跳转,实现数据的快速定位。建立索引可以每隔2个数据建立索引,也可以隔3个或5个。
像下面这一个有序列表,3,7,11,19,22,26,37。不用跳表需要查找6次,而利用跳表建立的索引,只需要比较4次,时间复杂度可以从原来的O(N)降到O(logN)。
好了,SDS、ziplist、skiplist都介绍完了。至于其他的,像hash和set用到的哈希表就是一个hashMap的kv键值对,相当于redis弄了一个嵌套的哈希结构。整数数组和双向链表的操作特征都是顺序读写,也就是通过数组下标或者链表的指针逐个元素访问,操作复杂度基本是O(N),操作效率比较低。
显然,整数数组和压缩列表在查找时间复杂度方面并没有很大的优势,那为什么Redis还会把它们作为底层数据结构呢?主要出于以下两点考虑:
以上就是全部内容,通过深刻理解Redis的底层数据结构,我们可以更加明确地选择适合自己业务场景的数据类型,从而充分发挥Redis的性能优势。