亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 數據庫 > Redis > 正文

Redis中的動態字符串學習教程

2020-03-17 12:41:47
字體:
來源:轉載
供稿:網友
這篇文章主要介紹了Redis中的動態字符串學習教程,以sds模塊的使用為主進行講解,需要的朋友可以參考下
 

sds 的用途
Sds 在 Redis 中的主要作用有以下兩個:

實現字符串對象(StringObject);
在 Redis 程序內部用作 char* 類型的替代品;
以下兩個小節分別對這兩種用途進行介紹。

實現字符串對象

Redis 是一個鍵值對數據庫(key-value DB), 數據庫的值可以是字符串、集合、列表等多種類型的對象, 而數據庫的鍵則總是字符串對象。

對于那些包含字符串值的字符串對象來說, 每個字符串對象都包含一個 sds 值。

“包含字符串值的字符串對象”,這種說法初聽上去可能會有點奇怪, 但是在 Redis 中, 一個字符串對象除了可以保存字符串值之外, 還可以保存 long 類型的值, 所以為了嚴謹起見, 這里需要強調一下: 當字符串對象保存的是字符串時, 它包含的才是 sds 值, 否則的話, 它就是一個 long 類型的值。
舉個例子, 以下命令創建了一個新的數據庫鍵值對, 這個鍵值對的鍵和值都是字符串對象, 它們都包含一個 sds 值:

redis> SET book "Mastering C++ in 21 days"OKredis> GET book"Mastering C++ in 21 days"

以下命令創建了另一個鍵值對, 它的鍵是字符串對象, 而值則是一個集合對象:

redis> SADD nosql "Redis" "MongoDB" "Neo4j"(integer) 3redis> SMEMBERS nosql1) "Neo4j"2) "Redis"3) "MongoDB"

用 sds 取代 C 默認的 char* 類型

因為 char* 類型的功能單一, 抽象層次低, 并且不能高效地支持一些 Redis 常用的操作(比如追加操作和長度計算操作), 所以在 Redis 程序內部, 絕大部分情況下都會使用 sds 而不是 char* 來表示字符串。

性能問題在稍后介紹 sds 定義的時候就會說到, 因為我們還沒有了解過 Redis 的其他功能模塊, 所以也沒辦法詳細地舉例說那里用到了 sds , 不過在后面的章節中, 我們會經常看到其他模塊(幾乎每一個)都用到了 sds 類型值。

目前來說, 只要記住這個事實即可: 在 Redis 中, 客戶端傳入服務器的協議內容、 aof 緩存、 返回給客戶端的回復, 等等, 這些重要的內容都是由 sds 類型來保存的。

redis 中的字符串
在 C 語言中,字符串可以用一個 /0 結尾的 char 數組來表示。

比如說, hello world 在 C 語言中就可以表示為 "hello world/0" 。

這種簡單的字符串表示,在大多數情況下都能滿足要求,但是,它并不能高效地支持長度計算和追加(append)這兩種操作:

每次計算字符串長度(strlen(s))的復雜度為 θ(N) 。
對字符串進行 N 次追加,必定需要對字符串進行 N 次內存重分配(realloc)。
在 Redis 內部, 字符串的追加和長度計算很常見, 而 APPEND 和 STRLEN 更是這兩種操作,在 Redis 命令中的直接映射, 這兩個簡單的操作不應該成為性能的瓶頸。

另外, Redis 除了處理 C 字符串之外, 還需要處理單純的字節數組, 以及服務器協議等內容, 所以為了方便起見, Redis 的字符串表示還應該是二進制安全的: 程序不應對字符串里面保存的數據做任何假設, 數據可以是以 /0 結尾的 C 字符串, 也可以是單純的字節數組, 或者其他格式的數據。

考慮到這兩個原因, Redis 使用 sds 類型替換了 C 語言的默認字符串表示: sds 既可高效地實現追加和長度計算, 同時是二進制安全的。

sds 的實現

在前面的內容中, 我們一直將 sds 作為一種抽象數據結構來說明, 實際上, 它的實現由以下兩部分組成:

typedef char *sds;struct sdshdr {  // buf 已占用長度  int len;  // buf 剩余可用長度  int free;  // 實際保存字符串數據的地方  char buf[];};

其中,類型 sds 是 char * 的別名(alias),而結構 sdshdr 則保存了 len 、 free 和 buf 三個屬性。

作為例子,以下是新創建的,同樣保存 hello world 字符串的 sdshdr 結構:

struct sdshdr {  len = 11;  free = 0;  buf = "hello world/0"; // buf 的實際長度為 len + 1};

通過 len 屬性, sdshdr 可以實現復雜度為 θ(1) 的長度計算操作。

另一方面, 通過對 buf 分配一些額外的空間, 并使用 free 記錄未使用空間的大小, sdshdr 可以讓執行追加操作所需的內存重分配次數大大減少, 下一節我們就會來詳細討論這一點。

當然, sds 也對操作的正確實現提出了要求 —— 所有處理 sdshdr 的函數,都必須正確地更新 len 和 free 屬性,否則就會造成 bug 。

數據類型定義
與sds實現有關的數據類型有兩個,一個是 sds:

  // 字符串類型的別名   typedef char *sds; 


另一個是 sdshdr:

  // 持有sds的結構   struct sdshdr {     // buf中已經被使用的字符串空間數量     int len;     // buf中預留字符串的空間數量     int free;     // 實際存儲字符串的地方     char buf[];   }; 


其中,sds只是字符串數組類型char*的別名,而sdshdr用于持有和保存sds的信息

比如,sdshdr.len可以用于在O(1)的復雜度下獲取sdshdr.buf中存儲的字符串的實際長度,而sdshdr.free則用于保存sdshdr.buf中還有多少預留空間

(這里sdshdr應該是sds handler的縮寫)

將sdshdr用作sds
sds模塊對sdshdr結構使用了一點小技巧:通過指針運算,它使得sdshdr結構可以像sds類型一樣被傳值和處理,并在需要的時候恢復成sdshdr類型

通過下面的函數定義來理解這個技巧

sdsnewlen 函數返回一個新的sds值,實際上,它創建的卻是一個sdshdr結構:

  sds sdsnewlen(const void *init, size_t initlen)   {     struct sdshdr *sh;        if (init) {       // 創建       sh = malloc(sizeof(struct sdshdr) + initlen + 1);     } else {       // 重分配       sh = calloc(1, sizeof(struct sdshdr) + initlen + 1);     }        if (sh == NULL) return NULL;        sh->len = initlen;     sh->free = 0;  // 剛開始free為0        if (initlen && init) {       memcpy(sh->buf, init, initlen);     }     sh->buf[initlen] = '/0';        // 只返回sh->buf這個字符串部分     return (char *)sh->buf;   } 


通過使用變量持有一個sds的值,在遇到那些只處理sds值本身的函數時,可以直接將sds傳給它們。比如說,sdstoupper 函數就是其中的一個例子:

 

  static inline size_t sdslen(const sds s)   {     // 從sds中計算出相應的sdshdr結構     struct sdshdr *sh = (void *)(s - (sizeof(struct sdshdr)));        return sh->len;   }         void sdstoupper(sds s)   {     int len = sdslen(s), j;        for (j = 0; j < len; j ++)       s[j] = toupper(s[j]);   } 


這里有一個技巧,通過指針運算,可以從sds值中計算出相應的sdshdr結構:

sds雖然是指向char *的buf(ps:并且空數組不占用內存空間,數組名即為內存地址),但是分配的時候是分配sizeof(struct sdshdr) + initlen + 1的,通過sds - sizeof(struct sdshdr)可以計算出struct sdshdr的首地址,從而可以得到len和free的信息

Redis中的動態字符串學習教程

sdsavail 函數就是使用這中技巧的一個例子:

 

  static inline size_t sdsavail(const sds s)   {     struct sdshdr *sh = (void *)(s - (sizeof(struct sdshdr)));        return sh->free;   } 


內存分配函數實現
和Reids 的實現決策相關的函數是 sdsMakeRoomFor :

 

  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)));        // 設置新sds的字符串長度     // 這個長度比完成本次拼接實際所需的長度要大     // 通過預留空間優化下次拼接操作     newlen = (len + addlen);     if (newlen < 1024 * 1024)       newlen *= 2;     else       newlen += 1024;        // 重新分配sdshdr     newsh = realloc(sh, sizeof(struct sdshdr) + newlen + 1);     if (newsh == NULL) return NULL;        newsh->free = newlen - len;        // 只返回字符串部分     return newsh->buf;   } 


這種內存分配策略表明,在對sds 值進行擴展(expand)時,總會預留額外的空間,通過花費更多的內存,減少了對內存進行重分配(reallocate)的次數,并優化下次擴展操作的處理速度

再把redis的如果實現對sds字符串擴展的方法貼一下,很不錯的思路:

  /**    * 按長度len擴展sds,并將t拼接到sds的末尾    */   sds sdscatlen(sds s, const void *t, size_t len)   {     struct sdshdr *sh;        size_t curlen = sdslen(s);        // O(N)     s = sdsMakeRoomFor(s, len);     if (s == NULL) return NULL;        // 復制     memcpy(s + curlen, t, len);        // 更新len和free屬性     sh = (void *)(s - (sizeof(struct sdshdr)));     sh->len = curlen + len;     sh->free = sh->free - len;        // 終結符     s[curlen + len] = '/0';        return s;   }      /**    * 將一個char數組拼接到sds 末尾    */   sds sdscat(sds s, const char *t)   {     return sdscatlen(s, t, strlen(t));   } 


注:相關教程知識閱讀請移步到Redis頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
精品伊人久久97| 日韩精品一二三四区| 国产精品成人一区二区三区吃奶| www.久久色.com| 亚洲天堂久久av| 国产精品a久久久久久| 伊人伊人伊人久久| 欧美性20hd另类| 久久精品电影网| 中文字幕日韩欧美精品在线观看| 国产精品尤物福利片在线观看| 久久九九全国免费精品观看| 亚洲欧美国内爽妇网| 亚洲va欧美va国产综合久久| 久久国产精品电影| 最近2019免费中文字幕视频三| 欧美激情亚洲一区| 国产精品r级在线| 亚洲成年网站在线观看| 国产精品视频久久久| 九九久久久久99精品| 国产成+人+综合+亚洲欧美丁香花| 北条麻妃99精品青青久久| 91午夜在线播放| 成人免费xxxxx在线观看| 亚洲成色777777在线观看影院| 色777狠狠综合秋免鲁丝| 久久久亚洲影院| 欧美有码在线观看| 亚洲黄色在线观看| 91精品国产高清自在线看超| 91精品久久久久久久| 精品国偷自产在线视频99| 一区二区欧美亚洲| 亚洲第一精品久久忘忧草社区| 最近2019中文字幕mv免费看| 国产精品h在线观看| 亚洲人av在线影院| 欧美成人精品在线视频| 91av免费观看91av精品在线| 欧美一级成年大片在线观看| 精品久久久久久中文字幕| zzjj国产精品一区二区| 亚洲精品国产欧美| 亚洲综合自拍一区| 国产一区二区三区高清在线观看| 国产精品一区二区电影| 亚洲国产成人精品久久久国产成人一区| 日韩精品黄色网| 青草青草久热精品视频在线观看| 日韩精品中文字幕视频在线| 92看片淫黄大片看国产片| 国产伦精品一区二区三区精品视频| 欧美在线视频一区| 精品magnet| 精品无人区乱码1区2区3区在线| 91色中文字幕| 韩剧1988免费观看全集| 亚洲一区二区久久| 亚洲精品国产综合久久| 中文字幕精品视频| 在线观看欧美www| 日韩中文字幕在线观看| 国产精品你懂得| 亚洲第一福利网| 精品一区二区三区电影| 98精品国产高清在线xxxx天堂| 亚洲福利视频专区| 日韩精品电影网| 久久精品视频在线观看| 国产成人综合精品在线| 亚洲最大激情中文字幕| 欧美与黑人午夜性猛交久久久| 欧美激情一区二区三区在线视频观看| 一道本无吗dⅴd在线播放一区| 91国产精品电影| 伊人久久免费视频| 人人爽久久涩噜噜噜网站| 日韩在线视频导航| 在线观看日韩www视频免费| 性夜试看影院91社区| 中文字幕欧美亚洲| 国产亚洲福利一区| 国产精品亚洲第一区| 97国产真实伦对白精彩视频8| 国产亚洲精品久久| 国产精品日韩在线| 69视频在线免费观看| 中文字幕亚洲情99在线| 成人久久精品视频| 欧美日韩国内自拍| 美女视频久久黄| 在线播放国产一区二区三区| 欧美精品少妇videofree| 在线成人中文字幕| 黄色一区二区在线观看| 亚洲色图第三页| 欧美香蕉大胸在线视频观看| 国产97在线|亚洲| 国内精品久久久久久久久| 欧美日韩国产一区二区三区| 欧美精品精品精品精品免费| 91九色国产社区在线观看| 国内精品视频在线| 久久69精品久久久久久国产越南| 亚洲激情在线观看视频免费| 色www亚洲国产张柏芝| 精品香蕉一区二区三区| 动漫精品一区二区| 欧美午夜xxx| 亚洲成avwww人| 欧美色视频日本版| 久久久久久久久久久久av| 欧洲午夜精品久久久| 亚洲高清久久网| 久久久国产视频| 琪琪亚洲精品午夜在线| 国内精品视频一区| 国产精品电影在线观看| 亚洲人精选亚洲人成在线| 亚洲成人a**站| 久久久久久高潮国产精品视| 97人洗澡人人免费公开视频碰碰碰| 日韩在线观看免费高清完整版| 亚洲精品一区中文| 亚洲色图综合网| 久久精品国产91精品亚洲| 亚洲成人久久网| 欧美日本亚洲视频| 神马久久桃色视频| 亚洲一区二区免费在线| 国产亚洲精品91在线| 国产亚洲欧美视频| 欧美怡红院视频一区二区三区| 国产精品成人观看视频国产奇米| 日韩欧美主播在线| 久久精品国产一区二区三区| 欧美日韩加勒比精品一区| 日韩大胆人体377p| 91国产视频在线| 日本精品免费观看| 亚洲无限av看| 欧美贵妇videos办公室| 亚洲欧美日韩中文视频| 亚洲精品欧美日韩| 91福利视频在线观看| 欧美黄色三级网站| 91在线无精精品一区二区| 国产乱肥老妇国产一区二| 国产免费成人av| 在线午夜精品自拍| 国产精品久久久久久久一区探花| 日本在线精品视频| 久久手机免费视频| 欧美在线观看网址综合| 国产大片精品免费永久看nba| 久久久综合免费视频| 久久久久久国产精品| 精品国内亚洲在观看18黄| 色七七影院综合| 国产精品露脸av在线| 在线视频欧美日韩精品| 欧美俄罗斯性视频| 欧美成人午夜免费视在线看片|