redis的数据类型:Hash

Hash类型介绍

我要存一个uid为1的用户对象,姓名为James,年龄是28,如果我想用Redis中的string类型来存,应该怎么存?如果我想用Redis中的Hash类型来存,又应该怎么存?

在这里插入图片描述

哈希类型中的映射关系通常称为field-value,为什么这里不用key-value?

主要是用于区分Redis整体的键值对(key-value),注意这里的value是指field对应的的值,不是键(key)对应的的值,请注意value在不同上下文的作用。

请问一个key如果对应的value类型是hash<field,value>,那么key field value三者之间的对应关系是怎样的?是一对一对一吗?

一个Key可以对应多个field,一个field对应一个value,key field value三者对应关系是1:n:n

Hash类型的命令

HSET

如何向一个hashtable中插入一个键值对?
使用HSET key field1 value1 field2 value2 ...
注意上面指令中,Hash键值对中的value类型就只能是string了
HSET指令的返回值为设置成功的键值对个数
HSETNX key field value 同样也可以设置键值对,但是有一定的条件,即NX:只有当key对应的Hashtable中field不存在时,才能创建成功

HGET

如何获得一个Hash表中field1对应的value?

使用指令hget key field1
结果返回指定key 和field对应的value

如果hget key field指令中,key或者field不存在,结果会怎样?

结果返回nil

hexists

如何判断指定key和field是否存在?(如何判断这个key+field的组合有没有一个value与之对应?)
通过执行指令hexists key field
结果返回值:一般查找成功返回1,不成功返回0

Hdel

如何删除hashtable中的一个<field,value>键值对?

hdel key field1 filed2...
key指定要删除的是哪一个hashtable中的键值对
field指定的是hashtable中某个具体的键值对

Hdel指令执行完毕之后,结果返回成功删除的键值对个数

如果我语句中指定的key和field中有无效的参数,返回的结果是什么?

返回0,表示成功删除的键值对个数是0

如何删除一个hashtable?

直接del key
其中key就是这个hashtable对应的key

HKEYS

如何查看hashtable中有哪些field?
HKEYS key(其中key就是这个hashtable对应的key)
这个操作,先根据key找到对应的hash,时间复杂度为O(1)
然后再遍历hash,打印出hash中的每个field,时间复杂度是O(N)

谈到O(N) ,这个N的含义还挺复杂的

  • 有的时候,N表示redis中所有key的个数
  • 有的时候,N表示redis指令中key的个数,比如HSET key field1 value1 field2 value2中,key的个数就是两个
  • 有的时候,N表示当前key对应的value里面的元素个数.(比如value的类型是hash,N就表示hash中的键值对个数)

注意!! 这个操作也是存在一定的风险的!! 类似于之前介绍过的 keys *(keys *是要打印出redis中的所有key),而HKEYS key是要打印出key对应hashtable中的所有field,如果 这个hash 中存在大量的 field,那也是很要命的,单线程Redis就被阻塞了

hvals

如何查看一个hashtable中的所有value?
hvals key(其中key就是这个hashtable对应的key)
和 hkeys 相对,hvals key能够获取到 hash 中的所有 value
H 系列的命令 必须要保证 key 对应的 value 得是哈希类型的!!
这个操作的时间复杂度,也是O(N),N是哈希的元素个数.如果哈希非常大,这个操作就可能导致redis服务器被阻塞住.

hgetall

如何查看hashtable中的所有键值对?
hgetall key
其中key就是这个hashtable对应的key
上述这样的操作,耗时还是比较大的。多数情况下,我们不需要查询所有的field,可能只想查其中的几个field,这时候我们就用HMGET

HMGET

如何查看hashtable中的多个键值对?
hmget key field1 field2 field3 ...
HMGET 类似于之前的MGET,可以一次查询多个field,HGET一次只能查一个field
有没有hmset,一次设置多个field和value呢??
有, 但是,并不需要使用. 因为hset已经支持一次设置多个field和value了.

HSCAN

上述hkeys, hvals, hgetall都是一条命令能完成所有的遍历操作,存在一定风险:如果hash的元素个数太多,执行的耗时会比较长,从而阻塞redis.
hscan遍历redis的hash. 但是它属于 “渐进式遍历”
=> 即敲一次命令,遍历一小部分. 再敲一次,再遍历一小部分. …… 连续执行多次,就可以完成整个的遍历过程了.
=> 时间就是可控的 化整为零
这也可以联想到ConcurrentHashMap:线程安全的哈希表,这个哈希表在扩容的时候,也是按照化整为零的方式进行的!!

C++ 的标准库,横向和其他编程语言比,就是个 弟中弟 ,C++ 标准库的设计精妙,实现也是很优雅的,但是从功能角度,C++ 标准库给咱们提供的功能太少了~~C++ 提供的各种容器,都是线程不安全的.Java 标准库则直接提供了一些线程安全的集合类(Java 中也有 “容器” 这样的术语,指的是别的了)

Hlen

如何查看一个hashtable中有多少个键值对?
HLEN key
在这里插入图片描述

O(1)获取hash的元素个数,不需要遍历~~

hincrby / hincrbyfloat

请问如何对Hashtable中的value进行加减操作?
hincrby / hincrbyfloat 指令
hincrby 就可以加减整数.使用格式为hincrby key field + num,其中key field用来指定一个hashtable中的value,num表示加数,NUM为负表示减数
hincrbyfloat 就可以加减小数.使用格式为hincrbyfloat key field + float,其中key field用来指定一个hashtable中的value,float表示要加的浮点数,为负表示要减的浮点数

HSTRLEN

如何计算哈希表中指定字段(field)对应的 value 的字符串长度?
HSTRLEN key field

Hash类型的内部编码

Hash类型的内部编码主要就是hashtableziplist两种,我们前面说过,hashtable的实现方式就和我们数据结构中理解的哈希表实现并无太大差别,主要就是基于数组和映射规则实现的,重点在于ziplist,为什么要引入ziplist?这种存储方式有什么好处呢?

对压缩的理解

一些具体的压缩算法:rar, zip, gzip, 7z……
举个例子:
在这里插入图片描述

压缩的本质,是针对数据进行重新编码。不同的数据,有不同的特点. 结合这些特点,进行精妙的设计,重新编码之后,就能够缩小体积。比如上图中,00...0在经过编码之后,就可以用0[100]来代替

为什么要用ziplist存储hashtable?

hash 首先是一个数组~~数组上有些位置有元素,有些没有元素,对于那些没有存放元素的映射位置,这些空间就被浪费了。所以我们引入了ziplist作为Hash类型的一种内部编码方式,这样就可以减少空间的浪费。
但凡事都是有代价的,ziplist 付出的代价就是ziplist进行读写的速度比较慢,并不能达到理想的O(1),如果元素个数少,慢的并不明显。如果元素个数太多了,慢就会雪上加霜.

hash类型的value什么时候用ziplist存?什么时候用hashtable存?

哈希中的元素个数比较少,使用 ziplist 表示。元素个数比较多,使用 hashtable 来表示。这个元素多少的界限值是什么呢?
查看redis.conf 文件中的hash-max-ziplist-entries 配置项(默认 512 个元素),键值对数量大于512用hashtable,键值对数量小于512用ziplist

每个 value 的值长度都比较短,使用 ziplist 表示。如果某个 hash类型的value,一开始用的是ziplist,后来长度越变越长,长到一定程度也会转换成 hashtable。这个value大小的界限值是什么呢?
查看redis.conf 文件中的hash-max-ziplist-value 配置项(默认 64 字节),value大于64字节用hashtable,value小于64字节用ziplist

Hash类型的使用场景

在这里插入图片描述
上图中的数据,用Redis中的Hash应该怎么存?

在这里插入图片描述

Redis当做MYSQL的Cache:实现伪代码

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) {
        响应 404
        return null;
    }

    // 走到这里说明redis没有命中,但是mysql命中了,这个时候我们就需要将此次信息添加到redis中
    // 将缓存以哈希类型保存
    Redis 执行命令: hmset key name userInfo.name age userInfo.age city userInfo.city

    // 写入缓存,为了防止数据腐烂(rot),设置过期时间为 1 小时(3600 秒)
    Redis 执行命令: expire key 3600

    // 返回用户信息
    return userInfo;
}

我们都知道,Redis的string类型也可以用来实现这样的功能,既然String和Hash都可以将Redis实现为MYSQL的Cache,请问这两种方式有什么区别呢?
hash的粒度更细,修改更加方便

  • 如果使用string (json)的格式来表示UserInfo,万一只想获取其中的某个field, 或者修改某个field,需要把整个json字符串都读出来, 解析成对象, 操作field, 再重写转成json字符串, 再写回去
  • 如果使用hash的方式来表示UserInfo,就可以使用field表示对象的每个属性(数据表的每个列),此时就可以非常方便的修改/获取任何一个属性的值了

使用hash的方式 ,确实读写field更直观高效, 但是付出的是空间的代价~~需要控制哈希在ziplist和hashtable两种内部编码的转换,可能会造成内存的较大消耗。

Hash类型与关系型数据库的区别是什么?

简单来说就是,相比与Hash类型,关系型数据库维护起来成本更高,但是支持的查询功能也更复杂

  1. 关系数据库可以做复杂的关系查询,而 Redis 去模拟关系型复杂查询,例如联表查询、聚合查询等基本不可能,维护成本高。

  2. 哈希类型是稀疏的,而关系型数据库是完全结构化的,例如哈希类型每个键可以有不同的 field,而关系型数据库一旦添加新的列,所有行都要为其设置值,即使为 null
    在这里插入图片描述

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值