(二)Redis 数据类型 - String

参考资料 : 《Redis 设计与实现》

  • String
    • 字符数组
    • 数值型【int】
    • bitmap
  • List
  • Set
  • Sorted Set
  • Hash

Help

redis-cli 中,使用 help 可以查看帮助,不同帮助按照组划分,@ 前缀

# 通用组
@generic
# 不同数据类型
@string
@list
@set
@sorted_set
@hash
@pubsub
@transactions
@connection
@server
@scripting
@hyperloglog
@cluser
@geo
@stream

String

基本操作
# 设置一个键值
set key val
# 获取一个键的值
get key
# 已存在就不设置
setnx key val
# 同时设置超时时间
setex key second val
# 设置多个
mset key val [key val...]
# 取出多个
mget key [key...]
# 设置多个值,如果都不存在【原子性的】
msetnx key val [key val...]
字符串操作
# 追加
append key content
# 截取一部分【这里支持反向索引,如 getrange k1 0 -1 获取全部】
getrange key start end
# 取旧的值,设置新的值
getset
# 设置一部分
setrange key start end
# 获取长度
strlen key
计算操作
# 自增 1
incr key
# 自增指定值
incrby key num
# 自减 1
decr key
# 自减指定值
decrby key num
# 增加浮点数值
incrbyfloat key num
位图操作 [bitmap]
# 设置某一位【这里的 val 只能是 0 或 1 】
setbit key offset val	
# 查看某一位的二进制值
getbit key offset
# 返回 key 的 val 中在 [start, end] 中间,第一次出现 bit 的位置 【start end指的是字节】
bitpos key bit [start] [end]
# 统计 [start, end] 中间出现了几个 1,【start end指的是字节】
bitcount key [start end]
# 对两个 key 的 val 进行按位 与[and] 或[or] 非[not] 异或[xor],并将结果保存在 destkey
bitop operation destkey key [key ...]

Redis 中按照字节存储内容,每个字节有8个字符,其中字符之间也有 offset 供我们做一些位操作

位图操作的应用场景
  1. 有一个用户系统,需要统计每个用户发生了登录行为的天数

    一个用户的存储成本:每年 365 天,对应到 365 bit = 45 byte + 5bit

    setbit sean 1 1:用户 Sean 第 1 天登录了

    setbit sean 7 1:用户 Sean 第 7 天登录了

    setbit sean 364 1:用户 Sean 第 364 天登录了

    bitcount sean -2 -1:统计用户年末的 8 + 5 = 13 天内登录了几次

  2. 京东 618 活动,凡是在 6月18日当天登录的客户都送礼物,需要根据平时的访问量来准备礼物数,统计活跃用户数

    先将一亿用户,映射到 [1, 1亿] 的区间上,以日期做 key 存储那天的用户登录情况

    setbit 20190101 1 1 :20190101 那天,用户 1 登录了

    setbit 20190102 1 1 :20190102 那天,用户 1 登录了

    setbit 20190102 2 1 :20190102 那天,用户 2 登录了

    bitop or dest 20190101 20190102 : 20190101 或 20190102 两天内的活跃用户数

    bitcount dest 0 -1

Redis 中,对于 String 类型的 encoding 有三种,分别是

  • int 数值型
  • embstr
  • raw 太大的数值(44位以上)

查看其编码可用如下命令:object encoding key

set k1 hello
strlen k1 # 5
set k2 9
object encoding k2 # int
strlen k2 # 2
append k2 999
get k2 # 9999
object encoding k2 # raw 这里演示了一些操作会把 string 的 encoding 改变
incr k2 # 10000
object encoding k2 # int
strlen k2 # 5

上述命令中,后面 k2 在 incr 之后,Redis 将其重新标记为了 int 型,但是在 strlen 时,还是占5个字节。这里证实了 Redis 是 二进制安全 的。redis 存储数据就是一个字符一个字节,当 k2 接收到了 incr 命令时,先查看自己的 encoding 是 int,那么可以直接进行自增操作,如果 发现其是 raw,则先尝试将其转换为 int 再进行自增操作。

set k3 a
strlen k3 # 1
append k3 中
strlen k3 # 4

这里将 k3 追加了一个中文字符后,其长度变为了4个字节,可以看出 这个汉字在 Redis 中占用了三个字节。然而这并不是 redis 决定的。这完全取决于 当前客户端对 中文字符使用何种编码集,当不同客户端分别使用 GBK 编码集和 UTF-8 编码集时,设置的中文取出后就变成乱码了。所以必须统一 redis 客户端使用的编码集

String 的底层结构

Redis 中的 String 是 简单动态字符串 Simple Dynamic String ,简称 SDS

SDS 的结构如下所示

struct sdshdr {
  // 记录 buf 中已使用的字节数量
  int len;
  // 记录 buf 中剩余的字节数量
  int free;
  // 字节数组,保存字符串
  char buf[];
}

redis 在保存一个字符串时,也沿用了 C 语言中字符串结尾是 \0 的这一惯例,目的是为了能直接使用 C 字符串函数库中的一部分函数。所以 SDS 实际占用空间 = len + free + 1 【字节】

SDS 的特点:
  • STRLEN 获取一个字符串的长度,时间复杂度为 O(1) 。
    • 【C语言中要遍历直到遇到 \0 ,时间复杂度为 O(N)】
  • 杜绝缓冲区溢出,试图向一个 SDS append【sdscat命令】 之前,SDS API 会先判断 free 字段够不够,不够自动扩容。
    • 【C语言中需要程序员关系是否有足够的内存空间用于 append】
  • SDS 在进行空间分配时,会预留一部分空间 (free) -> 空间预分配
空间预分配:
  • 如果 SDS 被修改过后的 len 值小于 1MB,将会分配 free = len 大小的预留空间;
  • 如果 SDS 被修改过后的 len 值大于 1MB,将会分配额外 1MB 的预留空间;
    • 有效地减少扩容次数
惰性空间释放:

​ 在执行 sdstrim 函数,移除 SDS 两端的指定字符串时,SDS 会将执行后的结果更新到 buf[] 中,并且更新 len 的值和 free 的值,并不会真正进行空间的释放。SDS 提供了相应 API 执行真正的空间释放。

SDS 与C 字符串对比
C 字符串SDS
获取字符串长度时间复杂度 O(N)O(1)
API 可能造成缓冲区溢出API 不会造成缓冲区溢出
修改字符串长度 N 次要进行 N 次内存分配修改字符串长度 N 次至多进行 N 次内存分配
只能保存文本数据可以保存二进制数据
可以使用全部 <string.h> 中的函数可以使用部分 <string.h> 中的函数
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值