1、基础数据结构(五种)
string(字符串)、list(列表)、hash(字典)、set(集合)、zset(有序集合)。
string(字符串)
string内部表示的是一个字符数组,Redis所有的数据结构都以唯一的key字符串作为名词,然后通过这个唯一的key值来获取相应的value数据。不同类型的数据结构的差异就在于value的结构不一样。
Redis的字符串是动态字符串,是可以修改的字符串,内部结构的实现类似于Java的ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配,内部为当前字符串分配的实际空间capacity一般要高于实际字符串长度len,当字符串长度小于 lMB 时,扩容都是加倍现有的空间 。如果字符串长度超过 1MB ,扩容时一次只会多扩1MB 的空间 。字符串最大长度为 512MB。
字符串常见操作:
如果value值是一个整数,可以进行自增操作,范围在signed long的最大值和最小值之间。超出就会报错。
list(列表)
Redis的列表相当于Java中的LinkedList,是一个双向链表。插入和删除操作非常快,时间复杂度为O(1),但是索引定位很慢,时间复杂的为O(n)。当列表弹出了最后一个元素之后,该数据结构被自动删除,内存会回收。
Redis列表结构常用来做异步队列使用,将需要延后处理的任务结构体序列化成字符串,塞进Redis的列表,另一个线程从中轮询数据进行处理。
列表常见操作:队列和栈
如果再深入的一点的话,Redis的底层存储不是一个简单的linkedlist,而是称之为“快速链表(quicklist)”的一个结构。后续介绍。
hash(字典)
相当于Java中的HashMap,是一个无序字典,底层也是链表加数组。但是不同的是Redis的字典的值只能是字符串,另外rehash的方式也不一样。因为 Java HashMap 在字典很大时 rehash 是个耗时的操作,需要一次性全部rehash。Redis 为了追求高性能,不能堵塞服务,所以采用了渐进 式rehash 策略。
渐进式rehash会在rehash的同时,保留新旧两个hash结构。查询的时候会同时查询两个hash结构,然后在后续的定时任务以及hash操作指令中,循序渐进地将旧hash的内容一点点地迁移到新的hash结构中。当搬迁完成了,就会使用新的hash结构取而代之。
当hash移除了最后一个元素后就会该数据结构自动删除,内存被回收。
hash结构也可以用来存储用户信息,与字符串需要一次性全部序列化整个对象不同,hash可以对用户结构中的每个字段单独存储,这样当我们需要获取用户信息时可以进行部分获取。而以整个字符串的形式去保存用户信息的话,就只能一次性全部读取,这样就会浪费网络流量。
hash也有缺点,hash结构的存储消耗要高于单个字符串,到底该使用hash还是字符串,根据实际情况进行权衡。
hash常见操作:
set(集合)
Redis的集合相当于Java语言里面的HashSet,内部的键值对是无序的、唯一的。它的内部实现相当于一个特殊的字典,字典中所有的value都是一个值null。
同样的,如果集合中最后一个元素被删除后这个数据结构就会被自动删除,内存被回收
set结构可以用来存储在某活动中中奖的用户ID,因为有去重的功能,可以保证同一个用户不会中奖两次。
zset(有序列表)
zset类似于Java的SortedSet和HashMap的结合体,一方面它是一个set,保证了内部value的唯一性,另一方面它可以给每个value赋予一个score,代表这个value的排序权重。它的内部实现用的是一种叫作“跳跃列表”的数据结构。
zset中最后一个元素被移除后该数据结构就会被删除,内存被回收。
zset可以用来存储粉丝列表,value是粉丝的用户ID,score是关注时间。我们可以对粉丝列表按关注时间进行排序。
跳跃列表
zset内部的排序功能是通过“跳跃列表”数据结构来实现的,它的结构非常特殊,也比较复杂。因为 zset 要支持随机的插入和删除,所以它不宜使用数组来表示。我们先看一个普通的链表数据结构
我们需要这个链表按照score值进行排序。这意味着当有新元素需要插入时,要定位到特定位置的插入点,这样才可以继续保证链表是有序的。通常我们会通过二分查找来找到插入点,但是二分查找的对象必须是数组,只有数组才可以支持快速
位置定位,链表做不到,那该怎么办?
假设有一家创业公司,刚开始只有几个人,团队成员之间人人平等,都是联合创始人。随着公司的成长,人数渐渐变多,团队沟通成本随之增加。这时候就会引入组长制,对团队进行划分。每个团队会有一个组长。开会的时候分团队进行,多个组长之间还会有自己的会议安排。当公司规模进一步扩展,需要再增加一个层级——部门,每个部门会从组长列表中推选出一个代表作为部长。部长们之间还会有自己的高层会议安排。
跳跃列表就类似于这种层级制,最下面一层所有的元素都会串起来。然后每隔几个元素挑选出一个代表,再将这几个代表使用另外一级指针串起来。然后在这些代表里再挑出二级代表,再串起来。最终就形成了金字塔结构。
想想你老家在世界地图中的位置:亚洲→中国→某省→某市→某县→某镇→某村→门牌某号,也是这样一个结构。
“跳跃列表”之所以“跳跃”,是因为内部的元素可能“身兼数职”,比如图1-12中间的这个元素,同时处于LO、L1和L2层中,可以快速在不同层次之间进行“跳跃”。
定位插入点时,先在顶层进行定位,然后下潜到下一级定位,一直下潜到最底层找到合适的位置,将新元素插进去。你也许会问,那新插入的元素如何才有机会“身兼数职”呢?
跳跃列表采取一个随机策略来决定新元素可以兼职到第几层。
首先其位于LO层的概率肯定是100%,而兼职到L1层只有50%的概率,到L2层只有25%的概率,到L3层只有12.5%的概率,以此类推,一直随机到最顶层L31层。绝大多数元素都过不了几层,只有极少数元素可以深入到顶层。列表中的元素越多,能够深入的层次就越深,元素能进入到顶层的可能性就会越大。
跳跃列表内部结构,后续再更新。