字符串对象是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结构处于连续的内存空间中,能更好地利用缓存带来的优势
编码转换
int编码的字符串对象和embstr编码的字符串对象在条件满足的情况下,会被转换为raw编码。
int编码的字符串对象,如果执行某个命令后会使对象值保存的不再是整数而是字符串,会将编码转换为raw。Redis中没有embstr编码的字符串的修改API(embstr是一个只读的结构),当要修改embstr编码的字符串是,总是将编码转换为raw,再执行修改命令。
简单动态字符串(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库函数。