redis五大数据结构:string,hash,list,set,zset(有序集合)
redis底层数据结构:简单动态字符串(SDS),链表,字典,跳表,整数集合,压缩列表
底层数据结构详解:
1.简单动态字符串:类似于c的结构体,但是SDS拥有记录已用长度(len)与剩余空间长度(free),当空间不足时会进行扩容。SDS最后会保存一个空字符
所以SDS获取字符串长度的时间复杂度为o(1),SDS自动扩容也不会像C一样产生溢出
利用未使用空间,SDS拥有空间预分配和惰性空间释放两种优化方法。
空间预分配:
修改SDS之后之后,如果len<1MB,将free修改为和len一样的长度,如果len>=1MB,将free修改为1MB。
例如:str = "redis"
如果拼接str与"cluster",那么修改之后len为5+6=11,11+11+1=23,len/free值都是11,加上最后一个空字符,总长为23。
要是str修改之后大小为20MB,那扩容之后就是20MB+1MB+1byte
将扩容需要的次数从最多n次变为至多n次。
惰性空间释放:
不会真正释放内存大小,例如删除redis中的字符e与d,那么str就变为ris,但是free=2。
这样减少释放空间的操作,将来扩容也会减少操作。
tips:SDS有专门的api可以真正释放空间,不用担心惰性释放带来空间的浪费。
二进制安全
C字符串中的字符必须符合某种编码(比如ASCII),并且除了字符串的末尾之外,字符串里面不能包含空字符,否则最先被程序读入的空字符将被误认为是字符串结尾,这些限制使得C字符串只能保存文本数据,而不能保存像图片、音频、视频、压缩文件这样的二进制数据。
因此,为了确保Redis可以适用于各种不同的使用场景,SDS的API都是二进制安全的。
SDS和c的区别:
2.链表
应用:列表键的底层实现之一就是链表。当一个列表键包含了数量比较多的元素,又或者列表中包含的元素都是比较长的字符串时,Redis就会使用链表作为列表键的底层实现。
除了链表键之外,发布与订阅、慢查询、监视器等功能也用到了链表,Redis服务器本身还使用链表来保存多个客户端的状态信息,以及使用链表来构建客户端输出缓冲区。
一个链表结点:
链表:
3.字典(key-value)
字典在Redis中的应用相当广泛,比如Redis的数据库就是使用字典来作为底层实现的,对数据库的增、删、查、改操作也是构建在对字典的操作之上的。
字典还是哈希键的底层实现之一,当一个哈希键包含的键值对比较多,又或者键值对中的元素都是比较长的字符串时,Redis就会使用字典作为哈希键的底层实现。
Redis的字典使用哈希表作为底层实现,而每个哈希表节点就保存了字典中的一个键值对。
字典所用的哈希表结构:
每个dictEntry结构保存着一个键值对
dictEntry结构:
字典结构:
type属性是一个指向dictType结构的指针,每个dictType结构保存了一簇用于操作特定类型键值对的函数,Redis会为用途不同的字典设置不同的类型特定函数。
privdata属性则保存了需要传给那些类型特定函数的可选参数。
Redis的哈希表使用链地址法来解决键冲突。
扩展和收缩哈希表的工作可以通过执行rehash(重新散列)操作来完成。
Redis Bgsave 命令用于在后台异步保存当前数据库的数据到磁盘。
对哈希表进行扩展:1.正在执行bgsave,负载因子>=5 2.没有执行bgsave,负载因子>=1
对哈希表进行收缩:负载因子<0.1
渐进式hash:rehash动作并不是一次性、集中式地完成的,而是分多次、渐进式地完成的。
原因:如果哈希表里保存的键值对数量千万甚至亿个键值对,那么要一次性将这些键值对全部rehash的话,庞大的计算量可能会导致服务器在一段时间内停止服务。
所以rehash期间,更新,删除,查找在两个表里进行,旧表查找不到的话去新表查找。
而插入就直接在新表进行,所以旧表只减不增。
字典使用哈希表作为底层实现,每个字典带有两个哈希表,一个平时使用,另一个仅在进行rehash时使用。
4.跳跃表
Redis使用跳跃表作为有序集合键的底层实现之一,如果一个有序集合包含的元素数量比较多,又或者有序集合中元素的成员是比较长的字符串时,Redis就会使用跳跃表来作为有序集合键的底层实现。
Redis只在两个地方用到了跳跃表,一个是实现有序集合键,另一个是在集群节点中用作内部数据结构。
每个节点都有向前,向后的指针,向后的可能有多个(看有多少层有这个数据),向前的只有一个。
插入方法:从底层开始,每一层选择插入或者不插入,插入一层的话就停止。
所以第一层的插入概率1/2,第二次1/4...
5.整数集合
整数集合是集合键的底层实现之一。
整数集合的结构:
contents数组各个项在数组中按值的大小从小到大有序地排列,并且数组中不包含任何重复项。contents数组的真正类型取决于encoding属性的值。
升级:
每当我们要将一个新元素添加到整数集合里面,并且新元素的类型比整数集合现有所有元素的类型都要长时,整数集合需要先进行升级,然后才能将新元素添加到整数集合里面。
升级步骤:1.根据新数据类型,扩展底层数组大小 2.将其他数据设置为新数据类型 3.新元素添加到底层数组
升级好处:一个是提升整数集合的灵活性,另一个是尽可能地节约内存。
节约内存:有int_16,int_32,int_64三种类型,整数集合现在的做法既可以让集合能同时保存三种不同类型的值,又可以确保升级操作只会在有需要的时候进行,这可以尽量节省内存。
没有降级操作。
6.压缩列表
压缩列表是列表键和哈希键的底层实现之一。
一个列表键只包含少量列表项,并且每个列表项要么就是小整数值,要么就是长度比较短的字符串,就会用压缩列表。