Redis源码之数据类型解析-SDS

当前分析Redis版本为6.2,需要注意。

SDS(Simple Dynamic Strings),简单动态字符串,主要用于存储字符串和整型数据(二进制安全)。在其头文件中是这样定义typedef char *sds,SDS是字符指针类型(或者字符数组),有五种类型,应该根据字符数组长度进行分类的:

// 该类型似乎没用,在创建新sds中,检测到类型为sdshdr5,直接设置为sdshdr8
struct __attribute__ ((__packed__)) sdshdr5 {unsigned char flags; // 低三位存储类型,高五位存储长度,比较特殊char buf[];
}; // 2 bytes
// T: 8 16 32 64
// 由于这几种除了相应类型有变化,结构基本一致,这里就用T代替
// unint8(1 byte) unint16(2 bytes) unint32(4 bytes) unint64(8 bytes)
struct __attribute__ ((__packed__)) sdshdrT {uintT_t len; // 记录字符串长度uintT_t alloc; // 排除头字段和空指针后的可分配空间unsigned char flags; // 1 byte 字符串类型char buf[]; // 1 byte
};

其中__attribute__((__packed__))表示当前结构体不做对齐优化处理,实际占多少就是多少字节的紧凑型结构体,比如这里的sdshdr5在没有__packed__指定属性的情况下应该占4个字节,但实际上这里只用了2个字节,看起来只有两个字节的差距,但对于sdshdr8及以后的类型就不止如此了。结构体中的flags参数还需要说明一下,该字节的低三位存储的是指定类型,高五位除了sdshdr5中存储了当前字符串长度之外的其他sdshdr均为使用。

比较细节的就是将char buf[]放在结构体尾部,这种不定长的字符数组,放在结构体中间会对长度有极大影响,难以扩展,比如在增加字符串时,需要将后面进行备份再拓展,极其不方便。像现在这样,放在结构体尾部,我们只需要根据字符指针往前偏移固定位置即可找到相应参数,实际上也是这么实现的。

接下来,定义了五种类型的标识和一些其他参数函数:

// SDS类型 flags&SDS_TYPE_MASK
#define SDS_TYPE_5  0 // 0000 0000
#define SDS_TYPE_8  1 // 0000 0001
#define SDS_TYPE_16 2 // 0000 0010
#define SDS_TYPE_32 3 // 0000 0011
#define SDS_TYPE_64 4 // 0000 0100
#define SDS_TYPE_MASK 7 // SDS类型掩码 0000 0111
#define SDS_TYPE_BITS 3 // SDS类型位长度
// sdshdr结构体指针变量(临时?)
#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T)));
// 获取sdshdr结构体: T是结构体后缀数字(8/16/32/64), s是字符数组指针
// 做指针偏移,字符数组指针减去结构体长度即结构体指针,再进行类型指定
#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))
// 获取sdshdr5的长度: 对其flags参数右移SDS类型位长度,移除类型标志
#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS)

接下来,看下几个内联静态函数:

  • sdslen 获取sds长度,主要通过记录的长度,除去sds5比较特殊之外,其余大体相似。

    static inline size_t sdslen(const sds s) {unsigned char flags = s[-1]; // 获取flags字段,就在字符串(sds)的前一字节switch(flags&SDS_TYPE_MASK) {case SDS_TYPE_5:return SDS_TYPE_5_LEN(flags);case SDS_TYPE_T: // T 8 16 32 64// 获取到对应sdshdr结构体,读取len字段,记录的是字符串长度return SDS_HDR(T,s)->len;//...}return 0;
    }
    
  • sdsavail 获取sds可用长度。

    static inline size_t sdsavail(const sds s) {unsigned char flags = s[-1];switch(flags&SDS_TYPE_MASK) {case SDS_TYPE_5: {return 0; // sdshdr5不支持扩充}case SDS_TYPE_T: { // T 8 16 32 64SDS_HDR_VAR(T,s); // 赋值临时指针变量shreturn sh->alloc - sh->len;}// ...}return 0;
    }
    
  • sdssetlen 设置sds长度,应该是初始化时使用,直接设置。

    static inline void sdssetlen(sds s, size_t newlen) {unsigned char flags = s[-1];switch(flags&SDS_TYPE_MASK) {case SDS_TYPE_5:{unsigned char *fp = ((unsigned char*)s)-1; // 获取flags指针// 左移SDS_TYPE_BITS位(增加类型位),然后和SDS_TYPE_5进行按位或运算*fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS); }break;case SDS_TYPE_T: // T 8 16 32 64SDS_HDR(T,s)->len = newlen; // 找到sds结构体len字段,直接赋值break;// ...}
    }
    
  • sdsinclen 增加sds长度,inc应该是increase。

    static inline void sdsinclen(sds s, size_t inc) { // 同设置长度相似unsigned char flags = s[-1];switch(flags&SDS_TYPE_MASK) {case SDS_TYPE_5:{unsigned char *fp = ((unsigned char*)s)-1;unsigned char newlen = SDS_TYPE_5_LEN(flags)+inc;*fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);}break;case SDS_TYPE_T: // T 8 16 32 64SDS_HDR(8,s)->len += inc;break;}
    }
    
  • sdsalloc 获取sds可分配空间,也就是sdsavail+sdslen

    static inline size_t sdsalloc(const sds s) {unsigned char flags = s[-1];switch(flags&SDS_TYPE_MASK) {case SDS_TYPE_5:return SDS_TYPE_5_LEN(flags);case SDS_TYPE_T: // T 8 16 32 64return SDS_HDR(T,s)->alloc;}return 0;
    }
    
  • sdssetalloc 设置可分配空间。

    static inline void sdssetalloc(sds s, size_t newlen) {unsigned char flags = s[-1];switch(flags&SDS_TYPE_MASK) {case SDS_TYPE_5:// 该类型没有可分配信息break;case SDS_TYPE_T: // T 8 16 32 64SDS_HDR(T,s)->alloc = newlen;break;}
    }
    

    以上是头文件(src/sds.h)的内联静态函数,再看看源文件(src/sds.c)的内联静态函数,还有三个,一并放在这里。

  • sdsHdrSize 获取sds的头字段长度,也就是对应结构体的长度。

    static inline int sdsHdrSize(char type) {switch(type&SDS_TYPE_MASK) {case SDS_TYPE_5:return sizeof(struct sdshdr5);case SDS_TYPE_T: // T 8 16 32 64return sizeof(struct sdshdrT);}return 0;
    }
    
  • sdsReqType 获取sds需要的类型,根据字符串长度。

    static inline char sdsReqType(size_t string_size) {if (string_size < 1<<5) // 2^5return SDS_TYPE_5;if (string_size < 1<<8) // 2^8return SDS_TYPE_8;if (string_size < 1<<16) // 2^16return SDS_TYPE_16;
    #if (LONG_MAX == LLONG_MAX) // 如果长精度与双长精度的最大值相等if (string_size < 1ll<<32) // 2^32return SDS_TYPE_32;return SDS_TYPE_64;
    #else // 不等的情况,否决了64的可能性?直接采用32return SDS_TYPE_32;
    #endif
    }
    
  • sdsTypeMaxSize 获取sds类型对应字符串的最大长度。

    static inline size_t sdsTypeMaxSize(char type) {// 留一个字符结束符if (type == SDS_TYPE_5)return (1<<5) - 1;if (type == SDS_TYPE_8)return (1<<8) - 1;if (type == SDS_TYPE_16)return (1<<16) - 1;
    #if (LONG_MAX == LLONG_MAX)if (type == SDS_TYPE_32)return (1ll<<32) - 1;
    #endif// 和 SDS_TYPE_64/SDS_TYPE_32最大值相等的return -1;
    }
    

也不多,内联静态函数,主要是一些工具性质的。接下来,看下,sds有关的常规函数吧。

  • _sdsnewlen 创建sds新字符串。

    // init 初始化字符串指针,可以为空
    // initlen 初始化长度
    // trymalloc 是否尝试分配空间
    sds _sdsnewlen(const void *init, size_t initlen, int trymalloc) {void *sh;sds s;char type = sdsReqType(initlen); // 根据初始化长度确定sds类型// 创建空字符串一般都是为了追加,所以采用sdshdr8。而不是sdshdr5。强行更正类型。if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;int hdrlen = sdsHdrSize(type);unsigned char *fp; // flags标识指针size_t usable; // 可用大小assert(initlen + hdrlen + 1 > initlen); // 防止溢出// 分配空间sh = trymalloc?s_trymalloc_usable(hdrlen+initlen+1, &usable) :s_malloc_usable(hdrlen+initlen+1, &usable);if (sh == NULL) return NULL;if (init==SDS_NOINIT) // const char *SDS_NOINIT = "SDS_NOINIT";init = NULL; // 重置为空else if (!init)memset(sh, 0, hdrlen+initlen+1); // 重置空间,防止脏数据干扰s = (char*)sh+hdrlen; // sdshdr结构偏移hdrlen,到字符串指针位置fp = ((unsigned char*)s)-1; // 获取flags指针位置usable = usable-hdrlen-1; // 可分配空间。。。分配的大小除开sds头长度和结束符if (usable > sdsTypeMaxSize(type)) // 标准化对齐限制。对应类型最大可用大小,该多少还是多少usable = sdsTypeMaxSize(type);switch(type) {case SDS_TYPE_5: {*fp = type | (initlen << SDS_TYPE_BITS);break;}case SDS_TYPE_8: {SDS_HDR_VAR(8,s);sh->len = initlen;sh->alloc = usable;*fp = type;break;}case SDS_TYPE_16: {SDS_HDR_VAR(16,s);sh->len = initlen;sh->alloc = usable;*fp = type;break;}case SDS_TYPE_32: {SDS_HDR_VAR(32,s);sh->len = initlen;sh->alloc = usable;*fp = type;break;}case SDS_TYPE_64: {SDS_HDR_VAR(64,s);sh->len = initlen;sh->alloc = usable;*fp = type;break;}}if (initlen && init)memcpy(s, init, initlen); // 拷贝字符串 inits[initlen] = '\0'; // 补充结束符return s; // 返回sds
    }
    
  • sdsnewlen 创建指定长度sds新字符串,直接调用 _sdsnewlen 即可实现,trymalloc为0。

  • sdstrynewlen 尝试创建指定长度sds新字符串,和 sdsnewlen 基本一样,调用 _sdsnewlen 第三个参数为1。

  • sdsempty 创建空字符串,调用 sdsnewlen("",0) 即可。

  • sdsnew 从一个以null结束的C字符串创建sds字符串。

    sds sdsnew(const char *init) {size_t initlen = (init == NULL) ? 0 : strlen(init); // 计算初始化长度return sdsnewlen(init, initlen);
    }
    
  • sdsdup 复制sds字符串,调用 sdsnewlen(s, sdslen(s)) 实现。

  • sdsfree 释放sds字符串,直接调用 s_free 对sds对应的sdshdr结构体处理。

  • sdsupdatelen 更新sds字符串长度,将len设置为字符串的真实长度。

  • sdsclear 将sds字符串置空,直接将len设置为0,并将第一个字符设置为结束符。

  • sdsMakeRoomFor 扩充sds长度,不会改变sdshdr->len,只是增加了addlen长度的空间。

    sds sdsMakeRoomFor(sds s, size_t addlen) { // addlen 需要扩充的长度void *sh, *newsh;size_t avail = sdsavail(s); // 获取sds可用长度size_t len, newlen;char type, oldtype = s[-1] & SDS_TYPE_MASK;int hdrlen;size_t usable;// 如果可用长度大于等于扩充长度,立即返回sds即可if (avail >= addlen) return s;len = sdslen(s);sh = (char*)s-sdsHdrSize(oldtype);newlen = (len+addlen); // 新的sds长度assert(newlen > len); // 防止溢出// #define SDS_MAX_PREALLOC (1024*1024) sds最大预分配大小,在sds.h中定义// 新长度小于SDS_MAX_PREALLOC,将新长度扩展成自身两倍,防止频繁分配。// 如果大于SDS_MAX_PREALLOC,直接加上SDS_MAX_PREALLOC。if (newlen < SDS_MAX_PREALLOC)newlen *= 2;elsenewlen += SDS_MAX_PREALLOC;type = sdsReqType(newlen); // 根据新计算长度确定sds类型// 该操作是扩充字符串,sdshdr5不支持更多初始空间,所以每次追加操作都会调用sdsMakeRoomFor// 确实废。所以,和_sdsnewlen一样,直接设置为sdshdr8类型。if (type == SDS_TYPE_5) type = SDS_TYPE_8;hdrlen = sdsHdrSize(type);assert(hdrlen + newlen + 1 > len); // 防止溢出if (oldtype==type) { // 如果类型相等,直接重分配newsh = s_realloc_usable(sh, hdrlen+newlen+1, &usable);if (newsh == NULL) return NULL;s = (char*)newsh+hdrlen; // 直接更新s指针} else {// 一旦sdshdr头发生变化,那么需要移除字符串前面的,不能使用reallocnewsh = s_malloc_usable(hdrlen+newlen+1, &usable);if (newsh == NULL) return NULL;memcpy((char*)newsh+hdrlen, s, len+1); //拷贝字符串ss_free(sh); // 释放原串s = (char*)newsh+hdrlen; // 重订s指针s[-1] = type;sdssetlen(s, len); // 设置长度}// 处理可分配空间大小usable = usable-hdrlen-1;if (usable > sdsTypeMaxSize(type))usable = sdsTypeMaxSize(type);sdssetalloc(s, usable);return s;
    }
    
  • sdsRemoveFreeSpacesdsMakeRoomFor 相反操作,瘦身。

    sds sdsRemoveFreeSpace(sds s) {void *sh, *newsh;char type, oldtype = s[-1] & SDS_TYPE_MASK;int hdrlen, oldhdrlen = sdsHdrSize(oldtype);size_t len = sdslen(s);size_t avail = sdsavail(s);sh = (char*)s-oldhdrlen;// 如果没有可用长度,直接返回即可,说明不需要瘦身if (avail == 0) return s;// 根据长度确定最小sds头,更好适配字符串type = sdsReqType(len);hdrlen = sdsHdrSize(type);if (oldtype==type || type > SDS_TYPE_8) { // 类型不变,或类型大于sdshdr8newsh = s_realloc(sh, oldhdrlen+len+1); // 重新分配空间if (newsh == NULL) return NULL;s = (char*)newsh+oldhdrlen;} else {newsh = s_malloc(hdrlen+len+1); // 分配新空间if (newsh == NULL) return NULL;memcpy((char*)newsh+hdrlen, s, len+1); // 拷贝s字符串s_free(sh); // 释放原串s = (char*)newsh+hdrlen; // 重新定位sdss[-1] = type;sdssetlen(s, len);}sdssetalloc(s, len); // 设置可分配空间大小为字符串真实长度,适配即可return s;
    }
    
  • sdsAllocSize 获取总可分配空间,包括头大小,可分配空间大小和一个字符结束符。

  • sdsAllocPtr 获取sds实际可分配指针,也就是对应的sdshdr。

  • sdsIncrLen sds增加长度,长度可以为负(表示向右截断)。

    void sdsIncrLen(sds s, ssize_t incr) { // incr 可以小于0unsigned char flags = s[-1];size_t len; // 更新后的长度switch(flags&SDS_TYPE_MASK) {case SDS_TYPE_5: {unsigned char *fp = ((unsigned char*)s)-1;unsigned char oldlen = SDS_TYPE_5_LEN(flags);assert((incr > 0 && oldlen+incr < 32) || (incr < 0 && oldlen >= (unsigned int)(-incr)));*fp = SDS_TYPE_5 | ((oldlen+incr) << SDS_TYPE_BITS);len = oldlen+incr;break;}case SDS_TYPE_T: { // T 8 16 32 64SDS_HDR_VAR(T,s);assert((incr >= 0 && sh->alloc-sh->len >= incr) || (incr < 0 && sh->len >= (unsigned int)(-incr)));len = (sh->len += incr);break;}default: len = 0; // 避免汇编警告}s[len] = '\0'; // 补充结束符
    }
    
  • sdsgrowzero 以0扩充字符串,指定长度len。

    sds sdsgrowzero(sds s, size_t len) {size_t curlen = sdslen(s); // 获取当前字符串长度if (len <= curlen) return s; // 指定长度过小,直接返回即可,不需要填充s = sdsMakeRoomFor(s,len-curlen); // 反之,需要扩充,先扩充空间if (s == NULL) return NULL;// 以0填充。初始化,防止脏数据干扰memset(s+curlen,0,(len-curlen+1));sdssetlen(s, len);return s;
    }
    
  • sdscatlen 追加指定长度len的二进制安全C字符串t到sds字符串。计算原长度,调用 sdsMakeRoomFor 扩展空间,然后拷贝字符串t,设置sds的len,最后补充结束符。

  • sdscat 追加C字符串(以null结束的),相当于对 sdscatlen 的包装。

  • sdscatsds 追加sds字符串,和 sdscat 相似。

  • sdscpylen 覆盖操作,将指定长度len的二进制安全字符串t覆盖sds字符串。

    sds sdscpylen(sds s, const char *t, size_t len) {if (sdsalloc(s) < len) { // 空间不够,需要进行扩容,如果s = sdsMakeRoomFor(s,len-sdslen(s));if (s == NULL) return NULL;}memcpy(s, t, len); // 直接拷贝s[len] = '\0';sdssetlen(s, len);return s;
    }
    
  • sdscpy 覆盖拷贝,以null结束的字符串到sds字符串,直接调用 sdscpylen

  • sdsll2str 双长整型转字符串,s是已分配好内存的地址。

    int sdsll2str(char *s, long long value) {char *p, aux; // aux 临时中间交换变量unsigned long long v;size_t l;// 生成字符串表示,这个方法会对字符串反转v = (value < 0) ? -value : value; // 转为非负数p = s; // 指向同一块空间do {// 按低位到高位顺序// '0' ASCII 48*p++ = '0'+(v%10); // *p = val & p++v /= 10;} while(v);if (value < 0) *p++ = '-'; // 判断符号l = p-s; // 计算长度*p = '\0'; // 补充结束符// 所以,再次反转表示p--;while(s < p) { // s向后 p向前aux = *s;*s = *p;*p = aux;s++;p--;}return l;
    }
    
  • sdsull2str 无符号双长整型转字符串,和 sdsll2str 大体相似。

  • sdsfromlonglong 从双长整型转为sds字符串。

    sds sdsfromlonglong(long long value) {char buf[SDS_LLSTR_SIZE]; // #define SDS_LLSTR_SIZE 21int len = sdsll2str(buf,value);return sdsnewlen(buf,len);
    }
    
  • sdscatvprintf

  • sdscatprintf 将任意数量字符串追加到指定sds。

  • sdscatfmtsdscatprintf 相似。

  • sdstrim 清除sds两端所有指定字符。

    sds sdstrim(sds s, const char *cset) {char *start, *end, *sp, *ep;size_t len;sp = start = s; // 定位到字符串头ep = end = s+sdslen(s)-1; // 定位到字符串尾// 从头开始遍历,只要当前sp指向的字符,在cset中,指针就继续后移while(sp <= end && strchr(cset, *sp)) sp++;// 从尾部朝前遍历,只要当前ep指向的字符,在cset中,指针就继续前移while(ep > sp && strchr(cset, *ep)) ep--;// 到这的时候,sp和ep所指向的字符,在cset中不存在,但是sp和ep之间的不管len = (sp > ep) ? 0 : ((ep-sp)+1); // 计算剩余长度if (s != sp) memmove(s, sp, len); // 如果有需要,前移字符串内容s[len] = '\0'; // 补充结束符sdssetlen(s,len); // 更新 sds->lenreturn s;
    }
    
  • sdsrange 截取从start到end索引的子串,闭区间。

    void sdsrange(sds s, ssize_t start, ssize_t end) {size_t newlen, len = sdslen(s);if (len == 0) return; // 空串,不做处理if (start < 0) { // 小于0,反向截取start = len+start;if (start < 0) start = 0; // 如果start还小于0,说明负值过大,默认从0开始}if (end < 0) { // 结束索引也一样end = len+end;if (end < 0) end = 0;}// 计算新长度newlen = (start > end) ? 0 : (end-start)+1;if (newlen != 0) {if (start >= (ssize_t)len) {newlen = 0;} else if (end >= (ssize_t)len) { // 当end大于字符串长度时,截取到尾部end = len-1;newlen = (start > end) ? 0 : (end-start)+1;}}if (start && newlen) memmove(s, s+start, newlen);s[newlen] = 0; // 补充结束符sdssetlen(s,newlen); // 更新长度
    }
    
  • sdstolower 将sds字符串转为小写,循环调用 tolower 即可。

  • sdstoupper 将sds字符串转为大写,循环调用 toupper 即可。

  • sdscmp 比较两个sds字符串,s1大返回正数,s2大返回负数,相等就为0。

  • sdssplitlen 使用指定分割字符或字符串对sds字符进行分割,返回分割后的sds字符串数组。

    sds *sdssplitlen(const char *s, ssize_t len, const char *sep, int seplen, int *count) { // count 分割后的数组数量// elements 返回字符串数组的元素个数// slots 用于申请返回字符串数组的容量int elements = 0, slots = 5;long start = 0, j; // 标记分割位置sds *tokens; // 存储分割好的sds字符串数组// 分割字符串长度或目标sds字符串长度为0,不进行处理,直接返回nullif (seplen < 1 || len < 0) return NULL;tokens = s_malloc(sizeof(sds)*slots); // 申请slots个sds空间if (tokens == NULL) return NULL;if (len == 0) {*count = 0;return tokens;}for (j = 0; j < (len-(seplen-1)); j++) {// 如果数组元素不太够用,直接重新分配两倍if (slots < elements+2) {sds *newtokens;slots *= 2;newtokens = s_realloc(tokens,sizeof(sds)*slots);if (newtokens == NULL) goto cleanup;tokens = newtokens;}// 分隔符只有一个字符,直接判断// 分隔符是多个字符组成,调用memcmp进行判断if ((seplen == 1 && *(s+j) == sep[0]) || (memcmp(s+j,sep,seplen) == 0)) {tokens[elements] = sdsnewlen(s+start,j-start); // 存放分割好的字符串if (tokens[elements] == NULL) goto cleanup;elements++;start = j+seplen; // 重新确定开始比较位置j = j+seplen-1; // 跳过分割符长度}}// 添加最后一个元素tokens[elements] = sdsnewlen(s+start,len-start);if (tokens[elements] == NULL) goto cleanup;elements++;*count = elements;return tokens;cleanup:{int i;for (i = 0; i < elements; i++) sdsfree(tokens[i]);s_free(tokens);*count = 0;return NULL;}
    }
    
  • sdsfreesplitres 释放由 sdssplitlen 分割产生的数组。

  • sdscatrepr 将字符串转义后追加到sds字符串。

  • is_hex_digit 判断字符是否为十六进制。

  • hex_digit_to_int 将十六进制转化成整型。

  • sdssplitargs 从字符串中切割参数?。

  • sdsmapchars 替换字符串,从from到to。

  • sdsjoin 将C字符串数组以分隔符sep粘贴到一起。

  • sdsjoinsds 粘贴sds字符串。

终于结束了,SDS类型,但是其中涉及到有关内存分配的函数暂未分析,那个需要独立分析。

深入解读Redis之数据类型解析-SDS相关推荐

  1. NoSQL概述-Redis安装-常用五大数据类型-概述Bitmaps,HyperLogLog,Geospatial和redis.conf 基本解析

    NoSQL 概述 NoSQL(NoSQL=Not Only SQL),意即"不仅仅是SQL",泛指 非关系型的数据库. NoSQL不依赖业务逻辑方式存储,而以简单的key-valu ...

  2. Redis基本数据类型String——数据结构解析

    String Redis没有直接使用C语言的传统的字符串表示,而是自己构建了一种名为简单动态字符串(simple dynamic string,SDS)的抽象类型. 下面我将解释为什么Redis要自己 ...

  3. Redis基础——数据类型详解

    命令参考:http://doc.redisfans.com/ 简介 Redis 是完全开源的,遵守 BSD 协议,是一个高性能的 key-value 数据库. Redis 与其他 key - valu ...

  4. Redis源码解析——双向链表

    相对于之前介绍的字典和SDS字符串库,Redis的双向链表库则是非常标准的.教科书般简单的库.但是作为Redis源码的一部分,我决定还是要讲一讲的.(转载请指明出于breaksoftware的csdn ...

  5. Redis源码解析——前言

    今天开启Redis源码的阅读之旅.对于一些没有接触过开源代码分析的同学来说,可能这是一件很麻烦的事.但是我总觉得做一件事,不管有多大多难,我们首先要在战略上蔑视它,但是要在战术上重视它.除了一些高大上 ...

  6. Redis基本数据类型、持久化机制、集群模式、淘汰策略、缓存穿透、击穿、雪崩、常见面试题大集合!

    redis redis reids的常用数据类型 1.String 2.Hash 3.List 5.Sorted Set(ZSet) 6.其他 发布(pub)订阅模式(sub)模式 作用 Redis的 ...

  7. 【Redis系列2】Redis字符串对象之SDS(简单动态字符串)实现原理分析

    Redis字符串对象之SDS实现原理分析 前言 字符串对象 为什么Redis的字符串对象是二进制安全的 SDS空间分配策略 空间预分配 惰性空间释放 SDS和C语言字符串区别 SDS的底层存储对象 d ...

  8. 【Redis】数据类型的详解与使用场景【原创】

    文章目录 Redis数据类型的详解与使用场景 1-1 NoSQL的概述 1. 概述 2. 为什么需要NoSQL 3. NoSQL产品 4. 分类 5. 特点 2-1 Redis的概述 1. 概述 2. ...

  9. Redis源码解析——字典基本操作

    有了<Redis源码解析--字典结构>的基础,我们便可以对dict的实现进行展开分析.(转载请指明出于breaksoftware的csdn博客) 创建字典 一般字典创建时,都是没有数据的, ...

  10. Redis源码解析——内存管理

    在<Redis源码解析--源码工程结构>一文中,我们介绍了Redis可能会根据环境或用户指定选择不同的内存管理库.在linux系统中,Redis默认使用jemalloc库.当然用户可以指定 ...

最新文章

  1. 腾讯推出的这款高性能 RPC 开发框架,确定不了解下吗?
  2. 最先进的AI还不如动物聪明?首届AI-动物奥运会英国开赛!
  3. MFRC522开发笔记
  4. java 运行环境注册表_Java运行环境与Windows注册表
  5. 三百英雄服务器维护2020,酸败英雄梦服维护公告《300英雄梦服》将定于2020年8月3...
  6. Git笔记(3) 安装配置
  7. cp和scp复制命令
  8. 内网通不用软件改积分_软件项目为什么不能够如期交付?
  9. JS控制文字只显示两行,超出部分显示省略号
  10. 仓库设置ower权限_中小企业都在用的免费多仓库管理软件
  11. 根据ip地址制作html,根据ip掩码计算可用ip
  12. ubuntu 18.04 更新显卡驱动
  13. LVDS信号与TTL信号
  14. windows 10 安装 db2 v11.1(血淋淋的教训)
  15. dubbo中 provider和 comsumer端timeout的设置区别
  16. mapping文件的编写
  17. 微积分:如何理解方向导数与梯度?
  18. 利用BARK和Telebot进行VPS实时预警推送
  19. 三面阿里,有惊无险成功拿到offer定级P7,只能说是真的难
  20. Python多线程爬取7160网站美女图片

热门文章

  1. android 小米pad 调试,小米平板2 开启USB调试模式
  2. 信息系统项目管理师——历年论文题目2012年-2020年
  3. 【npm】伙计,给我来一杯package.json!不加糖
  4. 20155322 2016-2017-2 《Java程序设计》第7周学习总结
  5. Android 垃圾分类APP(三)垃圾分类之语音输入
  6. 微信小游戏教程(三) 新手教程
  7. 大型门户网站架构分析
  8. 个人博客或网站快速被搜索引擎收录
  9. 趣味编程:有A,B,C,D,E五人,每人额头上都帖了一张黑或白的纸
  10. 2018-01-05-医药行业的IT革命探讨