【Redis】hash 类型

article/2025/6/21 7:10:24

hash

  • 一. hash 类型介绍
  • 二. hash 命令
    • hset、hget
    • hexists、hdel
    • hkeys、hvals、hgetall
    • hmset、hmget
    • hlen、hstrlen、hsetnx
    • hincrby、hincrbyfloat
  • 三. hash 命令小结
  • 四. hash 内部编码方式
  • 五. hash 的应用场景
    • 缓存功能
      • 缓存方式对比

一. hash 类型介绍

  • 哈希表在日常开发中,出场率是非常高的,面试中也是非常重要的考点。
  • Redis 自己已经是 “键值对” 结构了,就是通过哈希表的方式来实现的。
    • 把 key 这一层组织完成之后,到了 value 这一层,value 的其中一种类型还可以是哈希表。

在这里插入图片描述
在这里插入图片描述

二. hash 命令

hset、hget

  • hset:设置 hash 中指定的一个/多个 字段 (field) 以及对应的值 (value)
  • 语法:hset key field value [field value ...]
  • 时间复杂度:插入一组 field和value 为 O(1),插入 N 组 field和value 为 O(N)
  • 返回值:添加的字段的个数。

在这里插入图片描述

  • hget:获取 hash 中指定字段对应的 值 (value)
  • 语法:hget key field
  • 时间复杂度:O(1)
  • 返回值:字段对应的值或者 nil

在这里插入图片描述

hexists、hdel

  • hexists:判断 hash 中是否有指定的 字段 (field)
  • 语法:hexists key field
  • 时间复杂度:O(1)
  • 返回值:1 表示存在,0 表示不存在。

在这里插入图片描述

  • hdel:删除 hash 中指定的 字段 (field)
  • 语法:hdel key field [field ...]
  • 时间复杂度:删除一个元素为 O(1),删除 N 个元素为 O(N)
  • 返回值:本次操作删除的字段个数。

在这里插入图片描述

这里需要注意的是:

  • del 删除的是 key
  • hdel 删除的是 field

在这里插入图片描述

hkeys、hvals、hgetall

  • hkeys:获取 hash 中的所有 字段 (field)
  • 语法:hkeys key
  • 时间复杂度:O(N),N 为 字段 (field) 的个数。
  • 返回值:所有的 (field)

在这里插入图片描述

  • hvals:获取 hash 中的所有的字段对应的 值 (value)
  • 语法:hvals key
  • 时间复杂度:O(N),N 为 字段 (field) 的个数。
  • 返回值:所有的 值(value)

在这里插入图片描述

  • hgetall:获取 hash 中的所有 字段 (field) 以及对应的 值 (value)
  • 语法:hgetall key
  • 时间复杂度:O(N),N 为 字段 (field) 的个数。
  • 返回值:字段 (field) 和对应的值 (value)

在这里插入图片描述

总结:

  • h 系列的命令,必须要保证 key 对应的 value 得是 hash 类型。
  • 这两个操作,先根据 key 查找对应的 hash,为 O(1),然后再遍历 hash,为 O(N),N 表示 hash 的元素个数。
  • 注意:这些操作也是存在一定的风险的,类似之前介绍的 keys *,我们也不知道某个 hash 中是否会存在大量的 field,可能导致 Redis 服务器被阻塞。

hmset、hmget

  • hmset:设置 hash 中指定的一个/多个 字段 (field) 以及对应的值 (value)
  • 语法:hmset key field value [field value ...]
  • 时间复杂度:插入一组 field和value 为 O(1),插入 N 组 field和value 为 O(N)
  • 返回值:成功时返回 OK,失败时返回错误。

在这里插入图片描述

注意的是:hset 已经支持,在 hash 中插入一组 field和value,所以并不需要使用 hmset,多数情况下,不需要查询所有的 字段 (field),可能只需要查询其中几个 字段 (field),而 hget 一次只能查询一个 字段 (field),于是出现了 hmget

  • hmget:一次获取 hash 中多个 字段对应的值 (value)
  • 语法:hmget key field [field ...]
  • 时间复杂度:只查询一个元素为 O(1),查询多个元素为 O(N),N 为查询元素个数。
  • 返回值:字段对应的值或者 nil

在这里插入图片描述

总结:

  • 在使用 hkeys、hvals、hgetall 都是存在一定的风险的,如果 hash 元素的个数比较多时,执行的耗时会比较长,从而阻塞 Redis 服务器。
    • 一条命令完成所有的遍历操作。
  • 如果开发人员只需要获取部分的 字段 (field),可以使用 hmget。
    • 一条命令完成所有的遍历操作。
  • 如果一定要获取全部 字段 (field),可以尝试使用 hscan 命令,该命令采用 “渐进式遍历” Redis 的 hash 类型。
    • 敲一次命令,遍历一小部分。
    • 再敲一次命令,再遍历一小部分。
    • 连续执行多次,就可以完成整个遍历的过程。
    • 化整为零,做到时间可控,不会阻塞 Redis 服务器。

C++ 标准库的设计精妙,实现也是很优雅的,但是 C++ 标准库,横向和其他编程语言比,就是个 弟中弟。

  • C++ 标准库给咱们提供的功能太少,提供的各种 “容器”,都是线程不安全的,
  • Java 标准库则直接提供了一些线程安全的 “集合类” (Java 中也有 “容器” 这样的术语,但是指的是其他东西)
    • 例如:线程安全的哈希表 (ConcurrentHashMap),这个哈希表在扩容的时候,也是按照 “化整为零” 的方式进行的 (数据拷贝的过程化整为零)

hlen、hstrlen、hsetnx

  • hlen:获取 hash 中的所有 字段(field) 的个数。
  • 语法:hlen key
  • 时间复杂度:O(1),不需要遍历
  • 返回值:字段(field) 的个数。

在这里插入图片描述

  • hstrlen:获取 hash 中字段对应的 值(value) 的长度,单位是字节。
  • 语法:hstrlen key field
  • 时间复杂度:O(1)
  • 返回值:值(value) 的长度。

在这里插入图片描述

  • hsetnx:在字段不存在的情况下,设置 hash 中的一个 字段 (field) 以及对应的值 (value)
  • 语法:hsetnx key field value
  • 时间复杂度:O(1)
  • 返回值:1 表示设置成功,0 表示失败。

在这里插入图片描述

hincrby、hincrbyfloat

hash 这里的 value,也可以当做数字来处理,hincrby 就可以加减证书,hincrbyfloat 就可以加减小数,但是使用的频率不是很高,Redis 没有提供类似于 incr 和 decr 的命令。

  • hincrby:将 hash 中字段对应的 值 (value) 添加指定的整数值。
  • 语法:hincrby key field increment
  • 时间复杂度:O(1)
  • 返回值:该字段变化之后的 值(value)

在这里插入图片描述

  • hincrbyfloat:将 hash 中字段对应的 值 (value) 添加指定的小数值。
  • 语法:hincrbyfloat key field increment
  • 时间复杂度:O(1)
  • 返回值:该字段变化之后的 值(value)

在这里插入图片描述

三. hash 命令小结

命令执行效果时间复杂度
hset key field value [field value …]设置 字段(field) 以及对应的 值(value)O(k),k 示 字段(field) 的个数
hsetnx key field value不存在字段(field)时,设置 字段(field) 以及对应的 值(value),否则失败O(1)
hget key field获取字段对应的 值(value)O(1)
hmset key field value [field value …]批量设置 字段(field) 以及对应的 值(value)O(k),k 示 字段(field) 的个数
hmget key field [field …]批量获取字段对应的 值(value)O(k),k 示 字段(field) 的个数
hdel key field [field …]删除指定的 字段(field)O(k),k 示 字段(field) 的个数
hexists key field判断 字段(field) 是否存在O(1)
hkeys key获取所有的 字段(field)O(k),k 示 字段(field) 的个数
hvals key获取所有的字段对应的 值(value)O(k),k 示 字段(field) 的个数
hgetall key获取所有 字段(field) 以及对应的 值(value)O(k),k 示 字段(field) 的个数
hincrby key field n对应字段的 值(value) 加整数 nO(1)
hincrbyfloat key field n对应字段的 值(value) 加小数 nO(1)
hlen key获取 字段(field) 的个数O(1)
hstrlen key field获取字段对应的 值(value) 的长度O(1)
  • 哈希类型命令的效果、时间复杂度,开发⼈员可以参考此表,结合自身业务需求和数据大小选择合适的命令。

四. hash 内部编码方式

hash 内部有 2 中编码方式:

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

总结:

  • 如果 hash 中,元素的个数较少,使用 ziplist 表示,如果元素的个数比较多,使用 hashtable 表示。
  • 如果 hash 中,每个字段对应的 值(value) 的长度都比较短,使用 ziplist 表示,如果某个字段对应的 值(value) 的长度太长,也会转化成 hashtable 表示。

一些具体的压缩算法:rar、zip、gzip、7z。

  • 压缩的本质是,针对数据进行重新编码,不同的数据,有不同放入特点,结合这些特点,进行精妙的设计,重新编码之后,就能够缩小体积。
    • 例如:字符串 “abbcccddddeeee”,重新编码后就表示为,1a2b3c4d5e,这是比较粗糙的编码方式,事实上上述一些常见的压缩算法都是精妙设计的。
  • ziplist 也是同理,内部的数据结构也是精心设计的,目的就是节省内存空间。
    • 表示一个普通的 hash 表,可能会浪费一定的内存空间 (hash 首先是一个数组,数组上的有些位置有元素,有些位置没有元素)
  • ziplist 付出的代价就是,进行读写元素,速度是比较慢的,如果元素个数少,慢的不明显,如果元素个数太多了,就很慢。

可以通过 object encoding key 来查看具体的编码方式。

在这里插入图片描述

五. hash 的应用场景

缓存功能

string 也是可以作为缓存使用的,但是存储结构化数据 (类似:数据库中表这样的结构),使用 hash 类型更合适一些:

在这里插入图片描述

  • 相比于使用 JSON 格式的 string 缓存用户信息,哈希类型变得更加直观,并且在更新操作上变得更灵活。可以将每个用户的 id 定义为键后缀,多对 field-value 对应用户的各个属性,类似如下伪代码:
UserInfo getUserInfo(long uid) {// 根据 uid 得到 Redis 的键String key = "user:" + uid;// 尝试从 Redis 中获取对应的值userInfoMap = Redis 执行命令: hgetall key;// 如果缓存命中(hit)if (value != null) {// 将映射关系还原为对象形式UserInfo userInfo = 利用映射关系构建对象(userInfoMap);return userInfo;}// 如果缓存未命中(miss)// 从数据库中,根据 uid 获取用户信息UserInfo userInfo = MySQL 执行 SQL: select * from user_info where uid = <uid>// 如果表中没有 uid 对应的用户信息if (userInfo == null) {响应 404return null;}// 将缓存以哈希类型保存Redis 执行命令: hmset key name userInfo.name age userInfo.age city userInfo.city// 写入缓存,为了防⽌数据腐烂(rot),设置过期时间为1⼩时(3600秒)Redis 执行命令: expire key 3600// 返回用户信息return userInfo;
}
  • 如果使用 string (JSON 的格式) 的方式,来表示 USerInfo,万一只想获取某个 字段(field),或者修改某个 字段(field),就需要把整个 JSON 都读出来。解析成对象,操作 字段(field),再重新写成 JSON 字符串,写回过去。
  • 如果使用 hash 的方式,来表示 USerInfo,就可以使用 字段(field) 表示对象的每个属性 (数据表的每个列),此时就可以非常方便的获取/修改任何一个属性值了。
    • 使用 hash 的方式,确实 读写字段(field) 更直观高效,但是付出的是空间的代价,需要控制 hash 再 ziplist 和 hashtable 两种内部编码的转换,可能会造成内存较大的消耗。

注意:Redis 哈希类型和 MySQL 关系型数据库有两点不同之处:

  • 哈希类型是稀疏的,而关系型数据库是完全结构化的,例如哈希类型每个键可以有不同的 field,而关系型数据库一旦添加新的列,所有行都要为其设置值,即使为 null,如图下图所示。
  • 关系数据库可以做复杂的关系查询,而 Redis 去模拟关系型复杂查询,例如联表查询、聚合查询等基本不可能,维护成本高。

在这里插入图片描述

上述 hash 中,key 中存了 uid,而 field-value 又存了一份 uid,不存 field-value 中的 uid,是否就直接使用 key 中 id 来进行区分,是不是存储空间有一部的节省了呢?

  • 如果确实不想存这个 uid 也可以。
  • 但是,在工程实践中,一般都会把 uid 在 value 中再存一份,后续写到相关的代码,使用起来会比较方便。

缓存方式对比

  • 原生字符串类型:使用字符串类型,每个属性一个键。
    • 优点:实现简单,针对个别属性变更也很灵活。
    • 缺点:占用过多的键,内存占用量较大,同时用户信息在 Redis 中比较分散,缺少内聚性,所以这种方案基本没有实用性。
set user:1:name James
set user:1:age 23
set user:1:city Beijing
  • 序列化字符串类型,例如 JSON 格式。
    • 优点:针对总是以整体作为操作的信息比较合适,编程也简单。同时,如果序列化方案选择合适,内存的使用效率很高。
    • 缺点:本身序列化和反序列需要一定开销,同时如果总是操作个别属性则非常不灵活。
set user:1 经过序列化后的用户对象字符串
  • 哈希类型。
    • 优点:简单、直观、灵活,尤其是针对信息的局部变更或者获取操作。
    • 缺点:需要控制哈希在 ziplist 和 hashtable 两种内部编码的转换,hashtable 会造成内存的较大消耗。
hmset user:1 name James age 23 city Beijing

解释:高内聚,低耦合

  • 高内聚:模块内部的紧密性和单一功能。
    • 把有关联的东西放在一起,最好能放在指定的地方。例如:有些人喜欢把脱下来的衣服随意乱扔,床上、沙发上、椅子上,唯独不会出现在衣柜里,这种就是 “低内聚”。
  • 低耦合:模块之间关联关系的程度较低。
    • 关联关系越大,越容易相互影响,就认为 “高耦合”,有个成语叫 “藕断丝连”。
    • 追求 “低耦合”,是为了避免 “牵一发而动全身”,这边改出 bug,一想到了其他地方。
    • 使用 string 和 hash 方式,通过 userId,就可以获取用户所有的数据,而使用原生字符串类型,用户不同属性的信息,不是存放在一起的,而是散开的,需要将其全部找出来。

http://www.hkcw.cn/article/yIDSJsbbHW.shtml

相关文章

ubuntu/windows系统下如何让.desktop/.exe文件 在开机的时候自动运行

目录 1&#xff0c;​​让 .desktop 文件在 Ubuntu 开机时自动启动​ 1.1 创建 autostart 目录&#xff08;如果不存在&#xff09;​ ​ 1.2 将 .desktop 文件复制到 autostart 目录​ ​ 1.3 确保 .desktop 文件有可执行权限​ 2,windows 2.1 打开「启动」文件夹​…

1-Wire 一线式总线:从原理到实战,玩转 DS18B20 温度采集

引言 在嵌入式系统中&#xff0c;通信总线是连接 CPU 与外设的桥梁。从 I2C、SPI 到 UART&#xff0c;每种总线都有其独特的应用场景。而本文要介绍的1-Wire 一线式总线&#xff0c;以其极简的硬件设计和独特的通信协议&#xff0c;在温度采集、身份识别等领域大放异彩。本文将…

有机黑鸡蛋与普通鸡蛋:差异剖析与选购指南

在我们的日常饮食结构里&#xff0c;鸡蛋始终占据着不可或缺的位置&#xff0c;是人们获取营养的重要来源。如今&#xff0c;市场上鸡蛋种类丰富&#xff0c;除了常见的普通鸡蛋&#xff0c;有机黑鸡蛋也逐渐崭露头角&#xff0c;其价格通常略高于普通鸡蛋。这两者究竟存在哪些…

Fastapi 学习使用

Fastapi 学习使用 Fastapi 可以用来快速搭建 Web 应用来进行接口的搭建。 参考文章&#xff1a;https://blog.csdn.net/liudadaxuexi/article/details/141062582 参考文章&#xff1a;https://blog.csdn.net/jcgeneral/article/details/146505880 参考文章&#xff1a;http…

数字化转型进阶:精读41页华为数字化转型实践【附全文阅读】

该文档聚焦华为数字化转型实践&#xff0c;核心内容如下&#xff1a; 转型本质与目标&#xff1a;数字化转型是通过数字技术穿透业务&#xff0c;实现物理世界与数字世界的融合&#xff0c;目标是支撑主业成功、提升体验与效率、探索模式创新。华为以 “平台 服务” 为核心&am…

共享内存-systemV

01. 共享内存简述 共享内存是一个允许多个进程直接访问同一块物理内存区域的进程通信工具&#xff0c;因其本身不涉及用户态与核心态之间转换&#xff0c;故效率最佳。为了使用一个共享内存段&#xff0c;一般需要以下几个步骤&#xff1a; 调用shmget()创建一个新共享内存段…

大语言模型值ollama使用(1)

ollama为本地调用大语言模型提供了便捷的方式。下面列举如何在windows系统中快捷调用ollama。 winR打开运行框&#xff0c;输入cmd 1、输入ollama list 显示已下载模型 2、输入ollama pull llama3 下载llama3模型 3、 输入 ollama run llama3 运行模型 4、其他 ollama li…

【基础算法】高精度(加、减、乘、除)

文章目录 什么是高精度1. 高精度加法解题思路代码实现 2. 高精度减法解题思路代码实现 3. 高精度乘法解题思路代码实现 4. 高精度除法 (高精度 / 低精度)解题思路代码实现 什么是高精度 我们平时使用加减乘除的时候都是直接使用 - * / 这些符号&#xff0c;前提是进行运算的数…

uni-data-picker级联选择器、fastadmin后端api

记录一个部门及部门人员选择的功能&#xff0c;效果如下&#xff1a; 组件用到了uni-ui的级联选择uni-data-picker 开发文档&#xff1a;uni-app官网 组件要求的数据格式如下&#xff1a; 后端使用的是fastadmin&#xff0c;需要用到fastadmin自带的tree类生成部门树 &#x…

MonitorSDK_性能监控(从Web Vital性能指标、PerformanceObserver API和具体代码实现)

性能监控 性能指标 在实现性能监控前&#xff0c;先了解Web Vitals涉及的常见的性能指标 Web Vitals 是由 Google 推出的网页用户体验衡量指标体系&#xff0c;旨在帮助开发者量化和优化网页在实际用户终端上的性能体验。Web Vitals 强调“以用户为中心”的度量&#xff0c;而…

Kubernetes架构与核心概念深度解析:Pod、Service与RBAC的奥秘

&#x1f525;「炎码工坊」技术弹药已装填&#xff01; 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 引言&#xff1a;云原生时代的操作系统 在云原生技术浪潮中&#xff0c;Kubernetes&#xff08;简称K8s&#xff09;已成为容器编排领域的"分布式操…

enumiax:IAX 协议用户名枚举器!全参数详细教程!Kali Linux教程!

简介 enumIAX 是一个 Inter Asterisk Exchange 协议用户名暴力枚举器。enumIAX 可以以两种不同的模式运行&#xff1b;顺序用户名猜测或字典攻击。 enumIAX 可以以两种不同的模式运行&#xff1a;顺序用户名猜测或字典攻击。 顺序用户名猜测 在顺序用户名猜测模式下&#xf…

《深入解析SPI协议及其FPGA高效实现》-- 第一篇:SPI协议基础与工作机制

第一篇&#xff1a;SPI协议基础与工作机制 1. 串行外设接口导论 1.1 SPI的核心定位 协议本质 &#xff1a; 全双工同步串行协议&#xff08;对比UART异步、IC半双工&#xff09;核心优势 &#xff1a; 无寻址开销&#xff08;通过片选直连&#xff09;时钟速率可达100MHz&…

C++语法系列之模板进阶

前言 本次会介绍一下非类型模板参数、模板的特化(特例化)和模板的可变参数&#xff0c;不是最开始学的模板 一、非类型模板参数 字面意思,比如&#xff1a; template<size_t N 10> 或者 template<class T,size_t N 10>比如&#xff1a;静态栈就可以用到&#…

STL-list

1.list概述 List 并非 vector 与 string 那样连续的内存空间&#xff0c;list 每次插入或删除一个元素&#xff0c;都会新配置或释放一个元素的空间&#xff0c;所以list对于空间的使用很充分&#xff0c;一点也没有浪费&#xff0c;对于任意位置的插入或删除元素&#xff0c;时…

导入Maven项目

目录 5. 5.1 导入方法1 5.2 导入方法2 5.1 导入方法1 建议选择pom.xml文件导入 导入成功 5.2 导入方法2 导入成功

【含文档+PPT+源码】基于微信小程序的社区便民防诈宣传系统设计与实现

项目介绍 本课程演示的是一款基于微信小程序的社区便民防诈宣传系统设计与实现&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的 Java 学习者。 1.包含&#xff1a;项目源码、项目文档、数据库脚本、软件工具等所有资料 2.带你从零开始部署运行本套…

【Unity笔记】Unity WASD+QE 控制角色移动与转向(含 Shift 加速)实现教程

摘要&#xff1a; 在 Unity 游戏开发中&#xff0c;键盘控制角色的移动与转向是基础功能之一。本文详细讲解如何使用 C# 实现基于 WASD 移动、QE 转向 与 Shift 加速奔跑 的角色控制器&#xff0c;适用于第一人称、第三人称、自由漫游等场景。通过直观的 Transform 控制方法与可…

通讯方式学习——单总线协议(2024.04.09)

参考链接1: 单总线器件DS18B20测温程序该怎么编写&#xff1f;这个视进行了详细讲解&#xff01; 在此感谢各位前辈大佬的总结&#xff0c;写这个只是为了记录学习大佬资料的过程&#xff0c;内容基本都是搬运的大佬博客&#xff0c;觉着有用自己搞过来自己记一下&#xff0c;如…

大语言模型(LLM)入门 - (1) 相关概念

文章来自&#xff1a;大语言模型(LLM)小白入门自学项目-TiaoYu-1 GitHub - tiaoyu1122/TiaoYu-1: For People! For Freedom!For People! For Freedom! Contribute to tiaoyu1122/TiaoYu-1 development by creating an account on GitHub.https://github.com/tiaoyu1122/TiaoYu…