Redis基础数据类型
redis有5中基础数据类型结构,分别为 string(字符串)、list(列表)、hash(字典)、set(集合)、zset(有序集合).
redis所有的数据结构都用一个唯一的key字符串作为名称,然后通过这唯一的key获取相应的value数据,不同类型的数据结构在于value的结构不同,也就是说不管是什么数据类型,他们的key都是字符串,代表能找到相应数据的标识。
string(字符串)
字符串结构用于存储一些固定的信息,例如用户信息,在系统中将用户信息序列化成一个json字符串,随后根据用用户id作为key,json串作为value存入redis,
后续的过程中,获取用户信息时,就可以通过id来redis中获取,避免频繁访问数据库。
内部实现
和java中的字符串不同,java中的字符串对象是不可变的,在redis中,字符串是可变的,内部结构的实现类似于Java中的ArrayList,采用预分配冗余空间的方式减少内存的频繁分配,
如下图,内部为字符串分配的实际空间(capacity)一般要高于实际字符串长度(len),当字符串长度小于1MB时,扩容是按照当前的空间翻倍,如果超过1MB,扩容每次最多只多加1MB的空间。
打个假设, value 是 hello 时,那么字符串的长度可能是10,前面五个存hello,后面5个用于应对后续的扩容。
PS:字符串的最大长度是512MB。
要点:Redis的字符串可扩容,大小不超过1MB时,翻倍拓容,超过则每次只加1MB。
list(列表)
Redis的列表和Java中的LinkedList相似,是链表结构,
既然是链表,则意味着插入和删除的速度非常快,时间复杂度为O(1),但是查找就很慢了,时间复杂度则是O(N),
链表中的每个元素都有双向指针,可以指向上一个元素和下一个元素,方便支持向前遍历和向后遍历,当列表弹出最后一个元素后(也就是链表中没数据了),该list将会被自动删除,list占用的内存被回收。
快速列表
Redis的底层不是一个简单的LinkedList,而是一个quicklist(快速列表)结构,
当列表元素较少的情况下,会使用一块连续的内存存储,这个结构是ziplist,即压缩列表,它将所有的元素紧凑的放在一起存储,使用的是一块连续的内存空间(数组),当数据量比较多的时候才会改为quicklist。
因为普通的链表需要两个指针,指针也是占用内存空间的,还会加重内存的碎片化,比如一个链表中存的是int类型数据,还需要额外为这个数据增加两个额外的指针(prev和next),
所以redis将链表和ziplist结合组成quicklist,既能满足快速的插入删除性能,又不会出现太大的空间冗余,在性能和空间之间做了平衡。
要点:Redis中的列表其实是一个链表和ziplist的组合(为了节省空间),称为quicklist,而非想象中的类似java的ArrayList,该结构的好处是新增删除比较快,查找较慢。
hash(字典)
Redis字典和java的HashMap类似,都是无序字典,内部存储着键值对,同时双方的数据结构都是 数组+链表 的二维结构,当数组位置发生碰撞时,就会将碰撞的元素使用数组位置下的链表进行串联。
不同的是,Redis字典的值只能是字符串,并且rehash的方式和java也不一样,java的HashMap在rehash时,需要一次性全部完成,在数据量比较大的时候,性能较差,会比较慢,
redis为了追求高性能,保证rehash的时候不阻塞服务,采用了渐进式rehash策略。
渐进式 rehash
渐进式rehash会在rehash的同时,保留新旧两个hash结构,查询时会同时查询两个hash结构,
然后在后续的定时任务以及hash操作指令中,循环渐进的将旧hash的内容一点点迁移到新的hash结构中,
当迁移完成,新的hash将替代老的hash,老的hash在最后一个元素被移除后,将会被自动删除,内存被回收。
set(集合)
Redis中的集合相当于Java语言中的HashSet,键值对是无序,但唯一的,内部实现是一个字典,字典中所有的value都是一个null值,当集合中的最后一个元素被移除时,数据结构将被自动删除,内存则被回收。
zset(有序集合)
zset类似Java的SortedSet和HashMap的结合体,一方面是一个set可以保证唯一性,另一方面可以给set中的每个数据设置一个score,用于给数据进行排序,
zset的内部数据结构是一个叫做跳跃列表的树结构,同set一样,当集合中的最后一个元素被移除时,数据结构将被自动删除,内存则被回收。
跳跃列表
zset需要支持随机插入和删除,使用数组自然不合适,所以存储需要使用链表数据结构,同时这个链表还需要使用score值进行排序,
有序意味着,当有新元素要插入时,要定位到特定位置的插入点,这样才可以保证链表是有序的,通常定位插入点会用二分查找来做,但是只有数组才支持二分查找,链表做不到,此时就需要跳跃列表帮忙做这件事,
举个例子,如果在一个几百号人的公司里,老板分配任务,在没有组织架构的情况下,得单独为每个人分配,这样必然会很累,
但是公司可以选部门经理,再选小组组长,再到组员,这样老板每次下达任务只需要找部门经理,部门经理再找组长,组长再找组员,这样就会轻松很多,
跳跃列表就类似于上面的层级制,所有的元素都在最下层用链表串着,每隔几个元素选出一个组长,组长之间再选部门经理,
之所以叫做跳跃列表,是因为一个元素可能会同时存在于不同的层次中,例如下图中间的元素,可以同时存在于L0、1、2 三层中,
定位插入点时,先在顶层进行定位,然后下潜到下一层,直到下潜到最底层,将新元素插入进去,用这种方式可以达到类似二分查找的效果。