Redis基础系列(二)——字符串对象

  字符串对象是Reids最基本的数据类型,一个键最大能存储512MB。Redis的字符串对象是二进制安全的,可以包含任何数据,如图片数据或者序列化的对象。

字符串对象常用命令如下:

set key value  //设置指定key的值
mset key value[key value]  //同时设置多个key-value对
setex key seconds value  //设置指定key的值,并设置key的过期时间
psetex key milliseconds value  //设置指定key的值,并以毫秒方式设置key的过期时间
setnx key value  //指定key不存在时设置key的值
msetnx key value[key value]  //同时设置多个key-value对,当且仅当所有给定的key都不存在
setrange key offset value  //用value参数复写给定key所存储的字符串值,从偏移量offset开始
setbit key offset value  //对key所存储的字符串值,设置或清除指定偏移量上的位(bit)
append key value  //给指定key存储的值末尾追加value
get key  //获取指定key的值
mget key1[key2]  //获取多个给定key的值
getrange key start end  //返回key中字符串值的子字符串
getset key value  //将指定key的值设为value,并返回旧值
getbit key offset  //对key所存储的字符串值,获取指定偏移量上的位(bit)
strlen key  //返回key所储存的字符串值的长度
incr key  //将key中存储的值加一
incrby key increment  //将key中存储的值加上给定增量值
incrbyfloat key increment  //将key中存储的值加上给定浮点增量值
decr key  //将key中存储的值减一
decrby key decrement  //将key中存储的值减去给定增量值
复制代码

  字符串对象的编码有int、embstr、raw三种。如果一个字符串对象保存的是整数值,则使用int编码实现;如果一个字符串对象保存的是字符串值,且字符串值的长度小于等于39个字节,则使用embstr编码实现;如果一个字符串对象保存的是字符串值,且字符串值的长度大于39个字节,则使用raw编码实现。需要注意的是,浮点数以及不可用long表示的大整数,编码方式采用embstr或raw。

  embstr编码是raw编码的一种优化方式,其底层数据结构都是简单动态字符串(SDS)。所以,embstr编码和raw编码实现的字符串对象,都包含两部分的结构:redisObject和sdshdr。不同的是,raw编码方式会调用两次内存分配函数来分别创建redisObject结构和sdshdr结构;而embstr编码方式只调用一次内存分配函数为redisObject结构和sdshdr结构同时分配内存空间。使用embstr编码的字符串对象,相对raw编码方式有以下优点:

  • 创建对象时,内存分配函数从两次降为一次
  • 销毁对象时,内存释放函数从两次降为一次
  • embstr编码的字符串,redisObject结构和sdshdr结构处于连续的内存空间中,能更好地利用缓存带来的优势

raw编码的字符串对象.png

embstr编码的字符串对象.png

编码转换

  int编码的字符串对象和embstr编码的字符串对象在条件满足的情况下,会被转换为raw编码。

  int编码的字符串对象,如果执行某个命令后会使对象值保存的不再是整数而是字符串,会将编码转换为raw。Redis中没有embstr编码的字符串的修改API(embstr是一个只读的结构),当要修改embstr编码的字符串是,总是将编码转换为raw,再执行修改命令。

字符串命令的实现.png

简单动态字符串(SDS)

raw编码的字符串对象和embstr编码的字符串对象,对象值保存的不是C语言字符串,而是简单动态字符串结构。

//简单动态字符串结构定义
struct sdshdr {
    //记录buf数组中已使用字节的数量
    //等于SDS所保存字符串的长度
    int len;

    //记录buf数据中未使用的字节数量
    int free;

    //字节数组,用于保存字符串,以'\0'字符结尾,结尾空字符不计算在len中,以空字符结尾的好处是可以直接重用一部分C字符串函数库里的函数。
    char buf[];
}
复制代码

SDS与C语言字符串的区别

  • 常数复杂度获取字符串长度
    • 因为C字符串并不记录自身的长度信息,获取C字符串的长度必须遍历整个字符串,复杂度为O(N)。
    • SDS中使用len属性记录字符串的长度,获取字符串长度复杂度为O(1)。通过额外的空间记录字符串长度信息,确保了获取字符串长度的工作不会成为Redis的性能瓶颈。
  • 杜绝缓冲区溢出
    • 因为C字符串不记录自身的长度,在操作字符串时容易产生缓冲区溢出。SDS的空间分配策略则完全杜绝了发生缓冲区溢出的可能性:当对SDS进行修改时,会检查free的大小,并自动进行空间扩展。
  • 减少修改字符串时带来的内存重新分配次数
    • C字符串不记录自身长度,每次增长或缩短一个C字符串,程序总要对保存字符串的数组进行一次内存重新分配操作。
    • Redis作为数据库,经常被用于速度要求严苛、数据被频繁修改的场合,如果频繁重新分配内存,会对新能造成影响。SDS中,存在已使用的内存空间和未使用的空间,通过使用空间预分配和惰性空间释放两种优化策略,减少内存重新分配次数。
    • SDS空间预分配:如果对SDS进行修改之后,SDS的长度小于1M,则分配和len属性同样大小的未使用空间。如果对SDS进行修改之后,SDS的长度将大于等于1M,则分配1M的未使用空间。
    • 惰性空间释放:对SDS字符串缩短操作时,不会立即回收多出来的字节,而是把多出来的字节长度记录到free属性中,待增长字符串时使用。同时,SDS也提供相应的API释放未使用的空间,保证惰性空间释放策略不会造成内存浪费。
  • 二进制安全
    • C字符串必须符合某种编码,并且字符串中间不能存在空字符,否则最先被程序读入的空字符会被误认为是字符串结尾。
    • SDS通过len属性判断字符串结尾,保留最后一个字符为’\0’并不是用于识别字符串结尾,而是用于兼容部分C库函数。
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享