Redis五种值类型
Redis五种值类型
对于Redis来说,每一种数据结构都有着自己的内部编码,而且是多种实现的,这样Redis会在合适的场景选择合适的内部编码,通过
OBJECT ENCODING [key]
可以参看指定key
的内部编码。**这样做的好处: **
- 改进内部编码,对外的数据结构和命令没有影响,对用户提供黑箱模型。
- 多种内部编码可在不同场景下发挥各自的优势。如:
ziplist
比较节约内存,但是元素比较多的时候,性能会有所下降,此时Redis会将编码自动转换为linkedlist
,性能会有所改善。
string类型
String类型是我们最常用的一种value数据类型,在上文中我们看到string的encoding有三种类型,即对应的三种数据结构实现(int,embstr,raw)。
embstr和raw都是SDS存储(上文已介绍),但embstr只需要一次内存分配空间,而raw需要两次。
int编码字符串对象和embstr编码字符串对象在一定条件下会转化为raw编码字符串对象。
如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17127.0.0.1:6379> set test 1
OK
127.0.0.1:6379> object encoding test
"int"
127.0.0.1:6379> append test 1
(integer) 2
127.0.0.1:6379> object encoding test
"raw"
127.0.0.1:6379> set test1 aa
OK
127.0.0.1:6379> object encoding test1
"embstr"
127.0.0.1:6379> append test1 aa
(integer) 4
127.0.0.1:6379> object encoding test1
"raw"当你存储的是中文时,由于redis是二进制安全的,所以在不同编码下数据的长度是不同的,有兴趣的可以看看这篇文章redis是二进制安全。
命令 | 描述 |
---|---|
set key value | 赋值 |
get key | 取值 |
getset key value | 取值并赋值 |
mset k1 v1 k2 v2 | 赋值多个值 |
mget k1 k2 | 获取多个值 |
del key | 删除 |
incr key | 数值增1 |
incrby key increment | 设置增长数 |
decr key | 减少1 |
decrby key increment | 减少整数值 |
append key value | 追加数值 |
strlen key | 获取长度 |
注意
incr 将指定的 key 的 value 值递增 1,如果这个值不存在,就将这个值设置初始值为 0,并且 +1.
decr 是将指定的 key 的 value 值递减 1,如果这个值不存在,就将这个值设置初始化为 0,并且 -1.
incrby是将指定的 key 的 value 值递增 指定的大学,如果值不存在,则设置为 0,并且加指定的值
decrby将指定的 key 的值 减去 指定的值 如果值不存在,则设置为 0,并且减指定的值
append指定的 key 的 value 后面拼接给定的值,如果给定的 key 不存在,则创建这个 key 并且这个key 的值为后面给定的值
1
2
3
4
5
6127.0.0.1:6379> get num
(nil)
127.0.0.1:6379> incr num
(integer) 1
127.0.0.1:6379> get num
"1"如果这个值不能转成数值,不能增减,则抛出异常
1
2
3
4127.0.0.1:6379> get num
"ab"
127.0.0.1:6379> incr num
(error) ERR value is not an integer or out of range
字符串命令
String数据类型中比较常用的就是作为字符串使用,可以作为缓存存储一些信息,比如登录用户的一些信息等。如果对命令不太熟悉的前提下我们在Linux系统中可以在连接redis客户端的前提下使用
help @String
来获取各个命令的作用和语法或者在redis命令中心查看。作为字符串常用的命令包括以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29## ex(秒) 和 px(毫秒) 表示key的过期时间 nx 表示不存在才操作 xx表示存在才操作
SET key value [EX seconds|PX milliseconds] [NX|XX] [KEEPTTL]
summary: Set the string value of a key
since: 1.0.0
## set 多个 key value
MSET key value [key value ...]
summary: Set multiple keys to multiple values
since: 1.0.1
## 在原value后追加字符串
APPEND key value
summary: Append a value to a key
since: 2.0.0
## 在value的某个偏移量上重写新的value值
SETRANGE key offset value
summary: Overwrite part of a string at key starting at the specified offset
since: 2.2.0
## 获取value的值长度
STRLEN key
summary: Get the length of the value stored in a key
since: 2.2.0
## 获取旧值并设置新值
GETSET key value
summary: Set the string value of a key and return its old value
since: 1.0.0
字符串数值命令
当作为数值使用时,其操作都是原子性,故可以作为规避并发的一种手段,其应用场景包括 秒杀、点赞、收藏,抢购等。
作为数值常用的命令如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24## value + 1
INCR key
summary: Increment the integer value of a key by one
since: 1.0.0
## value - 1
DECR key
summary: Decrement the integer value of a key by one
since: 1.0.0
## value + increment
INCRBY key increment
summary: Increment the integer value of a key by the given amount
since: 1.0.0
## value - increment
DECRBY key decrement
summary: Decrement the integer value of a key by the given number
since: 1.0.0
## value + increment (浮点型)
INCRBYFLOAT key increment
summary: Increment the float value of a key by the given amount
since: 2.6.0
bitmap
bitmap不是实际的数据类型,而是在String类型上定义的一组面向位的操作。根据官方给出的文档提示,位图最大的优势就是在存储时能节省大量空间。其常用的应用场景包括统计活跃用户或用户登录天数等。
1
2
3
4
5
6
7
8127.0.0.1:6379> set hello big
OK
127.0.0.1:6379> getbit hello 0
(integer) 0
127.0.0.1:6379> getbit hello 1
(integer) 1
127.0.0.1:6379> getbit hello 2
(integer) 1
命令
1 | ## 统计key从start到end的被设置的为1的bit数 start end 是字节索引 |
简单演示
这里对bitmap进行简单演示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27redis> BITCOUNT bits
(integer) 0
redis> SETBIT bits 0 1 # 0001
(integer) 0
redis> BITCOUNT bits
(integer) 1
redis> SETBIT bits 3 1 # 1001
(integer) 0
redis> BITCOUNT bits
(integer) 2
127.0.0.1:6379> setbit bit 1 1
(integer) 0
127.0.0.1:6379> setbit bit 7 1
(integer) 0
# 分别在key为bit的二进制索引位置1和二进制位置7设置为1,这样实际上就是1000 0010 根据ASCII编码该值就是A。
127.0.0.1:6379> get bit
"A"
# 表示有2个位被设置为1
127.0.0.1:6379> bitcount bit
(integer) 2当添加start 和 end参数时 该值表示字节索引,从右到左
1
2
3
4
5
6
7
8
9
10
11127.0.0.1:6379> setbit bit 16 1
(integer) 0
127.0.0.1:6379> get bit
"A\x00\x80"
# 在第二进制索引位置16设置为1 即 10000000 01000001
# 当统计第1个字节到被设置为1的数量时则是2个
127.0.0.1:6379> bitcount bit 0 1
(integer) 2
# 当不添加则表示所有字节上述实例就是3个。
127.0.0.1:6379> bitcount bit
(integer) 3首先在二进制索引5设置为1 即 0001 0000 ,
- 然后分别获取第一个为0和第一个为1的索引位置;
- 然后再二进制索引8设置1 即 10010000 ,
- 当设置start 和 end时 该值表示字节索引
- 结果即为 5 和 8。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17# 1 0000
127.0.0.1:6379> setbit test 5 1
(integer) 0
# 获取第一个为1的索引位置;
127.0.0.1:6379> bitpos test 1
(integer) 5
# 获取第一个为0的索引位置;这里显示的都是0)
127.0.0.1:6379> bitpos test 0
(integer) 0
127.0.0.1:6379> setbit test 8 1
(integer) 0
# 开始索引分别从第二个字节开始
127.0.0.1:6379> bitpos test 1 1
(integer) 8
# 开始索引分别从第一个字节 10010000
127.0.0.1:6379> bitpos test 1 0
(integer) 5
应用场景
用途我们在上面介绍时分别都有提及到,不过作为字符串和数值的用途比较常见,此处不做撰述。我们来仔细看看作为bitmap时的应用场景如何使用。
首先就是统计用户登录天数。假设你用关系型数据库去处理,每个用户登录一天你有相关的登录记录表去录,如果你有1千万的活跃用户每天都在登录你的系统那么你每个人都去记录一条数据,一年365天光数据存储都占用了非常多的资源。如果是使用bitmap你可以这样做,登录人的id作为key,每一位表示一天,即如下表。(1表示登录过 ,0表示未登录)
1
2
3
4
5
6127.0.0.1:6379> setbit user01 1 1
127.0.0.1:6379> setbit user01 2 1
127.0.0.1:6379> setbit user01 16 1
# 出用户登录天数
127.0.0.1:6379> bitcount user01
(integer) 3
用户\天数 | 1 | 2 | 3 | 4 | 5 | … | 364 | 365 |
---|---|---|---|---|---|---|---|---|
userId1 | 0 | 1 | 1 | 0 | 1 | … | 0 | 1 |
userId2 | 1 | 1 | 0 | 1 | 1 | … | 1 | 0 |
… | … | … | … | … | … | … | … | … |
userId99 | 1 | 1 | 1 | 1 | 0 | … | 1 | 1 |
当用户登录使用
setbit userId offset(哪一天)1
就可以记录该用户当天登录,然后使用bitcount
即可以得出用户登录天数,或者你需要获取某个时间周期内的登录天数只要在bitcount
加上start
和end
即可,最最重要的是它占用的空间非常少比起关系型数据库少太多。其次就是活跃用户统计,比如你要统计日🔥用户,那么你也可以使用bitmap来做,思路和上面差不多,只不过现在是把日期作为key,用户的id映射为位,即如下表。(1表示活跃,0表示不活跃)
日期\用户 | 1 | 2 | 3 | 4 | 5 | … | 364 | 365 |
---|---|---|---|---|---|---|---|---|
20200818 | 0 | 1 | 1 | 0 | 1 | … | 0 | 1 |
20200819 | 1 | 1 | 0 | 1 | 0 | … | 1 | 1 |
- 当用户活跃时,使用
setbit 20200818 offset(用户id映射的位)1
即表示用户活跃,然后使用bitcount
即可以得出当天的活跃用户;如果你需要对某个时间周期内进行统计且需要去重则可使用bitop or destkey 时间key1 时间key...
然后再对destKey
进行bitcount
即可。
假设每个用户存储需要20B空间, 那么1亿个用户所占用的空间就是 20 * 100000000 = 20000000000B ≈ 18G,看起来也是个不小的内存消耗;如果你对精确度不是很高的要求那么可以考虑Redis提供的Hypeloglog,可以参考下 Redis HyperLogLog介绍及应用。
hash类型
散列类型,支持扩展key,提供字段属性,字段和字段值之间实现映射。
使用场景:存储对象信息(用户,商品信息等)
字段值只支持string类型。
命令 描述 HSET key field value 设置值,,0代表更新,1代表插入 HMSET key field value [field value …] 一次设置多个值 HSETNX key field value 设置字段的值,如果字段已存在,该操作无效果。 hget key field 获取值 hmget key field [field …] 获取多个值 hgetall key 获取所有的值 hdel key field [field …] 删除一个属性或多个属性 del key 删除整个 key HINCRBY key field increment 增加数字,不存在给默认值0 hexists key field 返回hash里面field是否存在 hlen key 获取某个 key 存在的属性个数 hkeys key 获取所有属性名称 hvals key 获取属性所有的值 1
2
3
4
5
6
7
8
9
10
11
12
13192.168.2.101:6379> HSET user:001 name zs
(integer) 1
192.168.2.101:6379> HSET user:001 name ls
(integer) 0
192.168.2.101:6379> HGET user:001 name
"ls"
192.168.2.101:6379> HMSET user:001 name zs age 12
OK
192.168.2.101:6379> HMGET user:001 name age
1) "zs"
2) "12"
有该key则不赋值,没有则赋值
192.168.2.101:6379> HSETNX user:001 name ww- Hash数据类型就是 key-value 键值对,对应的encoding可以是ziplist或者hashtable。 Hash对象只有同时满足下面两个条件时,才会使用ziplist(压缩列表):
- 哈希中元素数量小于512个;
- 哈希中所有键值对的键和值字符串长度都小于64字节。这样会非常节约内存。
- 其应用场景包括点赞,收藏等。
- 对其数据结构有兴趣的仍然可以了解Redis为何这么快–数据存储角度的Hash篇。
- Hash数据类型就是 key-value 键值对,对应的encoding可以是ziplist或者hashtable。 Hash对象只有同时满足下面两个条件时,才会使用ziplist(压缩列表):
常用的命令如下:
1 | ## 设置多个键值对 |
应用场景
Hash的数据类型可以用作点赞,收藏,阅读数等。比如你有一个帖子,你需要为它统计点赞,收藏,阅读。如果你使用string类型的话 你肯定是要设置3个key分别对应点赞,收藏,阅读,这样在无形中也浪费了存储空间;但是如果你使用hash的话 一个key就搞定,使用hset 帖子id 点赞数 0 收藏数 0 阅读 0
,当每增加一个变化时 使用HINCRBY key field increment
对field进行数值计算就可以解决。
其次就是当你缓存一些数据时,比如说用户如果你使用string缓存,那么必然是个json对象,当你只需要某个值时你需要把整个对象取出来然后处理,但hash就可以直接取出对应的值。
list类型
在 redis 中。list 是按照插入排序的一个字符串的链表,和数据结构中链表是一样的,
可以再头部(左侧)或者尾部(右侧)增加或者删除元素。
按照插入顺序排序
- ArrayList 使用数组方式
- LinkdList 使用双向链接方式
- 双向链表增加数据
- 双向链表删除数据
使用场景:用于商品评论
常用命令
命令 | 描述 |
---|---|
lpush key value [value …] | 左端添加,如果不存在,则创建一个 list,如果存在,则添加进 key 的 list |
LPUSHX key value | 只有当 key 已经存在并且存着一个 list 的时候,在这个 key 下面的 list 的头部插入 value。 与 LPUSH 相反,当 key 不存在的时候不会进行任何操作。 |
rpush key value | 右端添加 |
rpushx key value | 将一个值插入到已存在的列表w尾部,如果不存在,则不会插入,返回 (integer) 0 |
lrange key [start] [end] | 查看列表,index:从0开始,-1代表最后一位 |
lpop key | 左边弹出, 如果存在,返回头部的第一个元素,如果不存在,返回 nil.弹出以后就没有这个元素 |
rpop key | 右边弹出 |
llen key | 获取列表元素个数 |
lrem key count value | 从列表中删除元素 |
LINDEX key index | 查看指定索引的值 |
ltrim key start stop | 修剪片段,list 就会只包含指定范围的指定元素,start 和 stop 都是由0开始计数的 |
LINSERT key BEFORE/AFTER pivot value | 把 value 插入存于 key 的列表中在基准值 pivot 的前面或后面。当 key 不存在时,这个list会被看作是空list,任何操作都不会发生.当 key 存在,但保存的不是一个list的时候,会返回error。 |
RPOPLPUSH source destination timeout | 原子性地返回并移除存储在 source 的列表的最后一个元素(列表尾部元素), 并把该元素放入存储在 destination 的列表的第一个元素位置(列表头部) |
BRPOPLPUSH source destination timeout | BRPOPLPUSH 是 RPOPLPUSH 的阻塞版本。当 source 包含元素的时候,这个命令表现得跟 RPOPLPUSH 一模一样。 当 source 是空的时候,Redis将会阻塞这个连接,直到另一个客户端 push 元素进入或者达到 timeout 时限。 timeout 为 0 能用于无限期阻塞客户端。 |
lrem 从存于 key 的列表里移除前 count 次出现的值为 value 的元素。 这个 count 参数通过下面几种方式影响这个操作:
count > 0: 从头往尾移除值为 value 的元素。
count < 0: 从尾往头移除值为 value 的元素。
count = 0: 移除所有值为 value 的元素。
比如, LREM list -2 “hello” 会从存于 list 的列表里移除最后两个出现的 “hello”。
需要注意的是,如果list里没有存在key就会被当作空list处理,所以当 key 不存在的时候,这个命令会返回 0。
示例
1 | 127.0.0.1:6379> lpush mylist a b c |
ListAPI
List数据类型就是有序(插入顺序)元素的序列,其对应的encoding为上文表格中的ziplist和linkelist。Redis中的列表支持两端插入和弹出,并且可指定位置获取元素,可以充当数组,队列,栈等。因此,其应用场景包括微博的时间轴列表,阻塞队列等。
这里不对底层数据结构进行分析,若想了解zipList和linkedList可参考 Redis为何这么快–数据存储角度的List篇。
- 常用的命令如下:
1 | ## 获取列表index的值(从左开始) |
- 以上罗列的是从list的左侧开始,同样的redis也提供了从list的右侧开始。redis同样提供了阻塞式的一些命令。如下所示:
1 | ## 阻塞获取列表元素并删除(左侧) 可指定多个列表 同时可设置timeout时间(单位秒) |
这里要注意的是当有多个连接对同一个list进行阻塞监听时,redis的处理方法是维护了一个阻塞队列,提供先阻塞先服务,当多个阻塞同时满足唤醒条件时,先阻塞的优先唤醒。
应用场景
上面我们说List可以充当栈,队列。栈的特征是先进后出,那么在使用Redis的同一个方向命令(
lpush lpop
)时就可以实现栈的特点;队列的特征是先进先出,那么同样的在使用Redis的反向命令(lpush rpop
或rpush lpop
)时就可以实现队列的特点。微博的时间轴列表也比较简单,每次新增一个微博时只需要
lpush
进去即可,通过lrange
来获取最新的微博消息。阻塞队列的实现就需要使用
blpop
或brpop
,首先一定要先调用blpop
监听,然后再另外一个客户端进行lpush
操作,如果先lpush
再blpop
那么先lpush
的数据就不会被监听到。
set
- 无序,不重复
- Set是String的无序去重排列,对应的encoding是intset(整数集合)或hashtable。intset(整数集合)当一个集合只含有整数,并且元素不多时会使用intset(整数集合)作为Set集合对象的底层实现。其应用场景包括共同关注,随机事件等。
常用的命令
1 | # 集合内操作 |
应用场景
- Set的应用场景包括随机事件,公共好友。这其实就对应了Set的集合内命令和集合间命令。集合内命令的
spop key
和srandmember key count
就可以应对随机事件,比如抽奖系统,key为奖品,member为用户id,如果希望中奖的人不会再中就可以使用spop
,如果希望重复中奖就可以使用srandmember
;集合间命令获取交集sinter
就可以应对共同好友,共同关注,key为用户,member分别为用户的关注。
命令 | 描述 |
---|---|
SADD key member [member …] | 添加一个或多个指定的member元素到集合的 key中 |
spop key | 删除元素 |
SMEMBERS key | 查看所有元素 |
SMEMBER key member | 判断是否存在,是集合key的成员,则返回1,集合key不存在,则返回0 |
scard key | 长度 |
SUNION k1 k2 | 并集 |
SINTER k1 k2 | 交集 |
SDIFF k1 k2 | 差集 |
1 | 127.0.0.1:6379> sadd set1 a |
sortedset
又名zset,唯一且可排序。
为每个元素设置分数,根据分数实现排序。
应用场景: 排行榜
Sorted Set是在Set上添加一个分数(score)维度的集合,通过分数来为集合中的元素进行排序。相比于Set来说Sorted Set可理解为一个有序无重复的序列,其encoding为ziplist或skiplist。当一个有序集合的元素数量比较多或者成员是比较长的字符串时,Redis就使用skiplist(跳跃表)作为ZSet对象的底层实现。其应用场景主要包括排行榜等。
常用命令如下
1 | ## 添加集合元素 NX 不更新 只添加 XX只更新 不添加 CH 返回值为修改总数(默认是新增总数) INCR 分值添加 |
应用场景
- Sorted Set最常见的应用场景便是排行榜,使用
zadd key score member
就可以实现自动排序,默认按照从小到大排序,如果你希望倒序那就使用zrevrangebyscore
根据分数倒序即可,想获取某一个元素的排名使用zrank
,但要记住这仍是正序的,因为sorted set物理内存不随命令变化,想要倒序的排名使用zrevrank
。
命令 | 描述 |
---|---|
zadd key score member [score member … ] | 添加元素 |
ZSCORE key member | 查看排序的分数 |
ZREM key score member [score member … ] | 删除元素 |
ZRANGE key start stop [WITHSCORES] | start 和stop 都是基于零的索引,顺序输出[start,stop]包含的区间 |
ZREVRANGE key start stop [WITHSCORES] | 倒序输出 |
ZRANK key member | 返回有序集key中成员member的排名,其中有序集成员按score值递增(从小到大)顺序排列。排名以0为底,也就是说,score值最小的成员排名为0 |
ZREVRANK key member | |
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count] | 如果M是常量(比如,用limit总是请求前10个元素),你可以认为是O(log(N))。返回key的有序集合中的分数在min和max之间的所有元素(包括分数等于max或者min的元素)。元素被认为是从低分到高分排序的。min和max可以是-inf和+inf,这样一来,你就可以在不知道有序集的最低和最高score值的情况下,使用ZRANGEBYSCORE这类命令。 |
ZINCRBY key increment member | 增加某个成员分数 |
zcard key | 查看元素个数 |
ZCOUNT key min max | 统计范围内的元素个数 |
ZREMRANGEBYRANK key start stop | 根据排名删除范围内元素 |
ZREMRANGEBYSCORE key start stop | 根据分数删除范围内元素 |
1 | # 添加元素 |
RedisObject
redisObject是redis中封装value对象的数据结构。任何一个value都会被包装成一个redisObject。redisObject能指定value的类型,编码格式,内存回收,数据指针等。
这样设计的好处是在5种常用类型设置多种不同的的数据结构实现,优化对象在不同场景下的效率。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16typedef struct redisObject {
// 刚刚好32 bits
// 对象的类型,字符串/列表/集合/哈希表
unsigned type:4;
// 未使用的两个位
unsigned notused:2; /* Not used */
// 编码的方式,Redis 为了节省空间,提供多种方式来保存一个数据
// 譬如: “123456789” 会被存储为整数123456789
unsigned encoding:4;
// 当内存紧张,淘汰数据的时候用到
unsigned lru:22; /* lru time (relative to server.lruclock) */
// 引用计数
int refcount;
// 数据指针
void *ptr;
} robj这里介绍几个比较关键的属性:
type: 标记了value对象的数据类型,使用
type
命令来获取。所有数据类型如下所示:1
2
3
4
5
6/* Object types */
encoding: 标记了value对象的编码,也就是使用了什么数据结构,使用
object encoding
命令来获取。编码如下所示:1
2
3
4
5
6
7
8
9
10
11/* Objects encoding. Some kind of objects like Strings and Hashes can be
* internally represented in multiple ways. The 'encoding' field of the object
* is set to one of this fields for this object. */redis为优化内存,对数据类型提供了多种底层实现方式,type和encoding对应关系如下表格所示:
类型(type属性) 编码(encoding属性) 注释 REDIS_STRING REDIS_ENCODING_INT 整数值实现的字符串 REDIS_STRING REDIS_ENCODING_EMBSTR embstr编码的简单动态字符串实现的字符串 REDIS_STRING REDIS_ENCODING_RAW 简单动态字符串实现的字符串 REDIS_LIST REDIS_ENCODING_ZIPLIST 压缩列表实现的列表 REDIS_LIST REDIS_ENCODING_LINKEDLIST 双向链表实现的列表 REDIS_HASH REDIS_ENCODING_ZIPLIST 压缩列表实现的哈希表 REDIS_HASH REDIS_ENCODING_HT 字典实现的哈希表 REDIS_SET REDIS_ENCODING_INTSET 整数集合实现的集合 REDIS_SET REDIS_ENCODING_HT 字典实现的集合 REDIS_ZSET REDIS_ENCODING_ZIPLIST 压缩列表实现的有序集合 REDIS_ZSET REDIS_ENCODING_SKIPLIST 使用跳跃表+字典实现的有序集合 lru: redis对数据集占用内存的大小由周期性的计算,当超出限制时,会淘汰超时的数据。即淘汰的标准为: oversize & overtime。
举个栗子,当set hello world时会有以下数据模型:
- dictEntry: Redis给每个key-value键值对分配一个dictEntry,里面有着key和val的指针,next指向下一个dictEntry形成链表,这个指针可以将多个哈希值相同的键值对链接在一起,由此来解决哈希冲突问题(链地址法)。
- **sds: **键key “hello” 是以SDS(简单动态字符串)存储。
- redisObject: 值val是存储在redisObject。
简单动态字符串: 长度动态可变,可类比为Java中的ArrayLIst。Redis中不仅Key是以SDS形式存储,String类型value对象也有以SDS存储。
1
2
3
4
5struct sdshdr {
int len; // buf数组中已经使用的字节的数量,也就是SDS字符串长度
int free; // buf数组中未使用的字节的数量
char buf[]; // 字节数组,字符串就保存在这里面
};- **常数复杂度获取字符串长度: **len字段存储字符串长度
- **预空间分配: **
- SDS长度(len的值)小于1MB,那么程序将分配和len属性同样大小的未使用空间,这时free和len属性值相同。
- SDS长度(len的值)大于等于1MB,程序会分配1MB的未使用空间。
- 惰性释放空间: 当执行sdstrim(截取字符串)之后,SDS不会立马释放多出来的空间,如果下次再进行拼接字符串操作,且拼接的没有刚才释放的空间大,则那些未使用的空间就会排上用场。通过惰性释放空间避免了特定情况下操作字符串的内存重新分配操作。
- 杜绝缓冲区溢出: 使用C字符串的操作时,如果字符串长度增加(如strcat操作)而忘记重新分配内存,很容易造成缓冲区的溢出;而SDS由于记录了长度,相应的操作在可能造成缓冲区溢出时会自动重新分配内存,杜绝了缓冲区溢出。