目录

  • hash的数据结构
  • ziplist底层实现
  • 字典
    • 底层实现
    • 扩容
    • 缩容
  • 引用

hash的数据结构

  • hash底层数据结构的实现包括两种:ziplist和字典
  • 当保存的所有键值对字符串长度小于 64 字节并且键值对数量小于 512 时使用ziplist ,否则使用字典的方式

ziplist底层实现

ziplist是为了提高存储效率而设计的一种特殊编码的双向链表。它可以存储字符串或者整数,存储整数时是采用整数的二进制而不是字符串形式存储。他能在O(1)的时间复杂度下完成list两端的push和pop操作。但是因为每次修改操作都需要重新分配ziplist的内存,所以实际复杂度和ziplist的内存使用量相关

  • ziplist 中包含有zlbytes、zltail、zllen、entry、zlend等属性
  1. zlbytes:表示整个ziplist所占的空间大小,占4个字节
  2. zltail:压缩列表尾元素相对于压缩列表起始地址的偏移量,占4个字节
  3. zllen:压缩列表的元素数目,占两个字节;那么当压缩列表的元素数目超过(2^16)-1怎么处理呢?此时通过zllen字段无法获得压缩列表的元素数目,必须遍历整个压缩列表才能获取到元素数目
  4. zlend:压缩列表的结尾,占一个字节,恒为0xFF(255)
  5. entry:压缩列表存储的元素,可以为字节数组或者整数
  • entry 中包含有previous_entry_length、encoding、content等属性
  1. previous_entry_length:记录前一个节点的长度

该属性根据前一个节点的大小不同可以是1个字节或者5个字节;如果数值小于254,那就只用一个字节来表示长度,如果长度大于等于254就用5个字节,第一个字节是固定值254(FE)来标识这是个特殊的数据,剩下的4个字节来表示实际的长度

为什么要这么设计?

压缩列表目的是为了尽可能的节省存储空间,数据进行紧邻存储在一块连续的内存区域中;压缩表中元素的长度的是不同的,且为了减少存储空间,并没有保存前后节点的指针,无法通过后退指针来找到上一个元素,而通过保存上一个节点的长度,用当前的地址减去这个长度,就可以很容易的获取到了上一个节点的位置,通过一个一个节点向前回溯,来达到从表尾往表头遍历的操作

  1. encoding:数据的编码形式(字符串还是数字,长度是多少)
  2. content:实际存储的数据
  • 修改操作耗费性能:ziplist在内存中是高度紧凑的连续存储,这意味着它对修改并不友好,如果要对ziplist做修改类的操作,那就需重新分配新的内存来存储新的ziplist,代价很大

ziplist其实是一个逻辑上的双向链表,可以快速找到头节点和尾节点,然后每个节点(entry)中也包含指向前/后节点的"指针",但作者为了将内存节省到极致,摒弃了传统的链表设计(前后指针需要16字节的空间,而且会导致内存碎片化严重),设计出了内存非常紧凑的存储格式。内存是省下来了,但操作复杂性也更新的复杂度上来了

字典

底层实现
  • 字典(dict):其中包含长度为2的哈希表数组dictht,rehashIdx(默认-1)如果为-1说明当前没有扩容,如果不为 -1 则表示正在进行扩容,记录了原hash表需rehash的数组下标
    hash表数组中,ht[0] 在第一次往字典中添加键值时分配内存空间,而另一个 ht[1] 将会在hash表中数组扩容/缩容才会进行空间分配

  • hash表(dictht):字典dictht数组元素,其中包括了
    1 数据 dictEntry 类型的数组,每个数组的 item 可能都指向一个链表
    2 数组长度 size
    3 sizemask 等于 size - 1
    4 当前 dictEntry 数组中包含总共多少节点

  • hash表数组中元素(dictEntry):真正的数据节点,包括 key、value 和 next 节点

    整体结构如下所示:

扩容
  • 扩容时机:在dict->rehashidx == -1 , 也就是字典没有正在进行扩容/缩容的前提下,以下三种情况下对哈希表进行扩容并标记 dict->rehashidx 字段为0,且扩展的哈希表的数组大小是第一个hash表长度的 2倍
  1. 字典已使用节点数和数组大小之间的比率至少为 1:1,并且 dict_can_resize 为true
  2. 已使用节点数和字典大小之间的比率超过 dict_force_resize_ratio,该值默认为5
  3. 哈希表刚初始化完,是个空表,给哈希数组设置默认大小 DICT_HT_INITIAL_SIZE (4)
  • 扩容方式:为ht[1]分配长度为ht[0]2倍长度,rehashIdx设置为0表示正在进行扩容rehash,采取渐进式rehash
    redis对一个字典的rehash操作,不是一次性把该字典 dict->ht[0] 哈希表上所有哈希数组里的哈希数组元素全部重新哈希到 dict->ht[1]
    而是将全部的rehash操作分散到对该字典操作的各个命令上了,每次进行"一步"哈希操作(增加k/v,删除k/v,查找key,随机返回key)
  1. 下标从dict->rehashidx开始,在 dict->ht[0].table 数组中找到第一个不为NULL的项
  2. 将该项链表上的所有元素全部hash映射到 ditct->ht[1] 上
  3. 每重新映射一个元素, dict->ht[0].used --, dict->ht[1].used ++
  4. 该项链表处理完后,将 dict->rehashidx ++

如果 dict->ht[0].used == 0,说明 dict->ht[0]中的元素已全部rehash到dict->ht[1],释放dict->ht[0].table 数组,设置 dict->ht[0] = dict->ht[1],重置 dict->ht[1]的字段,设置 dict->rehashidx = -1,rehash操作结束

缩容

字典有扩容也有缩容,从字典中删除key后,若字典中的元素个数与字典数组大小满足一定关系,会触发缩容操作,缩绒条件是:

哈希数组长度大于默认值DICT_HT_INITIAL_SIZE (4),且节点数量 与 字典哈希表数字大小的比例 小于10%

缩容后新数组长度为hash表中元素个数

引用

https://blog.csdn.net/a158372582/article/details/106234075
https://baijiahao.baidu.com/s?id=1686862206783599500&wfr=spider&for=pc

redis中hash数据结构相关推荐

  1. Redis之Hash数据结构

    0.前言 redis是KV型的内存数据库, 数据库存储的核心就是Hash表, 我们执行select命令选择一个存储的db之后, 所有的操作都是以hash表为基础的, 下面会分析下redis的hash数 ...

  2. 向内存中连续存入数据_内存节省到极致!Redis中这个数据结构,值得每个程序员了解...

    在之前我们介绍了,Redis有五种基础数据类型,分别是String,Set,List,Hash与SortSet. 今天我们又学习了一个命令,我们可以使用DEBUG OBJECT key查询Redis中 ...

  3. redis 中 Hash哈希介绍 及常用命令 (附有示例)

    目录 一.Redis中Hash介绍 二.常用命令 三.示例 hset hget hmset   .. hexists hkeys hvals hincrbu hsetnx 四.redis中Hash底层 ...

  4. Redis中五大数据结构的底层实现

    来自:DBAplus社群 作者介绍 田兆壮,新炬网络开发工程师.具备扎实的Java.Scala开发经验,熟练使用Python和Shell等脚本语言:具备前后端开发能力,熟练使用关系型数据库和非关系型数 ...

  5. Redis中的数据结构与常用命令

    开发系统:Ubuntu 17.04 Redis驱动:StackExchange.Redis 1.2.3 Redis版本:3.2.1 开发平台:.NET Core 对于Redis的介绍这里只写一句:Re ...

  6. java必学之Redis中的数据结构

    目录 一.String不在适用Redis底层数据结构 1.String类型的内存消耗问题 2:String类型怎么保存数据 2.1RedisObject结构体: 2.2int.embstr和raw这三 ...

  7. redis中hash类型介绍

    在redis中,hash数据类型存储的数据与mysql数据库中存储一条记录极为相似,是一个string类型的field和value的映射表,它特别适合用于存储对象,但字段值只能是字符串,不支持其他类型 ...

  8. 一致性hash和redis中hash槽的区别

    一致性hash主要用于分布式系统中,用于解决数据选择节点存储.选择节点访问.增删节点后数据的迁移和重分布问题.redis集群并没有使用一致性hash,而是使用了hash槽来解决数据分配的问题. 一致性 ...

  9. Redis 的 hash,及其序列化问题

    Redis 的 hash,及其序列化问题 介绍 常用命令 应用场景 hash的序列化存储方式 介绍 一次应用中,需要将MySQL中的表在redis中缓存一份,防止查询次数太多,对数据库造成压力.用到了 ...

最新文章

  1. MariaDB Spider:实现MySQL横纵向扩展的小能手
  2. 粒子群算法求解无约束优化问题 源码实现
  3. App后台开发运维和架构实践学习总结(10)——基于Java-JWT前后端token认证实战使用详解
  4. 推荐系统--收藏起来慢慢研究
  5. ttest函数使用方法_TTEST 函数 - EXCEL 2010 中文帮助文档
  6. 搜狗浏览器安装第三方插件(crx和zip)
  7. Spire.Doc利用word样式读取word文字内容(.NET)
  8. linux节点状态 slurm,linux – 为什么slurm中的作业在TensorFlow脚本中无限期冻结?
  9. 题目:计算 1! + 2! + 3! + 4! +... + 10! 说明:4! 表示4的阶乘。4的阶乘是:1 * 2 * 3 * 4
  10. PAT 甲级1121 Damn Single
  11. 爱他美英国和德国价格查询_德国,以色列和英国转向开源,新的无人驾驶汽车技术以及更多新闻...
  12. Java实战教程:《iHRM 人力资源管理系统》
  13. ArcEngine根据属性分割要素类的实现方法
  14. 用QT制作图片转换成ICO格式 领卓教育
  15. Discuz!论坛教程之设置帖子被支持/反对(顶/踩)后自动提升主题
  16. 辰东暂排第二:第四届橙瓜网络文学奖20年十佳玄幻大神
  17. 平房误差函数_误差函数公式及性质
  18. iBackup Viewer for mac一款好用的(iPhone备份提取工具)
  19. 中兴B863AV3.2-M_S905L_支持3A和3A-B_安卓9.0_一次线刷_刷机固件升级包及教程
  20. Intel IPP for Linux

热门文章

  1. System.Globalization.CultureInfo.InvariantCulture 解决不同地域字符串格式不同问题
  2. B站【狂神说Java笔记】-注解和反射
  3. 页面载入动画(loading)
  4. 柠檬水找零---贪心算法(c++)实现
  5. Python实用技术二:数据分析和可视化
  6. Android 10 去掉系统默认谷歌输入法
  7. 如何在网络上找到另一台计算机,怎么用一个电脑通过另一台电脑共享上网?
  8. 基于visual c++之windows核心编程代码分析(61)打造自己的Windows输入法
  9. 取长补短战争升级,“蔚小理”煮酒论英雄
  10. 终端切换python环境_mac命令行切换python版本