127.0.0.1:6379> set number 100OK127.0.0.1:6379> object encoding number"int"127.0.0.1:6379> set number "100"OK127.0.0.1:6379> object encoding number"int"127.0.0.1:6379> set number abcOK127.0.0.1:6379> object encoding number"embstr"127.0.0.1:6379> set number aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaOK127.0.0.1:6379> object encoding number"raw"127.0.0.1:6379> set number 9999999999999999999999999OK127.0.0.1:6379> object encoding number"embstr"127.0.0.1:6379> set number 99999999999999999999999999999999999999999999999999999999999999OK127.0.0.1:6379> object encoding number"raw"我们用我们最常使用的字符串做了测试,观察到其编码类型随着我设置的 value 值不同而改变,我整理了如下表格来表示上面的测试结果

文章插图
当然,我是因为知道字符串的编码类型的条件,踩专门选取了这些有代表性的值进行测试,我们可以总结一个规律
- 不论是 100 还是 "100",编码类型都是 int,说明 redis 在判断是否可以用整数这个编码类型表示对象的时候,就只是看这个值是否能转换成一个整数
- 比较短的字符串 abc 被编码为 embstr,比较长的字符串 aaaaaaa..a 被编码为 raw,说明长短字符串的编码类型不一样,由此可以猜测 redis 可能是对短的字符串进行了存储上的优化策略(当然目前只是合理猜测,还有可能是对长字符串进行了某种优化)
- 整数 999...9 和更长的整数 9999999...9 也都被转换成了相应的表示字符串的类型,说明可以用整数编码类型表示的值,是有一定大小限制的
int 类型分析起来没什么意思,想想就知道肯定是能用整型存储的,尽量用整型存储,一定比字符串方式更节省空间嘛 。下面我们分析一下,长字符串和短字符串的编码类型做了区分,这是为什么呢?
不只是字符串类型,包括哈希、列表这些对象类型,都是用一个统一的结构体 redisObject 来表示的 。他的结构如下:
redis.h
typedef struct redisObject {unsigned type:4; // 对象类型unsigned encoding:4; // 编码类型void *ptr; // 值的指针...(省略一些不重要的字段)} robj;占了 4 位的 type 表示 对象类型(5 种那个),同样占了 4 位的 encoding 字段表示 编码类型(8 种那个),指针字段 ptr 表示实际值的 内存地址 。如果该对象的编码类型为整数(encoding=REDIS_ENCODING_INT),那么这个 ptr 指向的将会是一个 long 类型的变量 。
util.c
if (!string2ll(s,slen,&llval))return 0;...*lval = (long)llval;return 1;object.c...o->ptr = (void*) value;如果该对象的编码类型为 raw 或者 embstr,那么这个 ptr 指向的将会是一个 sdshdr 结构的变量sds.h
struct sdshdr {unsigned int len; // 字符串长度unsigned int free; // buf空闲数char buf[]; // 字符数组};既然都是指向同一个结构,那是怎么优化的呢?那就得进入如下两个方法具体看看了object.c
robj *createStringObject(char *ptr, size_t len) {if (len <= 39)return createEmbeddedStringObject(ptr,len);elsereturn createRawStringObject(ptr,len);}你看,这段代码非常清晰,字符串长度 <=39 时,就创建 embstr 类型的字符串对象,否则创建 raw 类型的字符串对象 。那么这两个创建方式的区别,一定就隐藏在这两个方法里,我们点进去!embstr 类型
robj *createEmbeddedStringObject(char *ptr, size_t len) {robj *o = zmalloc(sizeof(robj)+sizeof(struct sdshdr)+len+1);struct sdshdr *sh = (void*)(o+1);o->type = REDIS_STRING;o->encoding = REDIS_ENCODING_EMBSTR;o->ptr = sh+1;... (一些赋值操作)return o;}raw 类型robj *createRawStringObject(char *ptr, size_t len) {return createObject(REDIS_STRING,sdsnewlen(ptr,len));}sds sdsnewlen(const void *init, size_t initlen) {...struct sdshdr *sh = zmalloc(sizeof(struct sdshdr)+initlen+1);...}robj *createObject(int type, void *ptr) {robj *o = zmalloc(sizeof(*o));o->type = type;o->encoding = REDIS_ENCODING_RAW;o->ptr = ptr;...(一些赋值操作)return o;}对于阅读源码比较多的同学,可能立刻就察觉到了他们的区别 。其实很简单,就是 raw 类型这种方式,为 redisObject 和 sdshdr 结构分别申请了内存空间,而 embstr 只申请了一次内存空间,然后将这两个结构紧挨着放 。除此之外没有其他任何区别了 。直观图如下(图画反了,不想改了哈哈哈):
推荐阅读
- 严嵩是不是宦官,严嵩是怎么死的?严嵩是哪个朝代的?
- 面试官:数据库自增 ID 用完了会咋样?
- 鸿蒙系统官方测试:请摘掉你的有色眼镜,看官方的正确答案
- 严嵩是不是宦官,严嵩与魏忠贤是一个朝代吗
- 前端大佬问我MySQL怎么查询最近10分钟的数据?我是这么回答他的
- 刘备登基后官职最大的是谁,刘备称帝后官职排名
- 求职|2022国考面试技巧:公务员面试中求职动机如何说
- 某厂面试:如何优雅使用 SPI 机制
- 韩信是哪一部书里的人物,韩信出身寒微,曾在项羽帐下担任郎官,后在刘邦手下担任
- 应届生|跟应届生谈经验,跟有经验的人谈年龄,原来面试的“套路”真不少
