427的java基础八股(Redis常用数据类型)
Redis常用数据类型
目录
String
是二进制安全的, Redis 不会对存储的内容做任何编码 / 解码处理,能存储任意格式的二进制数据
存储:
文本字符,数字,二进制数据
自增自减等操作是原子性的
支持设置过期时间
单个value最大容量512mb
底层实现
SDS动态字符数组

自动预分配(扩容) / 惰性释放(缩容),减少内存操作次数
缓冲区溢出
C 语言提供的字符串操作函数,大多数都是不安全的,因为这些函数把缓冲区大小是否足够用交由开发者来保证,程序内部并不会判断缓冲区大小是否足够用,当发生了缓冲区溢出就有可能造成程序异常结束。
SDS 结构里引入了 alloc 和 len 成员变量, 通过 alloc - len 计算,可以算出剩余可用的空间大小,这样在对字符串做修改操作的时候,就可以由程序内部判断缓冲区大小是否足够用。当判断出缓冲区大小不够用时,Redis 会自动将扩大 SDS 的空间大小,以满足修改所需的大小。
Hash
压缩列表
哈希对象保存的所有键值对的 键和值的字符串长度都小于等于 64 字节;
哈希对象保存的 键值对数量小于等于 512 个。
哈希表
超过以上两个阈值的其中一个就会转换成哈希表
List
Zset
ZSet 的核心是「有序」+「唯一」:member(成员)唯一,按 score(分数)排序(score 相同则按 member 字典序),value是set集合实现的,无序且唯一
底层实现:
元素数量(member 总数)小于128且元素(member )大小小于64用压缩列表,反之跳表
ziplist(redis6之后被listpack替代)


prevlen
若前一个 entry 的长度 < 254 字节:prevlen用1 字节存储(直接存长度数值);
若前一个 entry 的长度 ≥ 254 字节:prevlen用5 字节存储(第 1 字节固定为0xFE标记,后 4 字节存长度数值)。
连锁更新问题
扩容/缩容,根据prevlen的特性
连锁更新会导致多次内存重新分配 + 大量数据移动
listpack



skiplist + 哈希表
哈希表
扩容
触发时机由负载因子决定



skiplist
核心结构
多层链表
查询操作逻辑
从最高层开始,每层内多次跳转(每次判断后继是否≤目标,是则跳转,最后一个大于目标值的节点是该层的前驱节点,为后续做插入操作做准备),直到不能跳为止,再向下一层,接着该节点继续跳转(上层的所有元素是下层的集合,也就是说下一层一定包含上一层的所有元素,这样才能接着上一层的元素继续遍历);重复此过程直到 L0 层,最终验证目标是否存在,在L0 层找到值相等的节点则返回” —— 总跳转次数是 O (log n),而非 O (n)(遍历所有节点)。
插入操作逻辑
先查询,需要确定晋升层数,通过随机算法,在0~1之间随机选一个数,小于0.25就晋升一层,一直到大于0.25就停止,在所有晋升的层中插入节点,前驱节点的后继指针指向新节点,新节点的后继指针指向原前驱的后继节点(若存在,不存在就置为null)。
删除操作逻辑
先查询,对目标节点存在的层级,将前驱节点的后继指针直接指向目标节点的后继指针节点,跳过目标节点(无需额外维护前驱指针),目标节点等待被gc回收
为什么用跳表不用b+数
内存 vs. 磁盘的设计初衷 (最重要的原因)
B+ 树是为磁盘 I/O 优化的: B+ 树的设计核心是降低树的高度,从而减少磁盘 I/O 次数。
Redis 是纯内存操作:在内存中,没有 “磁盘 I/O” 的瓶颈。B+ 树为了减少高度而设计的复杂页管理机制,在内存场景下显得多余且笨重。
实现复杂度与代码维护
B+ 树:插入和删除可能引发节点的分裂(Split)和合并(Merge),甚至需要对整棵树进行重平衡,代码量大且难以调试。
跳表(Skip List): 本质上是 “多层链表”。其插入、删除逻辑主要是修改指针,代码实现非常简洁
写入性能与重平衡代价
B+ 树:当插入数据导致页分裂时,可能需要移动大量数据或改变树结构
跳表的局部性:跳表的插入和删除操作是局部的。插入一个节点只需要修改前后节点的指针
Set
扩展数据类型
Bitmap
HyperLogLog
Geo
Stream
更多推荐




所有评论(0)