首页 > 试题广场 >

说一说hash类型的底层数据结构

[问答题]

说一说hash类型的底层数据结构

推荐

得分点

ziplist、hashtable

参考答案

标准回答

Redis的哈希对象的底层存储可以使用ziplist(压缩列表)和hashtable(字典)。当哈希类型元素个数小于hash-max-ziplist-entries 配置(默认512个),同时所有值都小于hash-max-ziplist-value配置(默认64 字节)时,Redis会使用ziplist作为哈希的内部实现。ziplist使用更加紧凑的结构实现多个元素的连续存储,所以在节省内存方面比hashtable更加优秀。当哈希类型无法满足ziplist的条件时,Redis会使用hashtable作为哈希的内部实现,因为此时ziplist的读写效率会下降,而 hashtable的读写时间复杂度为O(1)。

加分回答

压缩列表是Redis为了节约内存而开发的,它是由一系列特殊编码的连续内存块组成的顺序型数据结构。一个压缩列表可以包含多个节点,每个节点可以保存一个字节数组或者一个整数值。一个压缩列表的重要保存部分包括—zlbytes、zltail、zllen、entryX、zlend,其类型长度以及用途如下表所示:

属性 类型 长度 说明
zlbytes uint32_t 4字节 压缩列表占用的内存字节数;
zltail uint32_t 4字节 压缩列表表尾节点距离列表起始地址的偏移量(单位字节);
zllen uint16_t 2字节 压缩列表包含的节点数量,等于UINT16_MAX时,需遍历列表计算真实数量;
entryX 列表节点 不固定 压缩列表包含的节点,节点的长度由节点所保存的内容决定;
zlend uint8_t 1字节 压缩列表的结尾标识,是一个固定值0xFF;

字典(dict)又称为散列表,是一种用来存储键值对的数据结构。C语言没有内置这种数据结构,所以Redis构建了自己的字典实现。Redis字典的实现主要涉及三个结构体:字典、哈希表、哈希表节点。其中,每个哈希表节点保存一个键值对,每个哈希表由多个哈希表节点构成,而字典则是对哈希表的进一步封装。

延伸阅读

压缩列表结构示意图 :


图片说明

压缩列表节点示意图:


图片说明
字典:三个结构体:字典、哈希表、哈希表节点之间的关系图:


图片说明
其中,dict代表字典,dictht代表哈希表,dictEntry代表哈希表节点。可以看出,dictEntry是一个数组,这很好理解,因为一个哈希表里要包含多个哈希表节点。而dict里包含2个dictht,多出的哈希表用于REHASH。当哈希表保存的键值对数量过多或过少时,需要对哈希表的大小进行扩展或收缩操作,在Redis中,扩展和收缩哈希表是通过REHASH实现的,执行REHASH的大致步骤如下:

  1. 为字典的ht[1]哈希表分配内存空间

    如果执行的是扩展操作,则ht[1]的大小为第1个大于等于ht[0].used*2的2n。如果执行的是收缩操作,则ht[1]的大小为第1个大于等于ht[0].used的2n。

  2. 将存储在ht[0]中的数据迁移到ht[1]上

    重新计算键的哈希值和索引值,然后将键值对放置到ht[1]哈希表的指定位置上。

  3. 将字典的ht[1]哈希表晋升为默认哈希表

    迁移完成后,清空ht[0],再交换ht[0]和ht[1]的值,为下一次REHASH做准备。

当满足以下任何一个条件时,程序会自动开始对哈希表执行扩展操作:

  1. 服务器目前没有执行bgsave或bgrewriteof命令,并且哈希表的负载因子大于等于1;

  2. 服务器目前正在执行bgsave或bgrewriteof命令,并且哈希表的负载因子大于等于5。

为了避免REHASH对服务器性能造成影响,REHASH操作不是一次性地完成的,而是分多次、渐进式地完成的。渐进式REHASH的详细过程如下:

  1. 为ht[1]分配空间,让字典同时持有ht[0]和ht[1]两个哈希表;

  2. 在字典中的索引计数器rehashidx设置为0,表示REHASH操作正式开始;

  3. 在REHASH期间,每次对字典执行添加、删除、修改、查找操作时,程序除了执行指定的操作外,还会顺带将ht[0]中位于rehashidx上的所有键值对迁移到ht[1]中,再将rehashidx的值加1;

  4. 随着字典不断被访问,最终在某个时刻,ht[0]上的所有键值对都被迁移到ht[1]上,此时程序将rehashidx属性值设置为-1,标识REHASH操作完成。

REHSH期间,字典同时持有两个哈希表,此时的访问将按照如下原则处理:

  1. 新添加的键值对,一律被保存到ht[1]中;

  2. 删除、修改、查找等其他操作,会在两个哈希表上进行,即程序先尝试去ht[0]中访问要操作的数据,若不存在则到ht[1]中访问,再对访问到的数据做相应的处理。

编辑于 2021-09-15 10:54:31 回复(0)
链接:https://www.nowcoder.com/questionTerminal/bbb9f8fe17134fd7811e73e588d4b8f7
来源:牛客网

Redis的哈希对象的底层存储可以使用ziplist(压缩列表)和hashtable(字典)。当哈希类型元素个数小于hash-max-ziplist-entries 配置(默认512个),同时所有值都小于hash-max-ziplist-value配置(默认64 字节)时,Redis会使用ziplist作为哈希的内部实现。ziplist使用更加紧凑的结构实现多个元素的连续存储,所以在节省内存方面比hashtable更加优秀。当哈希类型无法满足ziplist的条件时,Redis会使用hashtable作为哈希的内部实现,因为此时ziplist的读写效率会下降,而 hashtable的读写时间复杂度为O(1)。

加分回答

压缩列表是Redis为了节约内存而开发的,它是由一系列特殊编码的连续内存块组成的顺序型数据结构。一个压缩列表可以包含多个节点,每个节点可以保存一个字节数组或者一个整数值。一个压缩列表的重要保存部分包括—zlbytes、zltail、zllen、entryX、zlend,其类型长度以及用途如下表所示:

属性 类型 长度 说明
zlbytes uint32_t 4字节 压缩列表占用的内存字节数;
zltail uint32_t 4字节 压缩列表表尾节点距离列表起始地址的偏移量(单位字节);
zllen uint16_t 2字节 压缩列表包含的节点数量,等于UINT16_MAX时,需遍历列表计算真实数量;
entryX 列表节点 不固定 压缩列表包含的节点,节点的长度由节点所保存的内容决定;
zlend uint8_t 1字节 压缩列表的结尾标识,是一个固定值0xFF;

字典(dict)又称为散列表,是一种用来存储键值对的数据结构。C语言没有内置这种数据结构,所以Redis构建了自己的字典实现。Redis字典的实现主要涉及三个结构体:字典、哈希表、哈希表节点。其中,每个哈希表节点保存一个键值对,每个哈希表由多个哈希表节点构成,而字典则是对哈希表的进一步封装。

发表于 2021-11-17 11:27:48 回复(0)
redis的hash类型的底层存储可以采用ziplist和hashtable。当哈希类型元素个数小于hash-max-ziplist-entries 配置(默认512个),同时所有值都小于hash-max-ziplist-value配置(默认64 字节)时,redis默认采用ziplist存储。ziplist使用更加紧凑的结构实现多元素的连续存储,在节省内存方面比hashtable更加优秀。一个ziplist重要保存部分包括zlbytes,zltail,zllen,entryx,zlend.ziplist遍历时,先根据zlbytes和zltail_offset定位到最后一个entry的位置,再根据最后一个entry的prelen确定前一个entry的位置。而hashtable(字典)主要涉及三个结构体:字典、哈希表、哈希表节点。每个哈希表节点保留一个键值对,一个哈希表由多个哈希表节点构成,字典是对哈希表的进一步封装。本质都是数组+链表/红黑树。
发表于 2022-02-16 11:33:59 回复(0)
JDK1.8以前是数组 + 链表, JDK1.8及以后采用数组 +  链表 + 红黑树
发表于 2021-11-25 08:45:29 回复(2)