背景

上一节讲Redis的高性能字符串结构SDS,今天我们来看一下redis的hash对象。

Hash对象

简介

  • redis的hash对象有两种编码(底层实现)方式,字典编码和压缩列表编码。在使用字典编码的时候程序就是将hash表的key存为字典的键,hash的value作为字典的值,字典的键值都是用的是字符串类型。
  • 在哈希对象保存的所有键值对的键和值的字符串长度都小于 64 字节和哈希对象保存的键值对数量小于 512 个使用的是ziplist,不能满足这个的使用的是hashtable(字典编码)

深度理解

ZipList(压缩列表)

  1. redis 的压缩列表是一块连续的内存空间,元素之间紧挨着存储,没有任何冗余空间
  2. 源码
struct ziplist<T> {
int32 zlbytes; // 整个压缩列表占用字节数
int32 zltail_offset; // 最后一个元素距离压缩列表起始位置的偏移量,用于快速定位到最后一个
节点
int16 zllength; // 元素个数
T[] entries; // 元素内容列表,挨个挨个紧凑存储
int8 zlend; // 标志压缩列表的结束,值恒为 0xFF
}/**
*entry对象源码
*/
struct entry {
int<var> prevlen; // 前一个 entry 的字节长度
int<var> encoding; // 元素类型编码
optional byte[] content; // 元素内容
}

  1. 压缩列表是支持双向遍历,所以才会有zltail_offset这个字段的,可以进行快速定位到最后一个元素。然后倒序查找(O(1))
  2. prevlen 表示的是前一个字段的长度,有人就有疑问了,为什么是前一个entry的长度,为什么不是自己的呢,其实他还有一个作用是在压缩列表倒叙遍历的时候,需要通过这个字段来快速定位到下一个元素的位置,由于他是一个连续的存储空间,已经知道当前元素的位置+这个空间地址就可以确定写一个entry的位置。为什么会这样呢?因为entry的大小是不一样的。如果是一样的话就可以根据下表进行行为(个人理解,有错误还请指出),且prevlen 是一个变长的整数,redis的常规操作,将不同长度使用不同的数据类型。节省内存
  3. encoding的意思是元素的编码类型,有了这个字段就可以决定元素内容的设定,内存大小的分配。防止内存分配浪费的一种方式。具体内容查看下面
1、00xxxxxx 最大长度位 63 的短字符串,后面的 6 个位存储字符串的位数,剩余的字
节就是字符串的内容。
2、01xxxxxx xxxxxxxx 中等长度的字符串,后面 14 个位来表示字符串的长度,剩余的
字节就是字符串的内容。
3、10000000 aaaaaaaa bbbbbbbb cccccccc dddddddd 特大字符串,需要使用额外 4 个字节
来表示长度。第一个字节前缀是 10,剩余 6 位没有使用,统一置为零。后面跟着字符串内
容。不过这样的大字符串是没有机会使用的,压缩列表通常只是用来存储小数据的。
4、11000000 表示 int16,后跟两个字节表示整数。
5、11010000 表示 int32,后跟四个字节表示整数。
6、11100000 表示 int64,后跟八个字节表示整数。
7、11110000 表示 int24,后跟三个字节表示整数。
8、11111110 表示 int8,后跟一个字节表示整数。
9、11111111 表示 ziplist 的结束,也就是 zlend 的值 0xFF。
10、1111xxxx 表示极小整数,xxxx 的范围只能是 (0001~1101), 也就是 1~13,因为
0000、1110、1111 都被占用了。读取到的 value 需要将 xxxx 减 1,也就是整数 0~12 就是
最终的 value。

  1. 之前有讲到hash对像选用压缩列表的两个前提条件,其中之一是键值的大小都小于64,具体为什么小于64和简=键值对小于512就不具体说了,可以结合一下SDS中的扩容方式思考一下,压缩列表没有冗余空间,在进行扩容的时候会出现频繁扩容,再加上占用空间大了后进行copy数据就很浪费性能了。所以当数据量大了后,就选择了另一种数据结构那就是hashtable(字典)

HashTable(字典)

简介

  • redis 的hashtable和java中的hashMap实现方式是类似的,都是通过数组和链表实现的。也就是key-value形式。当然它解决hash冲突的方式也是使用链地址法(解决hash冲突的几种方法可以想一下),当不同的key创建出了相同的hash值时将vlue就放入链表上,如下图。
  • 在细节方面和java中的hashMap差别还是很大的。列如扩容的过程,key值得hash算法等等。接下来我们根据源码细细的品一品。
  • 官方给的解释:字典(dictionary), 又名映射(map)或关联数组(associative array), 是一种抽象数据结构, 由一集键值对(key-value pairs)组成, 各个键值对的键各不相同, 程序可以添加新的键值对到字典中, 或者基于键进行查找、更新或删除等操作

其字典的底层结构是使用的是redis 中dict。不仅是hash对象底层使用了dict,而且在redis全局也是使用的是key-vlue结构,也就是字典的形式,还有Zset的数据结构底层也是基于redis 中的dict结构。我们来看一下其源码:

// resdis 全局使用的字典结构
struct RedisDb {
dict* dict; // all keys key=>value
dict* expires; // all expired keys key=>long(timestamp)
...}
// 有序集合的底层数据结构
struct zset {
dict *dict; // all values value=>score
zskiplist *zsl;
}

2. dict结构深度解析

  • 源码:
/** 字典** 每个字典使用两个哈希表,用于实现渐进式 rehash*/
typedef struct dict {// 特定于类型的处理函数dictType *type;// 类型处理函数的私有数据void *privdata;// 哈希表(2 个)dictht ht[2];// 记录 rehash 进度的标志,值为 -1 表示 rehash 未进行int rehashidx;// 当前正在运作的安全迭代器数量int iterators;} dict;

  • 可以类的成员变量中看到有两个hashtable,通常情况下是一个有值一个没有值。在压缩列表中我们遇到的问题是在扩容方面存在性能问题,这两个hashtable就是来解决扩容问题的。在扩容和缩容时进行渐进式搬迁,当搬迁结束的时候将旧的hashtable进行删除,新的hashtable 取而代之。
  • 那我们来细细的研究一下hashtable,(Java中的hashtable是Java中hashMap的线程安全版本)。在这里的hashtable和java中的hashmap是类似的,解决hash冲突的方式通过分桶的方式。一维数组,二维链表。但是在扩容还是有一些区别的。
struct dictEntry {
void* key;
void* val;
dictEntry* next; // 链接下一个 entry
}
struct dictht {
dictEntry** table; // 二维
long size; // 第一维数组的长度
long used; // hash 表中的元素个数
...
}

  • 来看一下redis中hash是如何进行的
    1.大字典的扩容是非常耗时间的,需要重新申请新的数组,然后将旧的字典所有的链表中的元素重新挂接到新的数组下面,这个过程时间复杂度为O(n),作为单线程的redis怎么会把时间浪费在这里呢,。于是他就采用了渐进式处理的方式(说到渐进式是否能想到他渐进式批量根据key查询呢scan 和 keys), rehash的过程点击这里。其思想也就是我们上面所说的小步执行。
  • 联系一下Set结构也是通过字典实现的,只不是所有的value都是NULL,有没有想到什么?Java中的hashSet是不是也和这个类似呢?。

总结

  1. hash对象有两种底层实现方式,hashtable(字典) 和 ziplist(压缩链表)
  2. 压缩链表由于是连续空间在刚开始数据量小的时候性能是显著的,但是在数据量大的时候就会出现扩容慢的问题
  3. 字典通过双hahstable的方式,再加上渐进式hash的方式解决了压缩列表的扩容的问题
  4. redis 高性能数据结构我们可以看到他在很对细节的把握很多,如不同的数字大小选用不同的字段类型,不同的存储方式采用不同的

将一个键值对添加入一个对象_细品Redis高性能数据结构之hash对象相关推荐

  1. python列表中的字典如何添加键值对_在Python中将键值对添加到字典中

    Python字典是键值对的无序集合.在本教程中,我们将看到如何将新的键值对添加到已经定义的字典中.以下是我们可以使用的两种方法. 将新键分配为下标 我们通过使用新键作为下标并为其分配值来向字典添加新元 ...

  2. 如何将键/值对添加到JavaScript对象?

    这是我的对象文字: var obj = {key1: value1, key2: value2}; 如何向对象添加{key3: value3} ? #1楼 您可以使用其中任何一个(提供的key3是您要 ...

  3. android修改默认遥控器键值,android 中遥控器键值的添加和修改

    前言:TV 上遥控器键值怎么样对应到android的系统中,最近一个客户需要在我们的平台上修改,所以,我顺便做了一下总结,方便以后参考. 请转载的朋友一定加上出处,十分感谢~~ 第一步: init.r ...

  4. 原生 遍历_细品原生JS从初级到高级知识点汇总(三)

    作者:火狼1 转发链接:https://juejin.im/post/5daeefc8e51d4524f007fb15 目录 细品原生JS从初级到高级知识点汇总(一) 细品原生JS从初级到高级知识点汇 ...

  5. css就近原则_细品100道CSS知识点(上)「干货满满」

    作者:hh_phoebe 转发链接:https://juejin.im/post/5ee0cf335188254ec9505381 目录 细品100道CSS知识点(上)[干货满满]本篇 细品100道C ...

  6. css为什么要用浮动_细品100道CSS知识点(上)「干货满满」

    作者:hh_phoebe 转发链接:https://juejin.im/post/5ee0cf335188254ec9505381 目录 细品100道CSS知识点(上)[干货满满]本篇 细品100道C ...

  7. 临键锁如何实现幻读_如何用Redis实现分布式锁?

    作者:敖丙 来源:https://juejin.im/post/5e9473f5e51d454702460323 前言 上一章节我提到了基于zk分布式锁的实现,这章节就来说一下基于Redis的分布式锁 ...

  8. java在类中创建一个对象_在另一个类中创建类对象

    我创建了两个类对象,每个对象都有一个构造函数,我试图让一个类对象成为另一个对象中的私有变量 . 这是我想要做的一个简单的例子,而不是实际的类名,而是一个例子 . 有更多的公共和私人变量,但为了简单起见 ...

  9. thymeleaf的能用在什么地方_细品 Spring Boot+Thymeleaf,还有这么多好玩的细节!

    松哥原创的 Spring Boot 视频教程已经杀青,感兴趣的小伙伴戳这里-->松哥要升级 SpringBoot 视频了,看看新增了哪些内容! 虽然现在流行前后端分离,但是后端模版在一些关键地方 ...

最新文章

  1. springboot自定义配置文件
  2. zimbra邮件系统详细配置教程
  3. Java工作笔记-IntelliJ IDEA中的精确搜索
  4. python基础 实战作业 ---Excel基本读写与数据处理
  5. C++语言的技术性规则
  6. 华为开发的新型智能手机的正面和背面采用玻璃材料制成
  7. Js判断本地是否存在要存数据
  8. sizeof(空类或空结构体)
  9. WPF中查找指定类型的父控件
  10. 使用matlab计算并绘制连续信号的傅里叶变换
  11. Eclipse中文汉化包安装教程
  12. dog log 算子_灰度图像--图像分割 Marr-Hildreth算子(LoG算子)
  13. 西工大计算机课程表,工大、高新、交大、爱知等7所名校初一作息时间表课表新鲜出炉!...
  14. 网络空间安全领域可投的期刊和会议
  15. 2022-08-01 网工进阶(二十四) STP进阶知识
  16. android 个人云存储,个人云存储app-个人云安卓版-地之图下载
  17. GSAP,专业的Web动画库
  18. kali linux 清华源_KALI LINUX 2.0 2019 更新国内源
  19. mysql电脑缺少dll文件下载_mysql.dll文件下载,金山毒霸dll修复工具帮您解决文件丢失导致“mysql.dll找不到”的系统问题...
  20. 统一UOS操作系统下载和安装教程总结

热门文章

  1. ActionForm类及表单数据验证
  2. 【TensorFlow】tf.nn.softmax_cross_entropy_with_logits中的“logits”到底是个什么意思?
  3. [日常] Go语言圣经--Channel习题
  4. [Step By Step]SAP HANA PAL指数回归预测分析Exponential Regression编程实例EXPREGRESSION(模型)...
  5. PPT | 云客堂——云服务助力Java 应用程序开发及部署
  6. AngularJS 的自定义指令
  7. Windows Azure Storage (4) Windows Azure Storage Service存储服务之Blob Share Access Signature
  8. 线性时间排序--桶排
  9. linux 用户/用户组添加修改删除(ubuntu/centos)
  10. 实验三 静态路由、默认路由配置