在Redis中有五种数据类型,分别是string,hash,list,set,sorted set。分别是字符串,字典,列表,集合,有序集合。一起聊聊底层是数据结构。字符串没什么好说的,重点聊聊列表、集合、有序集合。
1、列表list
list支持存储一组数据,底层对应有两种实现,分别是压缩列表ziplist和双向循环链表。当列表中数据比较小的时候,list采用ziplist作为实现方式。具体是单个数据小于64字节,列表中个数少于512。
其实这种ziplist方法在我们开发过程中也经常用到。比如我们传输二进制数据(非序列化),可以采用这种方式。格式如下:
<data_len><data_content><data_len><data_content>
压缩列表的好处就是节约内存,相比数组存储方式可以节约很多内存。因为数组定义肯定要按照最长的数据来申请内存。其余达不到这个长度的就会有部分内存空闲。当数据量较大的时候,list将转化为双向链表来进行存储。
2、字典hash
字典常用于存储一组数据对,每个数据包括key和value。字典也有两种数据结构,分别是ziplist和散列表。
当数据量较小的时候采用ziplist进行存储,当数据超过512个,单个长度大于64字节将采用散列表,这里散列表采用的是murmurhash2算法,采用链地址法来解决hash冲突问题。此外,Redis还支持散列表的动态扩容、缩容。这个我之前写过数据结构和算法的文章《手把手写一个集合框架(十)HashMap和HashSet》可以参考扩容和缩容、hash冲突进行rehash等。注意,这里在redis里面扩容是分批进行的,避免一次性扩容进行数据搬迁造成服务停顿现象。
3、集合set
set用于存储一组不重复的数据,这种有两种实现方式,分别是有序数组和散列表。当数据都是整数或数据个数不超过512的时候,将采用有序数组进行存储。其他情况采用散列表进行存储(可以想象为hashset)。
4、有序集合sortedset
有序集合用于存储一组数据,这里每个数据会携带一个score。通过score的大小进行排序。当数据较小的时候,将采用压缩列表ziplist进行存储。这里的条件是单个数据小于64字节,元素个数小于128。当超过这个的时候,将采用跳表skiplist进行存储而不是红黑树的实现。(像STL里面的map,Java集合里面的hashmap底层都是红黑树实现,红黑树的查找、插入平均复杂度为o(lgn))。
跳表作为一种平衡数据结构,经常和平衡树进行比较,在大多数场景下,跳表都可以达到平衡树的效率(查询节点支持平均O(lgN),最坏O(N)的复杂度),但实现和维护起来却比平衡树简单很多。
简单的说,跳表就是将顺序查找的链表经过节点的抽取来达到二分查找的效率。比如下面这个图很形象的展示了跳表的查找过程。
关于跳表,这里推荐几篇文章供参考。
1、http://ju.outofmemory.cn/entry/81525
2、http://www.imooc.com/article/258009
关于跳表的时间复杂度和空间复杂度,也可以自己推导一下。这里总结一下,跳表的平均插入、删除、查找时间复杂度都是o(lg(n)),空间复杂度o(n)。
本文先写到这里吧,明天继续搬砖去了,后续有空继续深入聊聊。
注:封面图片是去秋游摘葡萄拍的。