redis源码解析(一)动态字符串sds结构体

r囧r小猫 2022-05-10 07:36 295阅读 0赞

1. 简介

  Redis是一个开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。它通常被称为数据结构服务器,因为值(value)可以是 字符串(String), 哈希(Map), 列表(list), 集合(sets) 和 有序集合(sorted sets)等类型。

  由于该库代码优美,实现巧妙,值得学习,因此从本篇开始将学习最新版本4.0.11 redis的源码实现。这一切,就从最基础的数据结构sds开始。

2. sds结构

  动态字符串sds的实现主要在sds.c和sds.h中,其中头文件主要是实现结构体和几个基本函数,源文件中定义了动态字符串所需要的函数。基本功能和C++ STL的vector动态数组类似。

  下面就来看看sds结构体吧 。sds本身只是char *的别名,源码中定义如下

  1. typedef char *sds;

  根据sds动态字符串的长度不同,定义了不同的sdshdr结构体,存储sds相应的信息以及最后的buf(使用柔性数组从而可变长)。

  1. /* 针对不同长度整形做了相应的数据结构 * Note: sdshdr5 is never used, we just access the flags byte directly. * However is here to document the layout of type 5 SDS strings. */
  2. struct __attribute__ ((__packed__)) sdshdr5 {
  3. unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
  4. char buf[];
  5. };
  6. struct __attribute__ ((__packed__)) sdshdr8 {
  7. uint8_t len; /* used */
  8. uint8_t alloc; /* excluding the header and null terminator */
  9. unsigned char flags; /* 3 lsb of type, 5 unused bits */
  10. char buf[];
  11. };
  12. struct __attribute__ ((__packed__)) sdshdr16 {
  13. uint16_t len; /* used */
  14. uint16_t alloc; /* excluding the header and null terminator */
  15. unsigned char flags; /* 3 lsb of type, 5 unused bits */
  16. char buf[];
  17. };
  18. struct __attribute__ ((__packed__)) sdshdr32 {
  19. uint32_t len; /* used */
  20. uint32_t alloc; /* excluding the header and null terminator */
  21. unsigned char flags; /* 3 lsb of type, 5 unused bits */
  22. char buf[];
  23. };
  24. struct __attribute__ ((__packed__)) sdshdr64 {
  25. uint64_t len; /* used */
  26. uint64_t alloc; /* excluding the header and null terminator */
  27. unsigned char flags; /* 3 lsb of type, 5 unused bits */
  28. char buf[];
  29. };

  我们可以看到,这里主要区别在于len和alloc采用的类型长度不同,而这两个变量分别表示了当前buf的长度,以及已经分配的内存大小。flags用于表示当前采用哪种类型的sdshdr。

3. sds基本函数

  下面来看下sds.h中给出的基本函数。首先需要了解两个宏定义,源码如下:

  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))))

  看着这两个宏定义,是不是有点眼熟呀?原理其实和container_of是一样的,详情可以参考这里。核心思想其实是结构体存储在虚拟内存中一块连续的地址空间(堆)里,通过偏移量计算,可以根据sds获取到sdshdr的首地址或者其他结构体内变量的地址,从而得到他们的值。SDS_HDR返回的就是sdshdr的首地址,而SDS_HDR_VAR则建立了一个sdshdr指针sh,并赋值给sh。

  看懂了这两个宏定义,下面的函数就很简单了,比如这里sdslen()函数,就是SDS_HDR获取结构体指针后读取len的值。

  1. /*获取sdshdr长度,原理和container_of一样*/
  2. static inline size_t sdslen(const sds s) {
  3. unsigned char flags = s[-1];
  4. switch(flags&SDS_TYPE_MASK) {
  5. case SDS_TYPE_5:
  6. return SDS_TYPE_5_LEN(flags);
  7. case SDS_TYPE_8:
  8. return SDS_HDR(8,s)->len;
  9. case SDS_TYPE_16:
  10. return SDS_HDR(16,s)->len;
  11. case SDS_TYPE_32:
  12. return SDS_HDR(32,s)->len;
  13. case SDS_TYPE_64:
  14. return SDS_HDR(64,s)->len;
  15. }
  16. return 0;
  17. }

  sdsavail()函数返回alloc-len的值,即当前剩余可用空间。

  1. /*s对应的sdshdr剩余可用空间*/
  2. static inline size_t sdsavail(const sds s) {
  3. unsigned char flags = s[-1];
  4. switch(flags&SDS_TYPE_MASK) {
  5. case SDS_TYPE_5: {
  6. return 0;
  7. }
  8. case SDS_TYPE_8: {
  9. SDS_HDR_VAR(8,s);
  10. return sh->alloc - sh->len;
  11. }
  12. case SDS_TYPE_16: {
  13. SDS_HDR_VAR(16,s);
  14. return sh->alloc - sh->len;
  15. }
  16. case SDS_TYPE_32: {
  17. SDS_HDR_VAR(32,s);
  18. return sh->alloc - sh->len;
  19. }
  20. case SDS_TYPE_64: {
  21. SDS_HDR_VAR(64,s);
  22. return sh->alloc - sh->len;
  23. }
  24. }
  25. return 0;
  26. }

  函数sdssetlen()为sdshdr设置新的长度

  1. /*设置新的长度*/
  2. static inline void sdssetlen(sds s, size_t newlen) {
  3. unsigned char flags = s[-1];
  4. switch(flags&SDS_TYPE_MASK) {
  5. case SDS_TYPE_5:
  6. {
  7. unsigned char *fp = ((unsigned char*)s)-1;
  8. *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
  9. }
  10. break;
  11. case SDS_TYPE_8:
  12. SDS_HDR(8,s)->len = newlen;
  13. break;
  14. case SDS_TYPE_16:
  15. SDS_HDR(16,s)->len = newlen;
  16. break;
  17. case SDS_TYPE_32:
  18. SDS_HDR(32,s)->len = newlen;
  19. break;
  20. case SDS_TYPE_64:
  21. SDS_HDR(64,s)->len = newlen;
  22. break;
  23. }
  24. }

  函数sdsinclen()在现有的len基础上增加inc

  1. /*已有Len的基础上增加长度*/
  2. static inline void sdsinclen(sds s, size_t inc) {
  3. unsigned char flags = s[-1];
  4. switch(flags&SDS_TYPE_MASK) {
  5. case SDS_TYPE_5:
  6. {
  7. unsigned char *fp = ((unsigned char*)s)-1;
  8. unsigned char newlen = SDS_TYPE_5_LEN(flags)+inc;
  9. *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
  10. }
  11. break;
  12. case SDS_TYPE_8:
  13. SDS_HDR(8,s)->len += inc;
  14. break;
  15. case SDS_TYPE_16:
  16. SDS_HDR(16,s)->len += inc;
  17. break;
  18. case SDS_TYPE_32:
  19. SDS_HDR(32,s)->len += inc;
  20. break;
  21. case SDS_TYPE_64:
  22. SDS_HDR(64,s)->len += inc;
  23. break;
  24. }
  25. }

  函数sdsalloc()返回sdshdr分配的空间大小,即alloc值

  1. /* alloc等于可用+现长 * sdsalloc() = sdsavail() + sdslen() */
  2. static inline size_t sdsalloc(const sds s) {
  3. unsigned char flags = s[-1];
  4. switch(flags&SDS_TYPE_MASK) {
  5. case SDS_TYPE_5:
  6. return SDS_TYPE_5_LEN(flags);
  7. case SDS_TYPE_8:
  8. return SDS_HDR(8,s)->alloc;
  9. case SDS_TYPE_16:
  10. return SDS_HDR(16,s)->alloc;
  11. case SDS_TYPE_32:
  12. return SDS_HDR(32,s)->alloc;
  13. case SDS_TYPE_64:
  14. return SDS_HDR(64,s)->alloc;
  15. }
  16. return 0;
  17. }

  函数sdssetalloc()为sdshdr设置新的alloc值

  1. /*设置alloc*/
  2. static inline void sdssetalloc(sds s, size_t newlen) {
  3. unsigned char flags = s[-1];
  4. switch(flags&SDS_TYPE_MASK) {
  5. case SDS_TYPE_5:
  6. /* Nothing to do, this type has no total allocation info. */
  7. break;
  8. case SDS_TYPE_8:
  9. SDS_HDR(8,s)->alloc = newlen;
  10. break;
  11. case SDS_TYPE_16:
  12. SDS_HDR(16,s)->alloc = newlen;
  13. break;
  14. case SDS_TYPE_32:
  15. SDS_HDR(32,s)->alloc = newlen;
  16. break;
  17. case SDS_TYPE_64:
  18. SDS_HDR(64,s)->alloc = newlen;
  19. break;
  20. }
  21. }

4. 小结

  本文介绍了sds动态字符串的结构体及最基本的几个操作函数,在下一篇文章中将进一步分析实现“动态”的功能函数。

发表评论

表情:
评论列表 (有 0 条评论,295人围观)

还没有评论,来说两句吧...

相关阅读