redis的数据类型:Hash
文章目录
- Hash类型介绍
- 我要存一个uid为1的用户对象,姓名为James,年龄是28,如果我想用Redis中的string类型来存,应该怎么存?如果我想用Redis中的Hash类型来存,又应该怎么存?
- 哈希类型中的映射关系通常称为field-value,为什么这里不用key-value?
- 请问一个key如果对应的value类型是hash<field,value>,那么key field value三者之间的对应关系是怎样的?是一对一对一吗?
- Hash类型的命令
- HSET
- HGET
- 如何获得一个Hash表中field1对应的value?
- 如果hget key field指令中,key或者field不存在,结果会怎样?
- hexists
- Hdel
- 如何删除hashtable中的一个<field,value>键值对?
- 如果我语句中指定的key和field中有无效的参数,返回的结果是什么?
- 如何删除一个hashtable?
- HKEYS
- hvals
- hgetall
- HMGET
- HSCAN
- Hlen
- hincrby / hincrbyfloat
- HSTRLEN
- Hash类型的内部编码
- 对压缩的理解
- 为什么要用ziplist存储hashtable?
- hash类型的value什么时候用ziplist存?什么时候用hashtable存?
- Hash类型的使用场景
- 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类型的内部编码主要就是hashtable
与ziplist
两种,我们前面说过,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) {响应 404return 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类型,关系型数据库维护起来成本更高,但是支持的查询功能也更复杂
-
关系数据库可以做复杂的关系查询,而 Redis 去模拟关系型复杂查询,例如联表查询、聚合查询等基本不可能,维护成本高。
-
哈希类型是稀疏的,而关系型数据库是完全结构化的,例如哈希类型每个键可以有不同的 field,而关系型数据库一旦添加新的列,所有行都要为其设置值,即使为 null