hash
命令小结
命令 | 执行效果 | 时间复杂度 |
hset key field value | 设置值 | O(1) |
hget key field | 获取值 | O(1) |
hdel key field [field...] | 删除值 | O(k), k是field个数 |
hlen key | 计算field个数 | O(1) |
hgetall key | 获取所有的field-value | O(k), k是field的个数 |
hmget field [field...] | 批量获取field-value | O(k). k是field的个数 |
hmset field value [field value...] | 批量设置field-value | O(k), k是field的个数 |
hexists key field | 判断field是否存在 | O(1) |
hkeys key | 获取所有的field | O(k), k是field个数 |
hvals key | 获取所有的value | O(k), k是field个数 |
hsetnx key field value | 设置值, 但必须在field不存在时才能设置成功 | O(1) |
hincrby key field increment | 对应field-value + n | O(1) |
hincrbyfloat key field n | 对应field-value + n | O(1) |
hstrlen key field | 计算value的字符串长度 | O(1) |
内部编码
哈希的内部编码有两种:
ziplist(压缩列表): 当哈希类型元素个数小于hash-max-ziplist-entries配置(默认512个), 同时所有值都小于hash-max-ziplist-value配置(默认64字节)时, Redis会使用ziplist作为哈希的内部实现, ziplist使用更加紧凑的结构实现多个元素的连续存储, 所以在节省内存方面比hashtable更优秀.
hashtable(哈希表): 当哈希类型无法满足ziplist的条件时, Redis会使用hashtable作为哈希的内部实现, 因为此时ziplist的读写效率会下降, 而hashtable的读写时间复杂度为O(1)
使用场景
我们之前学过sql型数据库, 我们知道它是使用表的形式来组织数据的, 而在Redis是使用映射方式来保存映射信息.
下面来看一下两者的不同之处:
哈希类型是稀疏的, 而关系型数据库是完全结构化的, 例如哈希类型每个键都可以有不同的field, 而关系型数据库一旦添加新的列, 所有行都要为其设置值, 即使为null.
关系型数据库可以做复杂的关系查询, 而Redis去模拟关系型复杂查询, 例如联表查询, 聚合查询等基本不可能, 维护成本高.
缓存方式对比
截止目前为止, 我们已经能够用三种方法缓存用户信息, 下面给出三种方式的实现方法和优缺点分析.
1.原生字符串类型 -- 使用字符串类型, 每个属性一个键.
set user:1:name James
set user:1:age 23
set user:1:city Beijing
优点: 实现简单, 针对个别属性的变更也很灵活.
缺点: 占用过多的键, 内存占用量过大, 同时用户信息在Redis中比较分散, 缺少内聚性, 所以这种方案基本没有实用性.
2. 序列化字符串类型, 例如JSON格式
set user:1 经过序列化后的用户对象字符串
优点: 针对总是以整体为操作的信息比较合适, 编程也简单. 同时, 如果序列化方案选择合适, 内存的使用效率很高.
缺点: 本身序列化和反序列需要一定的开销, 同时如果总是个别操作属性则非常不灵活.
3.哈希类型
hmset user:1 name James age 23 city Beijing
优点: 简单, 直观, 灵活. 尤其是针对信息的局部变更或者获取操作.
缺点: 需要控制哈希在ziplist和hashtable两种内部编码的转换, 可能会造成内存较大的消耗.
List列表
列表类型是用来存储多个有序的字符串, 如图: a,b,c,d,e五个元素从左到右组成了一个有序的列表, 列表中的每个字符串称为元素,, 一个列表中最多可以存储 2 ^ 32 - 1个元素. 在Redis中, 可以对两端插入和弹出, 还可以获取指定范围的元素列表, 获取指定索引下标的元素等. 列表是一种比较灵活的数据结构, 它可以充当栈和队列的角色, 在实际开发上有很多应用场景.
结构(基本操作)演示:
列表类型特点:
第一: 列表中的元素是有序的, 这意味着可以通过索引下标获取某个元素或者某个范围的元素列表.
第二: 列表中的元素是可以重复的.
命令
lpush/rpush
将一个或者多个元素从左/右侧放入到list中.
语法:
lpush key element [element ...]
时间复杂度: 只插入一个元素为O(1) , 插入多个元素为O(N), N为插入元素个数.
返回值: 插入后list的长度
lpushx/rpushx
在key存在时, 将一个或者多个元素从左/右侧放入(头/尾插到)list中. 不存在, 直接返回.
lpushx key element [element ...]
时间复杂度: 只插入一个元素为O(1) , 插入多个元素为O(N), N为插入元素个数.
返回值: 插入后list的长度
lrange
获取从start到end区间所有的元素, 左闭右闭.
语法:
lrange key start stop
时间复杂度: O(N)
返回值: 指定区间的元素.
lpop/rpop
从list左侧/右侧取出元素(即头删/尾删).
语法:
lpop key
时间复杂度: O(1)
返回值: 取出的元素或者nil.
lindex
获取从左数第index位置的元素.
语法:
lindex key index
时间复杂度: O(N)
返回值: 取出的元素或者nil.
linsert
在特定位置插入元素.
语法:
linsert key <before | after> pivot element
时间复杂度: O(N)
返回值: 插入后的list长度
llen
获取list长度
语法:
len key
时间复杂度: O(1)
返回值: list的长度.
阻塞版本的命令
blpop和brpop是lpop和rpop的阻塞版本, 和对应非阻塞版本的作用基本一致. 除了:
在列表有元素的情况下, 阻塞和非阻塞的表现是一致的. 但如果列表中没有元素, 非阻塞版本会立即返回nil, 但阻塞版本会根据timeout, 阻塞一段时间, 期间Redis可以执行其它命令(就是再开一个客户端可以继续执行其它操作, 包括向对应列表中插入元素, 在阻塞的客户端中会立即返回该元素), 但要求执行该命令的客户端会表现为阻塞状态.
命令中设置了多个键, 那么会从左向右进行遍历键, 一旦有一个对应的列表可以弹出元素, 命令立刻返回.
如果多个客户端同时多一个键执行pop, 则最先执行命令的客户端会得弹出的元素.
使用语法:
blpop key[key ...] timeout
返回值: 取出的元素或nil.
使用图示: