Redis基本数据类型以及String
Posted 猿祖
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis基本数据类型以及String相关的知识,希望对你有一定的参考价值。
前言:
Redis也有自己的数据类型,包含string,list,hash,set,sorted set。下面就对每种数据类型原理以及操作做一个详细的介绍。
Redis是面向编程的语言,除了字符串,其他类型怎么表示呢?
Redis中定义了一个对象的结构体:
/* * Redis 对象 */ typedef struct redisObject { // 类型 unsigned type:4; // 不使用(对齐位) unsigned notused:2; // 编码方式 unsigned encoding:4; // LRU 时间(相对于 server.lruclock) unsigned lru:22; // 引用计数 int refcount; // 指向对象的值 void *ptr; } robj;
type表示了该对象的对象类型,即上面五个中的一个。但为了提高存储效率与程序执行效率,每种对象的底层数据结构实现都可能不止一种。encoding就表示了对象底层所使用的编码。下面先介绍每种底层数据结构的实现,再介绍每种对象类型都用了什么底层结构并分析他们之间的关系。
Redis对象底层数据结构共八种:
编码常量 编码所对应的底层数据结构
REDIS_ENCODING_INT(long 类型的整数,如果一个字符串可以转换为龙,那么该字符串就会被转换成为long类型,对象的ptr就会指向该long,并且对象类型也用int类型表示)
REDIS_ENCODING_EMBSTR embstr (编码的简单动态字符串,长度小于39个字节的字符串)
REDIS_ENCODING_RAW (简单动态字符串)
REDIS_ENCODING_HT (字典)
REDIS_ENCODING_LINKEDLIST (双端链表)
REDIS_ENCODING_ZIPLIST (压缩列表)
REDIS_ENCODING_INTSET (整数集合)
REDIS_ENCODING_SKIPLIST (跳跃表和字典)
string
Redis的字符串也是字符序列,一个Key对应一个Value,它是Redis里面最为基础的数据存储类型。字符串类型是二进制安全(字符串不是根据某种特殊的标志来解析的,无论输入是什么,总能保证输出是处理的原始输入而不是根据某种特殊格式来处理)的,可以包含任何数据等等。
一:实现原理:
在C语言中,字符串可以用\0结尾的char数组标示。这种简单的字符串表示,在大多数情况下都能满足要求,但是不能高效的计算length和append数据。所以Redis自己实现了SDS(简单动态字符串)的抽象类型。
(1)计算字符串长度的复杂度为O(N)
(2)对字符串追加,必定要重新分配内存
sds数据结构定义如下:
typedef char *sds; struct sdshdr { // 记录buf数据中已使用的字节数目 int len; // 记录buf 剩余的字符长度 int free; // 字符数据,用于存储字符串 大小等于len+free+1,其中多余的1个字节是用来存储‘\0‘的。 char buf[]; };
从数据结构定义,可以看出Redis的sds和C语言区别就是,sds是通过buf以及len来判断字符串内容的,而不是通过“\0”来判断。
(1)计算字符串的长度复杂度为0(1)
(2)操作字符串时,内存的分配复杂度最多为O(N)
C语言中对于一个N长的字符串底层是一个N+1长的字符数组(有一个字节存放空字符)。C字符串的长度和底层数组之间的长度存在着这样的关系,因此当进行字符串的操作而导致字符串长度发生变化的时候,需要对内存进行重新分配。如果操作会增长字符串,那么在执行之前,就需要进行内存分配扩充底层数组的大小。如果是缩短字符串的操作,则需要释放额外的内存。 如果字符串的操作不是很频繁,每次修改都重新分配一下内存是可以接受的。但是Redis作为一个数据库,其读写速度,数据修改频率都被要求达到很高的效率。因此这种低效的方式并不适合Redis。
Redis采用两种方式处理内存问题:
(1) 空间预分配 这种方式用于处理字符串长度增加的问题。如果对字符串的修改使得字符串的长度增加,API首先会判断buf的空间大小是否满足,如果满足则直接操作,如果不满足,则进行如下操作: 如果对SDS进行修改之后的,SDS的长度(即len的值)小于1MB。程序将额外分配和len一样大小的未使用空间。以上面的”hello” + ” world”的操作为例。在这个例子中”hello”的len是5(不考虑’\0′),修改之后的字符串”hello world”长度为11,那么新的SDS的buf的容量就是11*2+1。其中len和free都是11,多余的1字节用来存储’\0’。 如果对SDS修改之后的长度大于1MB,那么程序会分配1MB的未使用空间(没有分配等同大小的空间,避免资源浪费)。比如原数据是5MB,修改之后需要6MB的空间,进行修改的操作后,buf的实际空间应该是7MB,其中len为6MB,free为1MB。通过该策略实现了最多分配N次。
(2)惰性空间释放 当执行字符串长度缩短的操作的时候,SDS并不直接重新分配多出来的字节,而是修改len和free的值(len相应减小,free相应增大,buf的空间大小不变化)。通过惰性空间释放,可以很好的避免缩短字符串需要的内存重分配的情况。而且多余的空间也可以为将来可能有的字符串增长的操作做优化。
(3)防止内存溢出
char a[10] = "hello";
strcat(a, " world");
strcpy(a, "hello world");
上面的三句代码,就是C语言的字符串拼接和复制的使用,但是明显出现了缓冲区溢出的问题。字符数组a的长度是10,而”hello world”字符串的长度为11,则需要12个字节的空间来存储(不要忘记了’\0’)。 Redis的SDS是怎么处理字符串修改的这种情况。当使用SDS的API对字符串进行修改的时候,API内部第一步会检测字符串的大小是否满足。如果空间已经满足要求,那么就像C语言一样操作即可。如果不满足,则拓展buf的空间,使得满足操作的需求,之后再进行操作。每次操作之后,len和free的值会做相应的修改。
总结: SDS 具有以下优点
(1) 常数复杂度获取字符串长度。
(2)杜绝缓冲区溢出。
(3)减少修改字符串长度时所需的内存重分配次数。
(4)二进制安全。
(5)兼容部分 C 字符串函数。
二、命令操作
1.SET SETEX PSETEX SETNX
SET key value [EX][PX][NX][XX]
EX second:设置键的过期时间,单位为秒。(SET key value EX second等同于SETEX key second value)。
PX millisecond:设置键的过期时间,单位为毫秒。(SET key value PX millisecond等同于PSETEX key millisecond value)。
NX:当建不存在时,才对键进行设置操作。(SET key value NX等同于SETNX key value)。
XX:只有键已经存在时,才对键进行设置操作。
(1) 对不存在的键进行设置
127.0.0.1:6379> set key "value"
OK
127.0.0.1:6379> get key
"value"
(2)对已存在的键进行设置
127.0.0.1:6379> set key "newvalue"
OK
127.0.0.1:6379> get key
"newvalue"
(3)使用EX选项
127.0.0.1:6379> set key-with-EX "hello" EX 10
OK
127.0.0.1:6379> get key-with-EX
"hello"
127.0.0.1:6379> get key-with-EX
(nil)
(4)使用PX选项
127.0.0.1:6379> set key-with-PX "hello" PX 10000
OK
127.0.0.1:6379> get key-with-PX
"hello"
127.0.0.1:6379> get key-with-PX
(nil)
(5)使用NX|XX选项
127.0.0.1:6379> set key-with-NX "hello" NX
OK
127.0.0.1:6379> set key-with-NX "hello" NX
(nil)
127.0.0.1:6379> set key-with-XX "hello"
OK
127.0.0.1:6379> del key-with-XX
(integer) 1
127.0.0.1:6379> set key-with-XX "hello" XX
(nil)
2.APPEND
APPEND key value
如果key已存在并且是一个字符串,APPEND命令将value追加到key原来的位置。
如果key不存在,APPEND简单的将给定的key设为value。
127.0.0.1:6379> APPEND test "value"
(integer) 5
127.0.0.1:6379> get test
"value"
127.0.0.1:6379> APPEND test "1"
(integer) 6
127.0.0.1:6379> get test
"value1"
3. GET
返回key所关联的字符串值 如果 key 不存在那么返回特殊值 nil 。假如 key 储存的值不是字符串类型,返回一个错误,因为 GET 只能用于处理字符串值。
4. INCR INCRBY INCRBYFLOAT DECR DECRBY
INCR将key中的数字值加1。如果KEY不存在,那么KEY的值初始化为0,然后执行INCR操作。如果类型错误,返回一个错误。
INCR key
INCRBY 将 key 所储存的值加上增量 increment。
INCRBY key increment
INCRBYFLOAT 将key 中所储存的值加上浮点数增量 increment,无论加法计算所得的浮点数的实际精度有多长, INCRBYFLOAT 的计算结果也最多只能表示小数点的后十七位
INCRBYFLOAT key increment DECR DECRBY操作与其相反。
5.MSET MSETNX
MSET key value [key value ...]
如果某个给定 key 已经存在,那么 MSET 会用新值覆盖原来的旧值,如果这不是你所希望的效果,请考虑使用 MSETNX 命令:它只会在所有给定 key 都不存在的情况下进行设置操作。MSET 是一个原子性(atomic)操作,所有给定 key 都会在同一时间内被设置,某些给定 key 被更新而另一些给定 key 没有改变的情况,不可能发生。
6.GETRANGE
GETRANGE key start end
返回 key 中字符串值的子字符串,字符串的截取范围由 start 和 end 两个偏移量决定(包括 start 和 end 在内)。 负数偏移量表示从字符串最后开始计数, -1 表示最后一个字符, -2 表示倒数第二个,以此类推。 GETRANGE 通过保证子字符串的值域(range)不超过实际字符串的值域来处理超出范围的值域请求。
注:GETRANGE不支持回绕操作
例:127.0.0.1:6379> set test2 "aaaaaaa"
OK
127.0.0.1:6379> getrange test2 -1 -5 //回绕操作
""
127.0.0.1:6379> getrange test2 -3 -1
"aaa"
7.MGET
MGET key [key ...]
返回所有(一个或多个)给定 key 的值。如果给定的 key 里面,有某个 key 不存在,那么这个 key 返回特殊值 nil 。因此,该命令永不失败
8.STRLEN
返回 key 所储存的字符串值的长度。key不存在时返回0,。当 key 储存的不是字符串值时,返回一个错误。
9.SETRANGE
SETRANGE key offset value
用 value 参数覆写(overwrite)给定 key 所储存的字符串值,从偏移量 offset 开始。不存在的 key 当作空白字符串处理。SETRANGE 命令会确保字符串足够长以便将 value 设置在指定的偏移量上,如果给定 key 原来储存的字符串长度比偏移量小(比如字符串只有 5 个字符长,但你设置的 offset 是 10 ),那么原字符和偏移量之间的空白将用零字节(zerobytes, "\x00" )来填充。 注:能使用的最大偏移量是 2^29-1(536870911) ,因为 Redis 字符串的大小被限制在 512 兆(megabytes)以内。如果你需要使用比这更大的空间,你可以使用多个 key 。
10.GETSET
GETSET key value
将给定 key 的值设为 value ,并返回 key 的旧值(old value)。当 key 存在但不是字符串类型时,返回一个错误。
以上是关于Redis基本数据类型以及String的主要内容,如果未能解决你的问题,请参考以下文章