sds是redis中一个很重要的数据结构,今天看学一下它源码的实现。

typedef char *sds;/* Note: sdshdr5 is never used, we just access the flags byte directly.* However is here to document the layout of type 5 SDS strings. */
struct __attribute__ ((__packed__)) sdshdr5 {unsigned char flags; /* 3 lsb of type, and 5 msb of string length */ //flags字段的高5bits保存字符串实际长度char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {uint8_t len; /* used */  //有效字符串长度uint8_t alloc; /* excluding the header and null terminator */ //alloc-分配的内存空间长度unsigned char flags; /* 3 lsb of type, 5 unused bits */ //头部类型char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {uint16_t len; /* used */uint16_t alloc; /* excluding the header and null terminator */unsigned char flags; /* 3 lsb of type, 5 unused bits */char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {uint32_t len; /* used */uint32_t alloc; /* excluding the header and null terminator */unsigned char flags; /* 3 lsb of type, 5 unused bits */char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {uint64_t len; /* used */uint64_t alloc; /* excluding the header and null terminator */unsigned char flags; /* 3 lsb of type, 5 unused bits */char buf[];
};#define SDS_TYPE_5  0
#define SDS_TYPE_8  1
#define SDS_TYPE_16 2
#define SDS_TYPE_32 3
#define SDS_TYPE_64 4
#define SDS_TYPE_MASK 7
#define SDS_TYPE_BITS 3
#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T)));
#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))
#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS)

__attribute__ ((__packed__))是避免内存字节对齐节省内存,上面定义了5个结构体,redis会根据字符串的长度来选择合适的结构体,每个结构体有对应数据部分和头部。下面具体学习sds 的一些API。

static inline int sdsHdrSize(char type) {//根据结构体类型返回sds头部长度(头部字节相加的结果)switch(type & SDS_TYPE_MASK) {case SDS_TYPE_5:return sizeof(struct sdshdr5);  //1 sdshdr5对应buf里面没有设定大小,应为空case SDS_TYPE_8:return sizeof(struct sdshdr8);  //3case SDS_TYPE_16:return sizeof(struct sdshdr16);  //5 case SDS_TYPE_32:return sizeof(struct sdshdr32);   //9case SDS_TYPE_64:return sizeof(struct sdshdr64);   //17}return 0;
}static inline char sdsReqType(size_t string_size) {   根据size来选取合适的结构体if (string_size < 1<<5)  //1<<5 = 32。SDS_TYPE_5 高5位存储字符串的实际长度,低3位存储typereturn SDS_TYPE_5;if (string_size < 1<<8)  //1<<8 = 256   SDS_TYPE_8用8个位存储长度return SDS_TYPE_8;if (string_size < 1<<16) //1<<16 = 65526return SDS_TYPE_16;
#if (LONG_MAX == LLONG_MAX)if (string_size < 1ll<<32)return SDS_TYPE_32;return SDS_TYPE_64;
#elsereturn SDS_TYPE_32;
#endif
}

sdsReqType中的string_size是对应字符串的长度,这里注意每种结构体能存储串长是不一样的,因为串长的位不一样。sdsHdrSize用来获取对应的type的头部长度。

sds sdsnewlen(const void *init, size_t initlen) {//根据给定的初始化字符串 init 和字符串长度 initlen,创建一个新的sdsvoid *sh;sds s;    //字符串char *char type = sdsReqType(initlen);   根据sds长度选择结构体类型,/* Empty strings are usually created in order to append. Use type 8* since type 5 is not good at this. */if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;      //SDS_TYPE_5int hdrlen = sdsHdrSize(type);  返回相应结构体头部对应的大小unsigned char *fp; /* flags pointer. */sh = s_malloc(hdrlen+initlen+1); //分配内存   initlen+2 ,利用结构体的大小分配内存if (init==SDS_NOINIT)init = NULL;else if (!init)memset(sh, 0, hdrlen+initlen+1); //初始化if (sh == NULL) return NULL;s = (char*)sh+hdrlen;  //指向第一个有效数据fp = ((unsigned char*)s)-1;  //fp指向flagswitch(type) {case SDS_TYPE_5: {*fp = type | (initlen << SDS_TYPE_BITS);//0000 0011 << 3 = 0001 1000 //高5位保存字符串的长度,低3位保存的是typebreak;}case SDS_TYPE_8: {SDS_HDR_VAR(8,s);//让sh指向整个结构体的起始地址sh->len = initlen;sh->alloc = initlen;*fp = type;break;}case SDS_TYPE_16: {SDS_HDR_VAR(16,s);sh->len = initlen;sh->alloc = initlen;*fp = type;break;}case SDS_TYPE_32: {SDS_HDR_VAR(32,s);sh->len = initlen;sh->alloc = initlen;*fp = type;break;}case SDS_TYPE_64: {SDS_HDR_VAR(64,s);sh->len = initlen;sh->alloc = initlen;*fp = type;break;}}if (initlen && init)memcpy(s, init, initlen);s[initlen] = '\0';return s;
}

sdsnewlen函数根据传入的initlen的大小会选择不同的结构体类型。

void sdsfree(sds s) {if (s == NULL) return;s_free((char*)s-sdsHdrSize(s[-1]));
}

sdsfree这里的s是指向第一个有效数据的,sdsHdrSize(s[-1]))返回其对应的头结构大小,(char*)s-sdsHdrSize(s[-1])的结果就是s指向结构体的首地址。

/* Modify an sds string in-place to make it empty (zero length).* However all the existing buffer is not discarded but set as free space* so that next append operations will not require allocations up to the* number of bytes previously available. */
void sdsclear(sds s) { //在不释放 SDS 的字符串空间的情况下,重置 SDS 所保存的字符串为空字符串。sdssetlen(s, 0);  //设置长度为0s[0] = '\0';
}/* Enlarge the free space at the end of the sds string so that the caller* is sure that after calling this function can overwrite up to addlen* bytes after the end of the string, plus one more byte for nul term.** Note: this does not change the *length* of the sds string as returned* by sdslen(), but only the free buffer space we have. *///对sds中buf的长度进行扩展,确保在函数执行之后,buf至少会有addlen+1长度的空余空间(额外的1字节是为\0准备的)
sds sdsMakeRoomFor(sds s, size_t addlen) {//addlen是需要扩展的长度 void *sh, *newsh;size_t avail = sdsavail(s); //获取s目前的空余空间长度size_t len, newlen;char type, oldtype = s[-1] & SDS_TYPE_MASK;  //取低3位,低3位存储结构体类型。oldtype表示结构体类型int hdrlen;/* Return ASAP if there is enough space left. */if (avail >= addlen) return s;//s目前的空余空间已经足够,无须再进行扩展,直接返回len = sdslen(s);//获取s目前已占用空间的长度sh = (char*)s-sdsHdrSize(oldtype);          //sh指向结构体起始地址newlen = (len+addlen);// s 最少需要的长度// 根据新长度,为 s 分配新空间所需的大小if (newlen < SDS_MAX_PREALLOC)// 如果新长度小于 SDS_MAX_PREALLOC // 那么为它分配两倍于所需长度的空间newlen *= 2;else// 否则,分配长度为目前长度加上 SDS_MAX_PREALLOCnewlen += SDS_MAX_PREALLOC;type = sdsReqType(newlen);   //type可以指明存放长度所需要的字节/* Don't use type 5: the user is appending to the string and type 5 is* not able to remember empty space, so sdsMakeRoomFor() must be called* at every appending operation. */if (type == SDS_TYPE_5) type = SDS_TYPE_8;hdrlen = sdsHdrSize(type);//hdrlen表示头部所需要的长度if (oldtype==type) {newsh = s_realloc(sh, hdrlen+newlen+1);if (newsh == NULL) return NULL;// 内存不足,分配失败,返回s = (char*)newsh+hdrlen;} else {/* Since the header size changes, need to move the string forward,* and can't use realloc */newsh = s_malloc(hdrlen+newlen+1);if (newsh == NULL) return NULL;memcpy((char*)newsh+hdrlen, s, len+1);s_free(sh);s = (char*)newsh+hdrlen;s[-1] = type;sdssetlen(s, len); //设置已经使用了空间的长度}sdssetalloc(s, newlen);//设置分配空间的长度return s;
}

注意这里扩展可都会改变结构体的类型。

/* Reallocate the sds string so that it has no free space at the end. The* contained string remains not altered, but next concatenation operations* will require a reallocation.** After the call, the passed sds string is no longer valid and all the* references must be substituted with the new pointer returned by the call. */
sds sdsRemoveFreeSpace(sds s) {//回收sds中的空闲空间,回收不会对sds中保存的字符串内容做任何修改。void *sh, *newsh;char type, oldtype = s[-1] & SDS_TYPE_MASK;  //oldtype表示结构体类型int hdrlen, oldhdrlen = sdsHdrSize(oldtype);//oldhdrlen表示结构体头部的长度size_t len = sdslen(s);  //返回sds实际保存的字符串的长度size_t avail = sdsavail(s);  //获取剩余空间的长度sh = (char*)s-oldhdrlen;/* Return ASAP if there is no space left. */if (avail == 0) return s;/* Check what would be the minimum SDS header that is just good enough to* fit this string. *///上面的oldtype是根据总长算出来的typetype = sdsReqType(len);//根据sds有效串长度 计算结构体类型hdrlen = sdsHdrSize(type);根据结构体类型决定sds头部长度/* If the type is the same, or at least a large enough type is still* required, we just realloc(), letting the allocator to do the copy* only if really needed. Otherwise if the change is huge, we manually* reallocate the string to use the different header type. */if (oldtype==type || type > SDS_TYPE_8) {//判断结构体类型是否发生改变newsh = s_realloc(sh, oldhdrlen+len+1);  //压缩空间if (newsh == NULL) return NULL;s = (char*)newsh+oldhdrlen;} else {newsh = s_malloc(hdrlen+len+1);//进行内存重分配,让 buf 的长度仅仅足够保存字符串内容if (newsh == NULL) return NULL;memcpy((char*)newsh+hdrlen, s, len+1);s_free(sh);s = (char*)newsh+hdrlen;s[-1] = type;sdssetlen(s, len);}sdssetalloc(s, len);return s;
}

这里回收空间时也有可能改变结构体的类型。

/* Increment the sds length and decrements the left free space at the* end of the string according to 'incr'. Also set the null term* in the new end of the string.** This function is used in order to fix the string length after the* user calls sdsMakeRoomFor(), writes something after the end of* the current string, and finally needs to set the new length.** Note: it is possible to use a negative increment in order to* right-trim the string.** Usage example:** Using sdsIncrLen() and sdsMakeRoomFor() it is possible to mount the* following schema, to cat bytes coming from the kernel to the end of an* sds string without copying into an intermediate buffer:** oldlen = sdslen(s);* s = sdsMakeRoomFor(s, BUFFER_SIZE);* nread = read(fd, s+oldlen, BUFFER_SIZE);* ... check for nread <= 0 and handle it ...* sdsIncrLen(s, nread);*///正数为增加,负数为缩减
void sdsIncrLen(sds s, ssize_t incr) {//根据incr参数,增加sds的长度,缩减空余空间,并将\0放到新字符串的尾端unsigned 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);  //oldlen字符串的长度assert((incr > 0 && oldlen+incr < 32) || (incr < 0 && oldlen >= (unsigned int)(-incr)));*fp = SDS_TYPE_5 | ((oldlen+incr) << SDS_TYPE_BITS);  //高5位保存长度len = oldlen+incr;  //修正len值break;}case SDS_TYPE_8: {SDS_HDR_VAR(8,s);assert((incr >= 0 && sh->alloc-sh->len >= incr) || (incr < 0 && sh->len >= (unsigned int)(-incr)));len = (sh->len += incr);break;}case SDS_TYPE_16: {SDS_HDR_VAR(16,s);assert((incr >= 0 && sh->alloc-sh->len >= incr) || (incr < 0 && sh->len >= (unsigned int)(-incr)));len = (sh->len += incr);break;}case SDS_TYPE_32: {SDS_HDR_VAR(32,s);assert((incr >= 0 && sh->alloc-sh->len >= (unsigned int)incr) || (incr < 0 && sh->len >= (unsigned int)(-incr)));len = (sh->len += incr);break;}case SDS_TYPE_64: {SDS_HDR_VAR(64,s);assert((incr >= 0 && sh->alloc-sh->len >= (uint64_t)incr) || (incr < 0 && sh->len >= (uint64_t)(-incr)));len = (sh->len += incr);break;}default: len = 0; /* Just to avoid compilation warnings. */}s[len] = '\0';
}

注意这里时对有效字符串进行操作,但是空间不能超过规定的最大值。

/* Identical sdsll2str(), but for unsigned long long type. */
int sdsull2str(char *s, unsigned long long v) {char *p, aux;size_t l;/* Generate the string representation, this method produces* an reversed string. */p = s;do {*p++ = '0'+(v%10);v /= 10;} while(v);/* Compute length and add null term. */l = p-s;*p = '\0';/* Reverse the string. */p--;while(s < p) {aux = *s;*s = *p;*p = aux;s++;p--;}return l;
}

long long 类型转换为字符串,逐个扫描,最后进行反转。

/* Like sdscatprintf() but gets va_list instead of being variadic. */
sds sdscatvprintf(sds s, const char *fmt, va_list ap) {//将ap对应的参数拼接成字符串,fmt表示匹配模式va_list cpy;char staticbuf[1024], *buf = staticbuf, *t;size_t buflen = strlen(fmt)*2;/* We try to start using a static buffer for speed.* If not possible we revert to heap allocation. */if (buflen > sizeof(staticbuf)) {buf = s_malloc(buflen);if (buf == NULL) return NULL;} else {buflen = sizeof(staticbuf);}/* Try with buffers two times bigger every time we fail to* fit the string in the current buffer size. */while(1) {buf[buflen-2] = '\0';va_copy(cpy,ap);  //复制宏vsnprintf(buf, buflen, fmt, cpy);//fmt指定输出格式的字符串,它决定了你需要提供的可变参数的类型、个数和顺序va_end(cpy);if (buf[buflen-2] != '\0') {   //防止buf大小不够一次读不完if (buf != staticbuf) s_free(buf);buflen *= 2;buf = s_malloc(buflen);if (buf == NULL) return NULL;continue;}break;}/* Finally concat the obtained string to the SDS string and return it. */t = sdscat(s, buf);if (buf != staticbuf) s_free(buf);return t;
}

sdscatvprintf类似字符串拼接功能。

/* This function is similar to sdscatprintf, but much faster as it does* not rely on sprintf() family functions implemented by the libc that* are often very slow. Moreover directly handling the sds string as* new data is concatenated provides a performance improvement.** However this function only handles an incompatible subset of printf-alike* format specifiers:** %s - C String* %S - SDS string* %i - signed int* %I - 64 bit signed integer (long long, int64_t)* %u - unsigned int* %U - 64 bit unsigned integer (unsigned long long, uint64_t)* %% - Verbatim "%" character.*/
sds sdscatfmt(sds s, char const *fmt, ...) {//格式化连接字符串size_t initlen = sdslen(s); //获取串长const char *f = fmt;long i;va_list ap;va_start(ap,fmt);f = fmt;    /* Next format specifier byte to process. */i = initlen; /* Position of the next byte to write to dest str. */while(*f) {char next, *str;size_t l;long long num;unsigned long long unum;/* Make sure there is always space for at least 1 char. */if (sdsavail(s)==0) {s = sdsMakeRoomFor(s,1);}switch(*f) {case '%':next = *(f+1);  //后移f++;   //后移switch(next) {case 's':case 'S':str = va_arg(ap,char*);  //获取对应元素printf("str =%s\n",str);l = (next == 's') ? strlen(str) : sdslen(str);if (sdsavail(s) < l) {s = sdsMakeRoomFor(s,l);}memcpy(s+i,str,l);sdsinclen(s,l); //修改串长i += l;break;case 'i':case 'I':if (next == 'i')num = va_arg(ap,int);elsenum = va_arg(ap,long long);{char buf[SDS_LLSTR_SIZE];l = sdsll2str(buf,num);if (sdsavail(s) < l) {s = sdsMakeRoomFor(s,l);}memcpy(s+i,buf,l);sdsinclen(s,l);i += l;}break;case 'u':case 'U':if (next == 'u')unum = va_arg(ap,unsigned int);elseunum = va_arg(ap,unsigned long long);{char buf[SDS_LLSTR_SIZE];l = sdsull2str(buf,unum);if (sdsavail(s) < l) {s = sdsMakeRoomFor(s,l);}memcpy(s+i,buf,l);sdsinclen(s,l);i += l;}break;default: /* Handle %% and generally %<unknown>. */s[i++] = next;sdsinclen(s,1);break;}break;default:s[i++] = *f;sdsinclen(s,1);break;}f++;}va_end(ap);/* Add null-term */s[i] = '\0';return s;
}

此函数与sdscatprintf类似,但速度要快得多。

sds sdstrim(sds s, const char *cset) {//对sds左右两端进行修剪,清除其中cset指定的所有字符,sdsstrim(xxyyabcyyxy, "xy") 将返回 "abc"char *start, *end, *sp, *ep;size_t len;sp = start = s;ep = end = s+sdslen(s)-1; //最后一个字符while(sp <= end && strchr(cset, *sp)) sp++;  //strchr一个串中查找给定字符的第一个匹配之处while(ep > sp && strchr(cset, *ep)) ep--;len = (sp > ep) ? 0 : ((ep-sp)+1);if (s != sp) memmove(s, sp, len); //memmove字节拷贝s[len] = '\0';sdssetlen(s,len);return s;
}

该算法从左边和右边都遍历一次达到两边裁剪的目的。

/* Turn the string into a smaller (or equal) string containing only the* substring specified by the 'start' and 'end' indexes.** start and end can be negative, where -1 means the last character of the* string, -2 the penultimate character, and so forth.** The interval is inclusive, so the start and end characters will be part* of the resulting string.** The string is modified in-place.** Example:** s = sdsnew("Hello World");* sdsrange(s,1,-1); => "ello World"*/
void sdsrange(sds s, ssize_t start, ssize_t end) { //按索引对截取sds字符串的其中一段,start和end都是闭区间(包含在内)size_t newlen, len = sdslen(s);if (len == 0) return;if (start < 0) {start = len+start;if (start < 0) start = 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 = len-1;newlen = (start > end) ? 0 : (end-start)+1;}} else {start = 0;}if (start && newlen) memmove(s, s+start, newlen);s[newlen] = 0;sdssetlen(s,newlen);
}

注意start和end都可能为负值,其中-1表示末尾的字符,-2表示倒数第二个字符,依次类推。

/* Split 's' with separator in 'sep'. An array* of sds strings is returned. *count will be set* by reference to the number of tokens returned.** On out of memory, zero length string, zero length* separator, NULL is returned.** Note that 'sep' is able to split a string using* a multi-character separator. For example* sdssplit("foo_-_bar","_-_"); will return two* elements "foo" and "bar".** This version of the function is binary-safe but* requires length arguments. sdssplit() is just the* same function but for zero-terminated strings.*///使用分隔符sep对s进行分割,返回一个 sds 字符串的数组。count 会被设置为返回数组元素的数量。
sds *sdssplitlen(const char *s, ssize_t len, const char *sep, int seplen, int *count) {int elements = 0, slots = 5; long start = 0, j;sds *tokens;  //字符串数组if (seplen < 1 || len < 0) return NULL;tokens = s_malloc(sizeof(sds)*slots);  //sizeof(sds) = 8if (tokens == NULL) return NULL;if (len == 0) {*count = 0;return tokens;}for (j = 0; j < (len-(seplen-1)); j++) {/* make sure there is room for the next element and the final one */if (slots < elements+2) {  //elements是下标sds *newtokens;slots *= 2;newtokens = s_realloc(tokens,sizeof(sds)*slots);//tokens空间不够重新分配内存if (newtokens == NULL) goto cleanup;tokens = newtokens;}/* search the separator */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; /* skip the separator */}}/* Add the final element. We are sure there is room in the tokens array. */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;}
}

遍历字符串并和分隔符进行比较,然后将字符串保存在字符数组中。

sds sdscatrepr(sds s, const char *p, size_t len) {//将长度为 len 的字符串p以带引号的格式,追加到给定sds的末尾s = sdscatlen(s,"\"",1);while(len--) {switch(*p) {case '\\':case '"':s = sdscatprintf(s,"\\%c",*p);break;case '\n': s = sdscatlen(s,"\\n",2); break;  //  \\表示转义case '\r': s = sdscatlen(s,"\\r",2); break;case '\t': s = sdscatlen(s,"\\t",2); break;case '\a': s = sdscatlen(s,"\\a",2); break;case '\b': s = sdscatlen(s,"\\b",2); break;default:if (isprint(*p)) //判断字符c是否为可打印字符s = sdscatprintf(s,"%c",*p);elses = sdscatprintf(s,"\\x%02x",(unsigned char)*p);break;}p++;}return sdscatlen(s,"\"",1);
}

遍历字符串,然后格式化输入。

redis源码之sds相关推荐

  1. 面试杀手锏:Redis源码之SDS

    1.前言 Hello,欢迎大家来到< Redis 数据结构源码解析系列>,在<Redis为什么这么快?>一文中我说过 ,Redis 速度快的一个原因就是其简单且高效的数据结构. ...

  2. redis源码剖析(1):基础数据结构SDS

    目录 1.SDS概述 2.SDS的定义 2.1 3.2版本和3.0版本的差别 3.SDS数据结构解读 4.SDS重点源码分析 4.1 创建SDS 4.2 SDS拼接 4.3 SDS惰性空间释放 5.总 ...

  3. Redis源码初探(1)简单动态字符串SDS

    前言 现在面试可太卷了,Redis基本是必问的知识点,为了在秋招中卷过其他人(虽然我未必参加秋招),本菜鸡决定从源码层面再次学习Redis,不过鉴于本菜鸡水平有限,且没有c语言基础,本文不会对源码过于 ...

  4. Redis源码分析(sds)

    源码版本:redis-4.0.1 源码位置:https://github.com/antirez/sds 一.SDS简介 sds (Simple Dynamic String),Simple的意思是简 ...

  5. Redis源码-String:Redis String命令、Redis String存储原理、Redis String三种编码类型、Redis字符串SDS源码解析、Redis String应用场景

    Redis源码-String:Redis String命令.Redis String存储原理.Redis String三种编码类型.Redis字符串SDS源码解析.Redis String应用场景 R ...

  6. Redis源码阅读笔记(1)——简单动态字符串sds实现原理

    首先,sds即simple dynamic string,redis实现这个的时候使用了一个技巧,并且C99将其收录为标准,即柔性数组成员(flexible array member),参考资料见这里 ...

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

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

  8. Redis源码解析——前言

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

  9. Redis源码分析:基础概念介绍与启动概述

    Redis源码分析 基于Redis-5.0.4版本,进行基础的源码分析,主要就是分析一些平常使用过程中的内容.仅作为相关内容的学习记录,有关Redis源码学习阅读比较广泛的便是<Redis设计与 ...

最新文章

  1. C# SQLiteHelper
  2. 最新单步目标检测框架,引入双向网络,精度和速度均达到不错效果
  3. SharpZipLib 提取压缩包文件并转换为NPOI中的Excel文件
  4. m5310采用芯片 中移物联_联想首发瑞芯微最新智能物联芯片产品
  5. linux 无线 网桥,基于Linux无线网桥及无线网络设备驱动分析与研究
  6. JavaScript计算两个文本框内数据的乘积(四舍五入保留两位小数)
  7. 如何用四个月搞定java?
  8. java web 通过servlet访问web_inf jsp跳转_WEB-INF下的jsp通过servlet中超链接跳转
  9. Ext.Ajax.request
  10. php 怎么查看原生方法源码_你的2020搜索账单地址入口 你的2020搜索账单怎么查看查看方法...
  11. HTML标签(持续更新)
  12. 制作nginx的spec分享
  13. C/C++遍历目录下的所有文件(Windows篇)
  14. i310100和i59400f哪个好 i3 10100和i5 9400f差距大吗
  15. window防火墙端口映射_Windows 防火墙上也有端口映射功能
  16. matlab三维曲线簇,Matlab绘制三维曲线(plot3)和三维图形(mesh surf)
  17. 什么是中药药浴?中药药浴的操作方法和注意事项
  18. 钢笔工具(贝塞尔曲线)
  19. c语言窗体编辑框框函数,请教:下面c语言是创建口的小程序,函数MessageBox(NULL,,,,MB_OK);中的4个参数各起什么作用?...
  20. java面试换背景颜色_正在修生养息,突然收到阿里(蚂蚁)面试电话,四面阿里面经总结...

热门文章

  1. 【Android 应用开发】 ActionBar 样式详解 -- 样式 主题 简介 Actionbar 的 icon logo 标题 菜单样式修改
  2. 【Android 应用开发】Android之Bluetooth编程
  3. vue java 使用AES 前后端加密解密
  4. 帝国CMS的phomenewspic/ecmsinfo标签详解
  5. ajaxToolKit中 的折叠面板用法--Accordion
  6. exec函数介绍(整理)(附带:操作系统实验一:进程控制实验 代码)
  7. 在 word 中 mathType 菜单灰色,无法使用
  8. Pycharm 导入 Python 包、模块
  9. 【leetcode】1007. Minimum Domino Rotations For Equal Row
  10. 清华出品:一文看尽AI芯片两类瓶颈三大趋势,存储技术开拓新疆界 | 附全文...