持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第4天,点击查看活动详情
在redis中,哈希类型指对应的value本身又是一个键值对结构,和字符串类型比较如下
这是对应关系,我们再搞清楚一个概念,在哈希数据类型中,将value中的key称之为field
,如下图所示。
我们先看一看哈希的操作命令
1 命令
1.1 设置值
设置值的命令是hset key field value
127.0.0.1:6379> hset user:1 name luke
(integer) 1
奇怪的是,当key的值已经存在,这样设置会报错
127.0.0.1:6379> hset user name luke
(error) WRONGTYPE Operation against a key holding the wrong kind of value
这是因为,key为user的是存在的,并且value类型不是哈希类型
127.0.0.1:6379> get user
"demi"
当设置为同类型的时候是可以的,但是变更类型设置的时候就报错了。
这个命令的时间复杂度是O(1),hset就是简单的设置值,值得注意的就是设置的值若是存在,必须value的类型一样才可以去修改。
1.2 获取值
获取值的命令则是hget key field
127.0.0.1:6379> hget user:1 name
"luke"
当key或者field不存在的时候,都会返回nil
127.0.0.1:6379> hget user:1 name
"luke"
127.0.0.1:6379> hget user:1 name1
(nil)
127.0.0.1:6379> hget user:2 name
(nil)
这个命令的时间复杂度是O(1),获取值可以使用它,但是只能一个一个获取,这个命令不能批量获取。
1.3 删除field
我们可以删除一个或者多个field,使用的命令是hdel key field ...
127.0.0.1:6379> hdel user:1 name
(integer) 1
127.0.0.1:6379> hdel user:1 name
(integer) 0
操作删除了几个field就返回几,当没有数据删除的时候,就返回0.
127.0.0.1:6379> hset user1 name luke
(integer) 1
127.0.0.1:6379> hset user1 age 18
(integer) 1
127.0.0.1:6379> hdel user1 name age
(integer) 2
删除命令则是主动的使值失效,这个操作需要注意,不要同时删除过多的值,以免获取数据的时候获取不到,从而使大量的流量打到数据库。其实也很少使用,一般都给值设了过期时间,使其到时自动失效。
1.4 计算field个数
hlen key
会返回对应的key有几个field
127.0.0.1:6379> hset user3 name luke
(integer) 1
127.0.0.1:6379> hset user3 age 18
(integer) 1
127.0.0.1:6379> hset user3 sex 1
(integer) 1
127.0.0.1:6379> hlen user3
(integer) 3
这个命令旨在计算个数,当key不存在的时候会返回0,当key下边的field全部删除了也会返回0。从官方给出的时间复杂度O(1)来看是直接取了保存的总数,并没有轮询所有值。
1.5 批量设置或者获取field-value
批量设置是hmset key field value
批量获取是hmget key field
127.0.0.1:6379> hmset user4 name josh age 19 city xian
OK
127.0.0.1:6379> hmget user4 name age city
1) "josh"
2) "19"
3) "xian"
这两个命令则是设置值和获取值的升级版,主要是批量操作节省了网络IO成本,redis本来处理数据就是很快的,再减少网络成本之后,岂不是快的飞起。
1.6 判断field是否存在
判断是否存在的话命令是hexists key field
127.0.0.1:6379> hexists user1 name
(integer) 0
127.0.0.1:6379> hexists user4 name
(integer) 1
存在返回1,不存在返回0
1.7 获取所有field
hkeys key
可以获取到key下所有的field,会轮询所有。
127.0.0.1:6379> hkeys user4
1) "name"
2) "age"
3) "city"
1.8 获取所有value
hvals key可以获取到key下的所有value,会轮询所有并按照设置的顺序排列
127.0.0.1:6379> hvals user4
1) "josh"
2) "19"
3) "xian"
1.9 获取所有的field-value
hgetall key
会按照field value的顺序依次输出key对应的值。
127.0.0.1:6379> hgetall user4
1) "name"
2) "josh"
3) "age"
4) "19"
5) "city"
6) "xian"
这个命令会轮询所有的,当对应的key内field比较多的时候,会很影响性能。
1.10 hincrby hincrbyfloat
hincrby key field将field对应value的值加上指定的增量
127.0.0.1:6379> hmset user1 name luke age 18
OK
127.0.0.1:6379> hincrby user1 age 2
(integer) 20
127.0.0.1:6379> hget user1 age
"20"
hincrbyfloat key field这个命令则要求field对应value的值可以解析为float类型
127.0.0.1:6379> hset user:2 height 175.5
(integer) 1
127.0.0.1:6379> hincrbyfloat user:2 height 3
"178.5"
这两个命令其实与
incrby
和incrbyfloat
相同,只是作用域不同
1.11 计算value的字符串长度
hstrlen key field
可以返回field对应的value的长度。
127.0.0.1:6379> hmset user1 name luke age 18
OK
127.0.0.1:6379> hstrlen user1 name
(integer) 4
这个命令的时间复杂度是O(1),直接读取的值,并不会去轮询计算。
2 内部编码
哈希类型的内部编码有两种
- ziplist
- hashtable
2.1 ziplist
ziplist其实设计初衷是为了更节省内存,它会使用更加紧凑的结构实现多个元素的连续存储。
使用这个内部编码有两个要求:
- 元素个数小于hash-max-ziplist-entries配置,默认是512个
- 所有值的大小小于hash-max-ziplist-value配置,默认是64字节
2.2 hashtable
当hash类型不满足上边说的两个条件任意一个的时候,会使用hashtable类型的内部编码作为hashl类型的实现。
就不演示了,造满足这俩条件的数据也蛮累的,而且也比较简单,这里其实会牵扯到内存优化技巧,我们之后会聊到。
3 使用场景
存储类似于java中的对象信息数据,比如:
其实这样的数据字符串类型序列化也可以存储,只是使用哈希类型更加直观并且可以任意修改其中的属性内容。
那么就有小伙伴想了,那我不在数据库存了,我把用户表在redis中用hash存了不就行了。 这里的哈希类型和一般的关系型数据库还是有稍许不同的。
- 哈希类型中key的field是可变的,对于不同的的user,luke和crdric我们可以设置不同的field,但是在关系型数据库中,只要列确定了,每条数据都要设置值,尽管可能是null。
- 关系型数据库我们可以做复杂的关联查询,动辄关联几十张表进行查询,sql几十行。但是这么复杂的查询操作在redis中是很难实现的,就算实现了,数据的变化,也会让维护成本变得很高。
所以,就算redis中的哈希可以存储类似于关系型数据库表的数据,但是它是远远不能完全替代的。