1.1 Redis字符串的特点
1. 二进制安全,字符串中间可以包含‘\0‘字符。
2. redis在字符串的末尾会添加一个‘\0‘字符,所以redis字符串与标准C的字符串兼容。
3. 根据不同的字符串长度使用不同的类型,并且取消编译过程中的优化对齐,节省空间。
4. 为了提高字符串增长的效率,redis在扩充字符串长度操作(sdsMakeRoomFor)时的预分配了额外的空间,从而再下一次扩充长度时,不用再次分配空间。注意,redis并没有在新建字符串时分配额外的空间。redis基于一个假设:如果一个字符串有了一次扩充长度的操作,那么它之后很可能也会扩充长度,因此在这时预分配空间。对于redis来说,绝大部分字符串是不需要扩充长度的,因此在创建字符串时并不预分配空间。
1 struct __attribute__ ((__packed__)) sdshdr5 { 2 unsigned char flags; /* 3 lsb of type, and 5 msb of string length */ 3 char buf[]; 4 }; 5 struct __attribute__ ((__packed__)) sdshdr8 { 6 uint8_t len; /* used */ 7 uint8_t alloc; /* excluding the header and null terminator */ 8 unsigned char flags; /* 3 lsb of type, 5 unused bits */ 9 char buf[]; 10 }; 11 struct __attribute__ ((__packed__)) sdshdr16 { 12 uint16_t len; /* used */ 13 uint16_t alloc; /* excluding the header and null terminator */ 14 unsigned char flags; /* 3 lsb of type, 5 unused bits */ 15 char buf[]; 16 }; 17 struct __attribute__ ((__packed__)) sdshdr32 { 18 uint32_t len; /* used */ 19 uint32_t alloc; /* excluding the header and null terminator */ 20 unsigned char flags; /* 3 lsb of type, 5 unused bits */ 21 char buf[]; 22 }; 23 struct __attribute__ ((__packed__)) sdshdr64 { 24 uint64_t len; /* used */ 25 uint64_t alloc; /* excluding the header and null terminator */ 26 unsigned char flags; /* 3 lsb of type, 5 unused bits */ 27 char buf[]; 28 };
__attrubte__ ((packed)) 的作用就是告诉编译器取消结构在编译过程中的优化对齐,按照实际占用字节数进行分配。
1.2 实现中使用的技巧
1.2.1 计算结构体的地址
在使用字符串的时候,我们并不会经常使用字符串结构体(例如sdshdr32),而是会使用其中的字符串量(buf),因此需要根据buf找到结构体的地址
1 #define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T))); 2 #define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T)))) 3 #define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS)
1.2.2 根据buf计算flags的地址
利用下标为负数来获取flags的地址
1 static inline void sdssetalloc(sds s, size_t newlen) { 2 unsigned char flags = s[-1]; 3 switch(flags&SDS_TYPE_MASK) { 4 case SDS_TYPE_5: 5 /* Nothing to do, this type has no total allocation info. */ 6 break; 7 case SDS_TYPE_8: 8 SDS_HDR(8,s)->alloc = newlen; 9 break; 10 case SDS_TYPE_16: 11 SDS_HDR(16,s)->alloc = newlen; 12 break; 13 case SDS_TYPE_32: 14 SDS_HDR(32,s)->alloc = newlen; 15 break; 16 case SDS_TYPE_64: 17 SDS_HDR(64,s)->alloc = newlen; 18 break; 19 } 20 }
1.2.3 存储空间预分配
1 sds sdsMakeRoomFor(sds s, size_t addlen) { 2 void *sh, *newsh; 3 size_t avail = sdsavail(s); 4 size_t len, newlen; 5 char type, oldtype = s[-1] & SDS_TYPE_MASK; 6 int hdrlen; 7 8 /* Return ASAP if there is enough space left. */ 9 if (avail >= addlen) return s; 10 11 len = sdslen(s); 12 sh = (char*)s-sdsHdrSize(oldtype); 13 newlen = (len+addlen); 14 // 如果小于SDS_MAX_PREALLOC,则分配的新长度为请求长度的2倍 15 if (newlen < SDS_MAX_PREALLOC) 16 newlen *= 2; 17 else 18 /* 否则加上SDS_MAX_PREALLOC */ 19 newlen += SDS_MAX_PREALLOC; 20 21 // 根据新长度计算新类型 22 type = sdsReqType(newlen); 23 24 /* Don‘t use type 5: the user is appending to the string and type 5 is 25 * not able to remember empty space, so sdsMakeRoomFor() must be called 26 * at every appending operation. */ 27 if (type == SDS_TYPE_5) type = SDS_TYPE_8; 28 29 hdrlen = sdsHdrSize(type); 30 if (oldtype==type) { 31 // 新旧类型相同,则以原来的为模板重新分配 32 newsh = s_realloc(sh, hdrlen+newlen+1); 33 if (newsh == NULL) return NULL; 34 s = (char*)newsh+hdrlen; 35 } else { 36 /* Since the header size changes, need to move the string forward, 37 * and can‘t use realloc */ 38 newsh = s_malloc(hdrlen+newlen+1); 39 if (newsh == NULL) return NULL; 40 memcpy((char*)newsh+hdrlen, s, len+1); 41 s_free(sh); 42 s = (char*)newsh+hdrlen; 43 s[-1] = type; 44 sdssetlen(s, len); 45 } 46 sdssetalloc(s, newlen); 47 return s; 48 }
时间: 2024-08-06 11:41:30