Redis原理二之数据类型String
Redis对象类型
Redis中的每个对象都由一个redisObject结构表示,该结构中和保存数据有关的三个属性分别是type属性、 encoding属性和ptr属性:
Redis使用对象来表示数据库中的键和值,每次当我们在Redis的数据库中新创建一个键值对时,我们至少会创建两个对象,一个对象用作键值对的健(键对象),另一个对象用作键值对的值(值对象)。redisObject占用了16个字节,先来看一下redisObject的存储结构:
/*
1. Redis 对象
*/
typedef struct redisObject {
/*对象的类型. 4bits*/
unsigned type;
/*具体的数据结构,embstr、sds、 4bits*/
unsigned encoding;
/* 24位,对象最后一次被命令程序访问的时间,与内存回收有关*/
/* LRU time (relative to global lru_clock) or
* LFU data (least significant 8 bits frequency
* and most significant 16 bits access time). */
unsigned lru:LRU_BITS;
/*引用计数。当refcount为0的时候,表示该对象已经不被任何对象引用,则可以进行垃圾回收了。4bytes */
int refcount;
/*指向对象实际的数据结构。8bytes*/
void *ptr;
} robj;
预分配
- 阶段一: 插入新字符串
127.0.0.1:6379> set name tom
OK
127.0.0.1:6379> strlen name
(integer) 3
127.0.0.1:6379>
这里忽略len 和 free 消耗的8字节。
2. 阶段二: 追加字符串
127.0.0.1:6379> append name jack
(integer) 7
127.0.0.1:6379> strlen name
(integer) 7
127.0.0.1:6379>
2. 阶段三: 追加相同字符串
127.0.0.1:6379> append name jack
(integer) 11
127.0.0.1:6379> strlen name
(integer) 11
127.0.0.1:6379>
string的存储方式
我们都知道,Redis是由C语言编写的。在C语言中,字符串标准形式是以空字符\0作为结束符的,但是Redis里面的字符串却没有直接沿用C语言的字符串。主要是因为C语言中获取字符串长度可以调用strlen这个标准函数,这个函数的时间复杂度是O(N),由于Redis是单线程的,承受不了这个时间复杂度。
对于不同的对象,Redis会使用不同的类型来存储。对于同一种类型type会有不同的存储形式encoding。对于string类型的字符串,其底层编码方式共有三种,分别为int、embstr和raw。
- int
当存储的字符串全是数字时,此时使用int方式来存储。如果一个字符串对象保存的是整数值,并且这个整数值可以用long类型来表示,那么字符串对象会将整数值保存在字符串对象结构的ptr属性里面(将void*转换成1ong),并将字符串对象的编码设置为int。
127.0.0.1:6379> set myint 12345
OK
127.0.0.1:6379> type myint
string
127.0.0.1:6379> object encoding myint
"int"
127.0.0.1:6379>
- embstr
如果字符串对象保存的是一个字符串值,并且这个字符申值的长度小于等于44字节,那么字符串对象将使用一个简单动态字符串(SDS)来保存这个字符串值,并将对象的编码设置为embstr。
127.0.0.1:6379> set myembstr "abcdefg"
OK
127.0.0.1:6379> type myembstr
string
127.0.0.1:6379> object encoding myembstr
"embstr"
为什么小于44字节时是embstr类型?
内存和cpu cache的访问统一都是64byte对齐的。也就是说即便你只需要读取1bit的数据,CPU还是会把64byte的数据从内存逐步扔到L1。
len(buf[]) = 64 - 16 - 1(由于字符串结尾需要一个\0占用一个字节) = 47。
而sdshdr最小需要3个字节,所以 Max(embstr) = 47 - 3 = 44 。
- raw
如果字符串对象保存的是一个字符串值,并且这个字符串值的长度大于等于44字节,那么字符串对象将使用一个简单动态字符串(SDS)来保存这个字符串值,并将对象的编码设置为raw。对于embstr和raw这两种encoding类型,其存储方式还不太一样。对于embstr类型,它将RedisObject对象头和SDS对象在内存中地址是连在一起的,但对于raw类型,二者在内存地址不是连续的。
127.0.0.1:6379> set myraw "abcdefghiljlmnopqrstuvwxyzabcdefghijklmnopqrs"
OK
127.0.0.1:6379> object encoding myraw
"raw"
127.0.0.1:6379> type myraw
string
sds 优点
- 可动态扩展内存
可以减 少追加 (append) 操作所需的内存重分配次数 - 二进制安全
sds在Redis中是实现字符串对象的工具,并且完全取代char*…sds是二进制安全的,它可以存储任意二进制数据,不像C语言字符串那样以‘\0’来标识字符串结束,
因为传统C字符串符合ASCII编码,这种编码的操作的特点就是:遇零则止 。即,当读一个字符串时,只要遇到’\0’结尾,就认为到达末尾,就忽略’\0’结尾以后的所有字符。因此,如果传统字符串保存图片,视频等二进制文件,操作文件时就被截断了。 - 快速遍历字符串
SDS表头的len成员就保存着字符串长度,所以获得字符串长度的操作复杂度为O(1)。 - 与传统的C语言字符串类型兼容
SDS表头的buf被定义为字节数组,因为判断是否到达字符串结尾的依据则是表头的len成员,这意味着它可以存放任何二进制的数据和文本数据,包括’\0’