前面几个文章里我们介绍到了字典dict和跳表skiplist,它们都是redis为了追求性能而开发的基本数据结构,里面或多或少都借助了一些辅助的元素;例如字典dict在rehash时会同时存在两个哈希表,又或者跳表skiplist里节点多了层的结构,这些设计都是为了追求性能而牺牲了内存空间。
如果你多多少少了解HashMap的底层原理的话,你就知道:
在JDK1.8中,随着元素越来越多,HashMap发生hash冲突,桶中元素大于等于8个,并且容量大于等于64时,会由链表形式转化为红黑树结构;而当桶中元素降到6时又会转换回链表。
为什么有这样的变化呢?因为这体现了时间和空间平衡的思想,元素刚开始并不多时,链表的空间占用是比较少的,并且由于链表短,查询需要的时间也没有太大问题;可是随着链表越来越长,查询的需要的时间也就越来越长,就需要用占用空间大但是查询更高效的红黑树来帮忙了。
时间or空间,看来所有的数据结构都离不开这个命题。
而我们今天要说的压缩列表ziplist就是redis为了节约内存而设计开发的数据结构,并且作为列表键和哈希键的底层实现之一。
PS:在ziplist转成其他数据结构后,不会再退为ziplist结构。
各个部分在内存是连续的,对应的含义如下:
看到了上面这些属性,你可能不是很懂,但它其实算是一个“内存连续的双向链表”。
(自己试着归纳,如有错误还请评论区纠正~)
为什么这么说?你想想看双向链表的几个属性:
而这些我们根据上面的属性都可以得出来:
为什么要用“内存连续的双向链表”啊?当然是为了实现压缩的特点了。
既然是内存连续的,那肯定又牵扯到一个老问题:牵一发动全身
假如我要新添加一个节点,肯定要执行空间重分配操作,而且因为
删除操作也会引发这样的连锁更新,在最坏的情况下需要对压缩列表执行N次空间重分配操作。
但要注意的事,尽管连锁更新的耗时很长,但其实真实发生的概率是很低的:
上面我们是假设每个节点都在250~253字节之间,实际上,这种情况几乎没有。
因为这些,ziplist的一些操作命令的复杂度仅为O(N),我们可以放心使用,不用过分担心上述假设引起的性能问题。
我是苏易困,大家也可以叫我易困,一名Java开发界的小学生,文章可能不是很优质,但一定会很用心。
又一个转眼,清明假期过去了,作为一个假期就躺着的社畜,表示自己的内驱力还是不够,不过我现在想得还是能出去转转,以前一直期盼着居家办公,但真的居家办公后还是闷得发慌,而且加上最近发生一些不开心的事情,确实需要时间来整理心情。
但一码归一码,博文更新还是不能落下,redis的基本数据结构不知道后面还会不会继续,因为还有两个数据结构quicklist和intset我觉得没什么特别的地方,可能会从redis别的方面再入手写一点东西吧,也有可能会开启新的篇章吧~现在还不好说,大家就一起加油吧~