Redis — SDS API 詳解

SDS 數據結構

  • typedef char *sds;
     
    struct sdshdr {
        // 記錄buf數組中已使用字節的數量
        // 等於SDS所保存字符串的長度
        int len;
        // 記錄buf數組中未使用字節的數量
        int free;
        // 字節數組,用於保存字符串
        char buf[];
    };

SDS API

  • 函數名稱 作用 複雜度
    sdsnew 創建一個包含給定C字符串的SDS O(N),N爲給定C字符串的長度
    sdsempty 創建一個不包含任何內容的空SDS O(1)
    sdsfree 釋放給定的SDS O(N),N爲被釋放SDS的長度
    sdslen 返回SDS已經使用過的空間字符數 O(1),直接讀取SDS的len屬性來直接獲得
    sdsavail 返回SDS中未使用的空間字符數 O(1),直接讀取SDS的free屬性來直接獲得
    sdsdup 創建一個給定SDS的副本 O(N),N爲給定SDS的長度
    sdsclear 清空SDS中字符串保存的內容 因爲惰性空間釋放策略,複雜的爲O(1)
    sdscat 將C字符串拼接到SDS字符串末尾 O(N),N爲被拼接C字符串的長度
    sdscatsds 將SDS拼接到另一個SDS中 O(N),N爲被拼接SDS字符串的長度
    sdscpy 將給定的C字符串複製並覆蓋到SDS中的字符串 O(N),N爲被複制C字符串的長度
    sdsgrowzero 用空字符將SDS擴展至給定的長度 O(N),N爲擴展新增的字節數
    sdsrange SDS區間內的數據保留, 區間之外的數據覆蓋或清除 O(N),N爲被保留數據的字節數
    sdstrim 接受一個SDS和一個C字符串作爲參數, 從移除SDS中移除所有在C字符串中出現過的字符 O(N^2),N爲給定C字符串的長度
    sdscmp 對比兩個SDS字符串是否相等 O(N),N爲兩個SDS中較短的那個SDS的長度

API 實現

  • SDS 創建

    • 創建一個空的sds,len和free都爲0,buf數組也爲空

    • sds sdsempty(void) {
          return sdsnewlen("",0);
      }
    • 創建一個sds(空的或非空)

    • sds sdsnew(const char *init) {
          size_t initlen = (init == NULL) ? 0 : strlen(init);
          return sdsnewlen(init, initlen);
      }
    • 其中均會調用sdsnewlen,這個函數主要是分配內存和賦值

    • sds sdsnewlen(const void *init, size_t initlen) {
          struct sdshdr *sh;
       
          sh = malloc(sizeof(struct sdshdr)+initlen+1);
      #ifdef SDS_ABORT_ON_OOM
          if (sh == NULL) sdsOomAbort();
      #else
          if (sh == NULL) return NULL;
      #endif
          sh->len = initlen;
          sh->free = 0;
          if (initlen) {
              if (init) memcpy(sh->buf, init, initlen);
              else memset(sh->buf,0,initlen);
          }
          sh->buf[initlen] = '\0';
          return (char*)sh->buf;
      }
  • SDS 長度操作

    • 獲取 sds 的數據長度

    • size_t sdslen(const sds s) {
          struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
          return sh->len;
      }
    • 獲取 sds 的空閒大小

    • size_t sdsavail(sds s) {
          struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
          return sh->free;
      }
    • 更新 sds 的長度信息

    • void sdsupdatelen(sds s) {
          struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
          int reallen = strlen(s);
          sh->free += (sh->len-reallen);
          sh->len = reallen;
      }
  • SDS 擴容操作

    • static sds sdsMakeRoomFor(sds s, size_t addlen) {
          struct sdshdr *sh, *newsh;
          size_t free = sdsavail(s);
          size_t len, newlen;
       
          if (free >= addlen) return s;
          len = sdslen(s);
          sh = (void*) (s-(sizeof(struct sdshdr)));
          newlen = (len+addlen)*2;   //原先數據+新增數據 的兩倍
          newsh = realloc(sh, sizeof(struct sdshdr)+newlen+1);
      #ifdef SDS_ABORT_ON_OOM
          if (newsh == NULL) sdsOomAbort();
      #else
          if (newsh == NULL) return NULL;
      #endif
       
          newsh->free = newlen - len; //兩倍數據-原先數據長度,後面free再減去新增數據長度addlen,即可保證free的大小和len的最終的大小相等
          return newsh->buf;
      }
    • 函數的參數 addlen是要增加的長度,首先它會跟 free 比較,如果 addlen 小,sds 放得下,無需擴容;如果 addlen 大,則需要擴容,重新分配內存,重新計算的數組大小是原先存放的數據加上新增數據之和的兩倍(newlen),因爲一倍給 free,另外一倍存放最終的數據,目前的新 sds 中的free 大小是兩倍長度 - 原先的數據大小。
  • SDS 追加在某個SDS的後面

    • sdscat 將字符串 t 追加在具有sds結構的 s 後面,並返回一個sds結構(如果沒有擴容就是原先那個sds)
    • sds sdscat(sds s, char *t) {
          return sdscatlen(s, t, strlen(t));
      }
    • 其中用到了sdscatlen
    • sds sdscatlen(sds s, void *t, size_t len) {
          struct sdshdr *sh;
          size_t curlen = sdslen(s);
       
          s = sdsMakeRoomFor(s,len);
          if (s == NULL) return NULL;
          sh = (void*) (s-(sizeof(struct sdshdr)));
          memcpy(s+curlen, t, len);
          sh->len = curlen+len;      //最終大小爲原先數據長度加上新增的字符串長度
          sh->free = sh->free-len;   //這個len是新增數據長度,sh->free大小是兩倍長度減去原先數據長度,再減個len,即爲free字段是原先數據長度加上新增的數據長度,保證和最終的sh->len大小一致
          s[curlen+len] = '\0';
          return s;
      }
  • SDS 將字符串拷貝到SDS中,會覆蓋掉原先的數據

    • sds sdscpy(sds s, char *t) {
          return sdscpylen(s, t, strlen(t));
      }
    • sds sdscpylen(sds s, char *t, size_t len) {
          struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
          size_t totlen = sh->free+sh->len;
       
          if (totlen < len) {
              s = sdsMakeRoomFor(s,len-totlen); //此處應該是len- sh->len
              if (s == NULL) return NULL;
              sh = (void*) (s-(sizeof(struct sdshdr)));
              totlen = sh->free+sh->len; //新的sh->free爲兩倍大小除去原先的數據長度,sh->len爲原先的數據長度,現在相加,totlen值爲兩倍大小
          }
          memcpy(s, t, len);
          s[len] = '\0';
          sh->len = len;     //最終的sds中的len爲拷貝的字符串的長度
          sh->free = totlen-len;   ////最終的sds中的free也爲拷貝的字符串的長度(兩倍減去了一倍)
          return s;
      }
    • 同樣最後會保證 free 的大小和 len 的相同。
  • SDS 通過格式化輸出的形式,追加到給定的SDS後

    • sds sdscatprintf(sds s, const char *fmt, ...) {
          va_list ap;
          char *buf, *t;
          size_t buflen = 32;
       
          va_start(ap, fmt);
          while(1) {
              buf = malloc(buflen);
      #ifdef SDS_ABORT_ON_OOM
              if (buf == NULL) sdsOomAbort();
      #else
              if (buf == NULL) return NULL;
      #endif
              buf[buflen-2] = '\0';     //在倒數第二個打上標記,如果被覆蓋了說明分配的內存不夠
              vsnprintf(buf, buflen, fmt, ap);
              if (buf[buflen-2] != '\0') {
                  free(buf);
                  buflen *= 2;
                  continue;
              }
              break;
          }
          va_end(ap);
          t = sdscat(s, buf);
          free(buf);
          return t;
      }
    • 此函數是以格式化輸出的形式追加到給定的 sds 後面,首先默認分配32字節的內存,在倒數第二個字節打上結束標記,然後拷貝格式化輸出的內容,如果倒數第二個字節被覆蓋,說明分配的大小不夠,採取的策略是翻倍嘗試,最後調用前面的 sdscat 追加。
  • SDS 比較兩個SDS

    • int sdscmp(sds s1, sds s2) {
          size_t l1, l2, minlen;
          int cmp;
       
          l1 = sdslen(s1);
          l2 = sdslen(s2);
          minlen = (l1 < l2) ? l1 : l2;
          cmp = memcmp(s1,s2,minlen);
          if (cmp == 0) return l1-l2;
          return cmp;
      }
    • sds s1和s2保存的字符串長度可能不一樣,如果長度一樣的話,直接調用 memcmp 比較即可,如果不一樣的話,首先需要計算出較小的長度 minlen,調用 memcmp 比較前 minlen 個字符串,如果前 minlen 個不一樣的話,直接返回比較結果(此時已出勝負),如果前 minlen 個是一樣的,則較長的爲大。
  • SDS 對給定的字符串按給定的 sep 分隔符來切割

    • /* 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.
       */
      sds *sdssplitlen(char *s, int len, char *sep, int seplen, int *count) {
          int elements = 0, slots = 5, start = 0, j;
       
          sds *tokens = malloc(sizeof(sds)*slots);
      #ifdef SDS_ABORT_ON_OOM
          if (tokens == NULL) sdsOomAbort();
      #endif
          if (seplen < 1 || len < 0 || tokens == NULL) return NULL;
          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) {
                  slots *= 2;
                  sds *newtokens = realloc(tokens,sizeof(sds)*slots);
                  if (newtokens == NULL) {
      #ifdef SDS_ABORT_ON_OOM
                      sdsOomAbort();
      #else
                      goto cleanup;
      #endif
                  }
                  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) {
      #ifdef SDS_ABORT_ON_OOM
                      sdsOomAbort();
      #else
                      goto cleanup;
      #endif
                  }
                  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) {
      #ifdef SDS_ABORT_ON_OOM
                      sdsOomAbort();
      #else
                      goto cleanup;
      #endif
          }
          elements++;
          *count = elements;
          return tokens;
       
      #ifndef SDS_ABORT_ON_OOM
      cleanup:
          {
              int i;
              for (i = 0; i < elements; i++) sdsfree(tokens[i]);
              free(tokens);
              return NULL;
          }
      #endif
      }
    • 比如字符串 foo_-_bar_-_fun使用分割符_-_,分割符長度是3,最後得到3個(count的值)sds *結構的 tokens,tokens[0] 存放的是sds結構的 foo,tokens[1] 存放的是 sds 結構的 bar,tokens[2] 存放的是 sds 結構的 foo。
  • SDS 字符串範圍操作

    • sdsrange----截取給定的sds,[start, end]字符串。如start爲3,表示字符串中第4個字符,字符串下標從0開始,如爲-1,表示是最後一個字符,同理-2表示倒數第2個字符。
    • sds sdsrange(sds s, long start, long end) {
          struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
          size_t newlen, len = sdslen(s);
       
          if (len == 0) return s;
          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 >= (signed)len) start = len-1;
              if (end >= (signed)len) end = len-1;
              newlen = (start > end) ? 0 : (end-start)+1;
          } else {
              start = 0;
          }
          if (start != 0) memmove(sh->buf, sh->buf+start, newlen);
          sh->buf[newlen] = 0;
          sh->free = sh->free+(sh->len-newlen);
          sh->len = newlen;
          return s;
      }
    •  sdstrim---對給定sds,刪除前端/後端在給定的C字符串中出現過的字符。
    • sds sdstrim(sds s, const char *cset) {
          struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
          char *start, *end, *sp, *ep;
          size_t len;
       
          sp = start = s;
          ep = end = s+sdslen(s)-1;
          while(sp <= end && strchr(cset, *sp)) sp++;
          while(ep > start && strchr(cset, *ep)) ep--;
          len = (sp > ep) ? 0 : ((ep-sp)+1);
          if (sh->buf != sp) memmove(sh->buf, sp, len);
          sh->buf[len] = '\0';
          sh->free = sh->free+(sh->len-len);
          sh->len = len;
          return s;
      }
  • SDS 複製操作

    • 返回一個新的sds,內容與給定的 s 相同。
    • sds sdsdup(const sds s) {
          return sdsnewlen(s, sdslen(s));
      }
  • SDS 釋放操作

    • void sdsfree(sds s) {
          if (s == NULL) return;
          free(s-sizeof(struct sdshdr));
      }

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章