哈希
哈希表是我们在Java学习中是最最重要的数据结构。
原因有2个:
1、日常开发中,哈希表的出场频率非常高。
2、面试中,哈希表也是非常重要的考点。
Redis自身已经是键值对结构了,Redis自身的键值对就是通过哈希的方式来组织的。把key这一层组织完成之后,到了value这一层~~value的其中一种类型还可以再是哈希。
命令
hset
设置hash中指定字段(field)的值(value)。
HSET key field value [field value ...]
这里的value是以字符串的形式进行存储的。
时间复杂度:插入一组field为O(1),插入N组field为O(N)
返回值:添加字段的个数。

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

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

hdel
删除hash中指定的字段。
1 HDEL key field [field ...]
时间复杂度:删除一个元素为O(1).删除N个元素为O(N)。
返回值:本次操作删除的字段个数。
del删除的是key,hdel删除的是value。
hkeys
获取hash中的所有字段。
HKEYS key
时间复杂度:这个操作,先根据key找到对应的hash[ O(1)],然后遍历hash[O(N)这里的N是hash的元素个数]。总体是O(n)[n为hash中元素个数]。
在redis中谈到O(N),N表示:
1、redis整体的key的个数
2、当前命令中key的个数(可近似看作O(1))
3、当前key对应的value里面的元素个数
......
返回值:字段列表

注意:这个操作也是存在一定风险的!!!类似于之前介绍过的keys *!!!
主要是咱们也不字段某个hash是否会存在大量的field~~
hvals
获取hash中所有的value。(和hkeys相对)
HVALS key
时间复杂度:这个操作,先根据key找到对应的hash[ O(1)],然后遍历hash[O(N)这里的N是hash的元素个数]。总体是O(n)[n为hash中元素个数]。
返回值:所有的值。
和hkeys一样,如果哈希非常大,这个操作就可能倒是redis服务器被阻塞住。
H系列的命令,必须保证key对应的value得是哈希类型!!!
hgetall
获取hash中所有的键值对。
HGETALL key
时间复杂度:这个操作,先根据key找到对应的hash[ O(1)],然后遍历hash[O(N)这里的N是hash的元素个数]。总体是O(n)[n为hash中元素个数]。
返回值:字段和对应的值。
注意:像上面这三个操作都是比较危险的,因为在生产环境中,你不知道当前hash里面有多少键值对,从而导致执行耗时较长,从而阻塞redis!!!
如果需要遍历生产环境中的hash,可以使用hscan来进行遍历redis的hash,它属于"渐进式遍历",敲一次,遍历一小部分,再敲一次,遍历一小部分......连续执行多次,就可以遍历完整个表了。这种思想也被称为化整为零。Java中的ConcurrentHashMap也是使用的这个思想,这个哈希表在扩容的时候,类似于蚂蚁搬家,一次搬运一小部分......
多数情况下,是不需要查询所有的field,只要查询其中的几个field即可~
hmget
类似于之前的mget可以一次查询多个field。
HMGET key field [field ...]
时间复杂度:只查询一个元素为O(1),查询多个元素为O(N),N为查询元素个数。
返回值:字段对应值或者nil。

有没有hmset设置多个field的value呢?有,但是没必要使用,hset本身已经支持设置多个field和value了。
hlen
获取hash中所有字段的个数。
HLEN key
时间复杂度:O(1),这里获取长度不需要遍历哈希表,只需要用一个变量存储哈希表中字段个数即可。
返回值:字段个数。
hsetnx
类似于setnx 不存在的时候才能设置成功,如果存在,则失败。
时间复杂度:O(1)
返回值:1表示设置成功,0表示失败。
hincrby
将hash中字段对应的数值添加指定的值
HINCRBY key field increment
时间复杂度:O(1)
返回值:该字段变化后的值。
hincrbyfloat
hincrby的浮点数版本。
HINCRBYFLOAT key field increment
时间复杂度:O(1)
返回值:该字段变化之后的值。
总结
哈希编码方式
哈希内部的编码方式有两种:
1、哈希中的元素个数比较少,使用ziplist表示,元素个数比较多,使用hashtable来表示。
2、每个value的值长度都比较短,使用ziplist表示,如果某个value长度太长了,也会转换成hashtable。

下图中的配置是写到redis.conf中的,可以修改的~

ziplist是一个压缩列表,那么什么是压缩呢?
在我们的计算机中,经常有一些文件的后缀是:rar,zip,gzip,7z......一些具体的压缩算法~~压缩的本质,是针对数据进行重新编码。
不同的数据,有不同的特点,结合这些特点,进行精妙的设计。重新编码之后,就能够缩小体积~~
例如:
ziplist也是同理~~内部的数据结构也是精心设计的,目的是为了节省内存空间。
在元素少的时候,表示一个普通的hash表可能会浪费一定的空间~~(hash首先是一个数组,数组上有些位置有元素,有些没有元素)。
ziplist付出的代价,进行读写元素,速度是比较慢的。如果元素个数少,慢的并不明显。如果元素个数太多了,慢就会雪上加霜。
哈希的应用场景
1、作为缓存
在redis中,string是可以作为缓存使用的,但是存储结构化数据,使用hash类型更合适一些~~
上述场景使用string类型也能够做到,但是就需要使用到json这样的数据格式了。
如果使用json格式来表示UserInfo万一指向获取其中某个field,或者修改某个field,就需要把整个json读出来解析成对象,操作field,再重新写成json字符串,再写回去~~
如果使用hash的方式来表示UserInfo。就可以使用field表示对象的每个属性(数据表的每个列),此时就可以非常方便地修改/获取任何一个属性地值了~~
使用hash的方式,确实读写field更直观高效,但是需要付出空间地代价,ziplist和hash他变了两种内部编码的转换,可能会造成较大的内存消耗。
哈希类型和关系型数据库两点不同之处:
1、哈希类型是稀疏的,而关系型数据库是完全结构化的,例如:哈希类型每个键可以有不同的field,而关系型数据库一旦添加新的列,所有行都要为其设置值,即使为null。
2、关系型数据库可以做复杂的关系查询,而Redis很难去模拟关系型复杂查询,例如:联表查询,聚合查询等基本不可能,维护成本高。
缓存类型对比:
1、原生字符串类型
优点:实现简单,针对个别属性变更也很灵活。
缺点:低内聚(相同的模块,联系却很少),实用性低
2、序列化字符串类型

优点:针对总是以整体作为操作的信息比较合适,编程也简单。同时,如果序列化方案选择合适,内 存的使用效率很高。
缺点:每次操作都需要进行对象和json字符串之间格式转换。
3、哈希类型

优点:简单、直观、灵活
缺点:需要消耗更大的内存空间,
