关于Redis的五大数据类型,它们分别为:String、List、Hash、Set、SortSet。本文将会从它的底层数据结构、经常使用操做命令、一些特色和实际应用这几个方面进行解析。对于数据结构的解析,本文只会从大的方面来解析,不会介绍详细的代码实现。redis
String是Redis中最经常使用的一种数据类型,也是Redis中最简单的一种数据类型。首先,表面上它是字符串,但其实他能够灵活的表示字符串、整数、浮点数3种值。Redis会自动的识别这3种值。那么,String的底层数据机构又是怎样的呢?因为Redis是使用c语言实现的,而c语言中没有String这一数据类型,那么就须要本身实现一个相似于String的结构体。它的名字就叫作SDS(simple dynamic string),下面是它的代码结构。数据库
1 typedef struct sdshdr { 2 // buf中已经占用的字符长度 3 unsigned int len; 4 // buf中剩余可用的字符长度 5 unsigned int free; 6 // 数据空间 7 char buf[]; 8 }
若是有了解过Java集合框架类的朋友都知道,这种结构与集合中动态数组结构相似,那么就会涉及到一系列的扩容判断和操做,但这些具体的作法在这里不深刻讲解。不过有一点比较重要的就是String的value值最大能够存放512MB的数据,因此有时候它不只仅能够存放字符,还能够存放字节数据。json
在讲实际应用以前,要声明的是Redis是基于单线程IO多路复用的架构实现的NoSql,意味着它的操做都是串行化的,因此在命令操做上不会出现线程安全问题,基于这个特性能够有不少应用。数组
首先,List的主要存取操做有lpush、lpop、rpush、rpop,有点像是双向队列。List的实现是灵活多样的,它分别有ziplist(压缩链表)、LinkedList(双向链表)两种实现方式。安全
以下图所示,它是基于连续内存实现(相似数组)。固然,它的每个entry的大小可能不是一致的,这就须要特殊的控制手段去解决,因此才叫压缩表。那么数组有的特性它都会有,好比在lpush、lpop的时候就会有数据的搬移,时间复杂度是O(n)。因此,通常在数据元素较少时使用ziplist结构实现。session
则与咱们平常所学的双向链表相差无异,一样也保留则头尾指针、数据长度等数据,这里就再也不详细说明,须要了解的去读一读Java的LInkedList源码也不错。数据结构
首先,Hash的特性咱们能够想象为Java集合中的HashMap,一个hash中能够有多个field:value(键值对)。关于hash的实现一样有两种状况。一种是基于ZipList,一种是基于HashTable实现。架构
这里的的ZipList与List当中的ZipList实际上是相差无几的,惟一的特色就是Hash存储的时候,它的entry数量是成对增长的,同时也是成对存在的,因此它的长度必定是2的整数倍。filed值放在前面,value放在后面的形式存放。固然采用ZipList操做时,它的查找删改查的时间复杂度就会变为O(n),因此ZipList适合在数据较少的状况下使用。框架
2.HashTable分布式
虽说Hash与Java中的HashMap功能相似,但在HashTable这个结构上仍是有必定的不一样点的。要想了解HashTable的实现,须要了解三个结构。它们分别是:dict、dictht、entry。entry和前面list中提到的相似,下面列出前面两个结构的定义:
1 // 哈希表(字典)数据结构,Redis 的全部键值对都会存储在这里。其中包含两个哈希表。 2 typedef struct dict { 3 // 哈希表的类型,包括哈希函数,比较函数,键值的内存释放函数 4 dictType *type; 5 // 存储一些额外的数据 6 void *privdata; 7 // 两个哈希表 8 dictht ht[2]; 9 // 哈希表重置下标,指定的是哈希数组的数组下标 10 int rehashidx; /* rehashing not in progress if rehashidx == -1 */ 11 // 绑定到哈希表的迭代器个数 12 int iterators; /* number of iterators currently running */ 13 } dict; 14 15 typedef struct dictht { 16 //槽位数组 17 dictEntry **table; 18 //槽位数组长度 19 unsigned long size; 20 //用于计算索引的掩码,能够理解为hash函数 21 unsigned long sizemask; 22 //真正存储的键值对数量 23 unsigned long used; 24 } dictht;
关系能够总结为下面这幅图:
Set是一个不容许重复的,无顺序的数据集合。值得注意的是,这里说的无顺序其实仍是有一点歧义的,那么究竟是怎么回事呢?接下来的博文就会有提到这个差别。
这里的IntSet是一种在知足特定状况下所使用的数据结构。这种状况就是当所有value都为整型时,redis会使用IntSet这种结构。在这个状况下它是有序的。这是为何呢?先从结构开始提及,为了平衡空间的性能的消耗,Redis在数据都为整型的时候使用了一种基于动态数组的结构体,同时在存放元素时保正元素的大小顺序,这样就可使用二分查找以时间复杂度O(logn)来完成增删改查的操做。这样就节省了不少空间。至于增和删操做,一样会涉及到数组的数据搬移操做。下面为它的结构体代码:
1 typedef struct intset { 2 // 编码方式 3 uint32_t enconding; 4 // 集合包含的元素数量 5 uint32_t length; 6 // 保存元素的数组 7 int8_t contents[]; 8 } intset;
当存在非整型数据的时候,Redis会自动把IntSet转换为HashTable的结构存放数据,但HashTable不能转换为IntSet。这里的HashTable与上面Hash结构提到的HashTable没有太大的差异。惟一的差异就在于Set存放在HashTable中只有Key值,没有value值,因此在HashTable的Entry中,Enrty的value永远为null。
SortSet是一个实现了数据有序且惟一的键值对集合。其中,Entry的键为string类型,值为整型或浮点型,表示权值score。其中SortSet的顺序就是经过Score的值来肯定的。
SortSet的实现结构一样有两种,一种是ZipList结构实现,适用于较少数据的状况。另外一种是SkipList+HashTable的形式,使用与数据较多的状况,其中SkipList是在保证有序的状况下优化范围查找的时间复杂度,而HashTable则是优化增删改查的时间复杂度。
SortSet的ZipList和Hash中的数据结构相似,一样也是存放键值对,可是它维护了基于Score的有序性(默认从小到大),这里就再也不赘述。
首先来讲明主要的SkipList(跳表),跳表是一种基于有序链表,经过创建多层索引,以空间换时间的方式实现平均查找效率为O(logn)复杂度的一种数据结构。下面给出一个跳表的基本形式图:
能够看到在根据数据的权值Score进行查找的时候,从最顶层的索引开始查找。当找到数据在某个范围后,在往下一层的索引查找,而后就这样一路缩小查找的范围。看上去是否是有点像二分查找?至于跳表的具体介绍和实现,能够参考这篇文章:为何Redis要用跳表实现有序集合?
上面能够看到,基于跳表的实现的有序集合能够完成增删改查实现O(logn)的时间复杂度,那么有没有更加快的方式来实现O(1)的时间复杂度呢?一般提及O(1)的时间复杂度都会想起HashTable这个数据结构。Redis就利用HashTable+SkipList的组合数据结构,HashTable来实现增删改查的时间复杂度为O(1)的同时SkipList保证数据的有序性,能够方便的获取一个范围的数据。
至于HashTable的实现与前面谈到的一致,下面用一张图来讲明两个数据结构结合是什么样子的。
上图中,每个节点能够当作一个跳表的节点同时也是HashTable中的一个节点。
这样,当咱们须要增删改查的时候,利用HashTable的特性实现时间复杂度为1的操做,当咱们须要基于权值Score进行范围查找的时候能够经过SkipList进行时间复杂度为O(logn)的查找。
太多东西记不住?来张思惟导图帮你记忆一下。