《闲扯Redis六》Redis五种数据类型之Hash型
一、前言
Redis 提供了5种数据类型:String(字符串)、Hash(哈希)、List(列表)、Set(集合)、Zset(有序集合),理解每种数据类型的特点对于redis的开发和运维非常重要。
Redis 中的 hash 是我们经常使用到的一种数据类型,根据使用方式的不同,可以应用到很多场景中。
二、实现分析
由上述结构图可知,Hash类型有以下两种实现方式:
1、ziplist 编码的哈希对象使用压缩列表作为底层实现 2、hashtable 编码的哈希对象使用字典作为底层实现
1.ziplist 编码作为底层实现
ziplist 编码的哈希对象使用压缩列表作为底层实现, 每当有新的键值对要加入到哈希对象时, 程序会先将保存了键的压缩列表节点推入到压缩列表表尾, 然后再将保存了值的压缩列表节点推入到压缩列表表尾, 因此:
保存了同一键值对的两个节点总是紧挨在一起, 保存键的节点在前, 保存值的节点在后; 先添加到哈希对象中的键值对会被放在压缩列表的表头方向,而后来添加到哈希对象中的键值对会被放在压缩列表的表尾方向。
例如, 我们执行以下 HSET 命令, 那么服务器将创建一个列表对象作为 profile 键的值:
redis> HSET profile name "Tom"
(integer) 1
redis> HSET profile age 25
(integer) 1
redis> HSET profile career "Programmer"
(integer) 1
profile 键的值对象使用的是 ziplist 编码, 其中对象所使用的压缩列表结构如下图所示。
2.hashtable 编码作为底层实现
hashtable 编码的哈希对象使用字典作为底层实现, 哈希对象中的每个键值对都使用一个字典键值对来保存:
字典的每个键都是一个字符串对象, 对象中保存了键值对的键;
字典的每个值都是一个字符串对象, 对象中保存了键值对的值。
例如, 如果前面 profile 键创建的不是 ziplist 编码的哈希对象, 而是 hashtable 编码的哈希对象, 那么这个哈希对象结构如下图所示。
三、命令实现
因为哈希键的值为哈希对象, 所以用于哈希键的所有命令都是针对哈希对象来构建的, 下表列出了其中一部分哈希键命令, 以及这些命令在不同编码的哈希对象下的实现方法。
命令 |
ziplist 编码实现方法 |
hashtable 编码的实现方法 |
---|---|---|
HSET |
首先调用 ziplistPush 函数, 将键推入到压缩列表的表尾, 然后再次调用 ziplistPush 函数, 将值推入到压缩列表的表尾。 |
调用 dictAdd 函数, 将新节点添加到字典里面。 |
HGET |
首先调用 ziplistFind 函数, 在压缩列表中查找指定键所对应的节点, 然后调用 ziplistNext 函数, 将指针移动到键节点旁边的值节点, 最后返回值节点。 |
调用 dictFind 函数, 在字典中查找给定键, 然后调用dictGetVal 函数, 返回该键所对应的值。 |
HEXISTS |
调用 ziplistFind 函数, 在压缩列表中查找指定键所对应的节点, 如果找到的话说明键值对存在, 没找到的话就说明键值对不存在。 |
调用 dictFind 函数, 在字典中查找给定键, 如果找到的话说明键值对存在, 没找到的话就说明键值对不存在。 |
HDEL |
调用 ziplistFind 函数, 在压缩列表中查找指定键所对应的节点, 然后将相应的键节点、 以及键节点旁边的值节点都删除掉。 |
调用 dictDelete 函数, 将指定键所对应的键值对从字典中删除掉。 |
HLEN |
调用 ziplistLen 函数, 取得压缩列表包含节点的总数量, 将这个数量除以 2 , 得出的结果就是压缩列表保存的键值对的数量。 |
调用 dictSize 函数, 返回字典包含的键值对数量, 这个数量就是哈希对象包含的键值对数量。 |
HGETALL |
遍历整个压缩列表, 用 ziplistGet 函数返回所有键和值(都是节点)。 |
遍历整个字典, 用 dictGetKey 函数返回字典的键, 用dictGetVal 函数返回字典的值。 |
四、编码转换
当哈希对象可以同时满足以下两个条件时, 哈希对象使用 ziplist 编码:
哈希对象保存的所有键值对的键和值的字符串长度都小于 64 字节;
哈希对象保存的键值对数量小于 512 个;
不能满足这两个条件的哈希对象需要使用 hashtable 编码。
注意:这两个条件的上限值是可以修改的, 具体请看配置文件中关于 hash-max-ziplist-value 选项和 hash-max-ziplist-entries 选项的说明。
对于使用 ziplist 编码的列表对象来说, 当使用 ziplist 编码所需的两个条件的任意一个不能被满足时, 对象的编码转换操作就会被执行: 原本保存在压缩列表里的所有键值对都会被转移并保存到字典里面, 对象的编码也会从 ziplist 变为 hashtable 。
以下代码展示了哈希对象编码转换的情况:
1.键的长度太大引起编码转换
# 哈希对象只包含一个键和值都不超过 64 个字节的键值对
redis> HSET book name "Mastering C++ in 21 days"
(integer) 1
redis> OBJECT ENCODING book
"ziplist"
# 向哈希对象添加一个新的键值对,键的长度为 66 字节
redis> HSET book long_long_long_long_long_long_long_long_long_long_long_description "content"
(integer) 1
# 编码已改变
redis> OBJECT ENCODING book
"hashtable"
2.值的长度太大引起编码转换
# 哈希对象只包含一个键和值都不超过 64 个字节的键值对
redis> HSET blah greeting "hello world"
(integer) 1
redis> OBJECT ENCODING blah
"ziplist"
# 向哈希对象添加一个新的键值对,值的长度为 68 字节
redis> HSET blah story "many string ... many string ... many string ... many string ... many"
(integer) 1
# 编码已改变
redis> OBJECT ENCODING blah
"hashtable"
3.键值对数量过多引起编码转换
# 创建一个包含 512 个键值对的哈希对象
redis> EVAL "for i=1, 512 do redis.call('HSET', KEYS[1], i, i) end" 1 "numbers"
(nil)
redis> HLEN numbers
(integer) 512
redis> OBJECT ENCODING numbers
"ziplist"
# 再向哈希对象添加一个新的键值对,使得键值对的数量变成 513 个
redis> HMSET numbers "key" "value"
OK
redis> HLEN numbers
(integer) 513
# 编码改变
redis> OBJECT ENCODING numbers
"hashtable"
五、要点总结
1.Hash类型两种编码方式,ziplist 与 hashtable
2.hashtable 编码的哈希对象使用字典作为底层实现
3.ziplist 与 hashtable 编码方式之间存在转换
- 通过pl/sql来格式化sql(r4笔记第63天)
- 程序员如何写出杀手级的简历
- 过滤器第二篇【编码、敏感词、压缩、转义过滤器】
- JSP第二篇【内置对象的介绍、4种属性范围、应用场景】
- Struts2的配置和一个简单的例子
- 监听器第一篇【基本概念、Servlet各个监听器】
- 监听器第二篇【统计网站人数、自定义session扫描器、踢人小案例】
- 通俗易懂的分析如何用Python实现一只小爬虫,爬取拉勾网的职位信息
- JSP第一篇【JSP介绍、工作原理、生命周期、语法、指令、行为】
- 一条执行时间两天半的sql语句简化(r4笔记第62天)
- 05-01总结方法,数组(一维)
- 02 Java类的加载机制
- 权限管理系统
- 03 JVM的垃圾回收机制
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 口算训练 HDU - 6287
- Codeforces Round #674 (Div. 3) A ~ F 详细讲解
- C# 生成chart图表的三种方式
- C# Task.Run调用外部参数
- Git全方位指南,即用即查就可
- Python入门教程笔记(二)控制语句
- Python入门教程笔记(一)简介、变量及基本数据类型
- C#知识点讲解之C#delegate、event、Action、EventHandler的使用和区别
- typescript高级用法之infer的理解与使用
- 基于业务场景下的图片/文件上传方案总结
- LeetCode96|二叉搜索树中的搜索
- LeetCode95|字符串中的第一个唯一字符
- LeetCode94|Pow(x,n)
- LeetCode93|数值的整数次方
- LeetCode92|排序数组