日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

Redis内部数据结构详解之简单动态字符串(sds)

發(fā)布時間:2024/9/20 数据库 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Redis内部数据结构详解之简单动态字符串(sds) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

本文所引用的源碼全部來自Redis2.8.2版本。

Redis中簡單動態(tài)字符串sds數據結構與API相關文件是:sds.h, sds.c。

轉載請注明,本文出自:http://blog.csdn.net/acceptedxukai/article/details/17482611


預備知識


下面介紹有關sizeof計算參數所占字節(jié)數的部分實例,方便下面對sds數據結構地址的計算理解

[cpp]?view plain?copy
  • <span?style="font-family:Courier?New;">typedef?struct?Node{??
  • ????int?len;??
  • ????char?str[5];??
  • }Node;??
  • typedef?struct?Node2{??
  • ????int?len;??
  • ????char?str[];??
  • }Node2;??
  • sizeof(char*)?=?4??
  • sizeof(Node*)?=?4??
  • sizeof(Node)?=?12??
  • sizeof(Node2)?=?4??
  • </span>??
  • 簡單解釋下上述sizeof的結果值,前兩個等于4是因為指針;第三個值等于12是因為len占4個字節(jié),char str[5]實際應該占5個字節(jié),但是由于計算機內存對齊的原因其實際占8個字節(jié);最后一個等于4,因為char str[]沒有實際長度,不被分配內存。

    了解sizeof之后還需要了解stdarg.h中的va_list, va_start,va_end,va_copy的知識,這個在網上有很多就不多解釋了。


    簡單動態(tài)字符串sds與char*對比

    sds在Redis中是實現字符串對象的工具,并且完全取代char*.

    char*的功能比較單一,不能實現Redis對字符串高效處理的需求,char*的性能瓶頸主要在:計算字符串長度需要使用strlen函數,該函數的時間復雜度是O(N),而在Redis中計算字符串長度的操作十分頻繁,O(N)的時間復雜度完全不能接受,sds實現能在O(1)時間內得到字符串的長度值;同時,在處理字符串追加append操作時,如果使用char*則需要多次重新分配內存操作。


    簡單動態(tài)字符串sds數據結構

    [cpp]?view plain?copy
  • <span?style="font-family:Courier?New;">typedef?char?*sds;??
  • ??
  • struct?sdshdr?{??
  • ????int?len;?????//buf已占用的長度,即當前字符串長度值??
  • ????int?free;????//buf空余可用的長度,append時使用??
  • ????char?buf[];??//實際保存字符串數據??
  • };</span>??
  • 通過增加len字段,就可以實現在O(1)時間復雜度內得到字符串的長度,增加free字段,在需要append字符串時,如果free的值大于等于需要append的字符串長度,那么直接追加即可,不需要重新分配內存。sizeof(sdshdr) = 8.?

    簡單動態(tài)字符串sds中函數API

    函數名稱

    作用

    復雜度

    sdsnewlen

    創(chuàng)建一個指定長度的sds,接受一個指定的C字符串作為初始化值

    O(N)

    sdsempty

    創(chuàng)建一個只包含空字符串””的sds

    O(N)

    sdsnew

    根據給定的C字符串,創(chuàng)建一個相應的sds

    O(N)

    sdsdup

    復制給定的sds

    O(N)

    sdsfree

    釋放給定的sds

    O(1)

    sdsupdatelen

    更新給定sds所對應的sdshdr的free與len值

    O(1)

    sdsclear

    清除給定sds的buf,將buf初始化為””,同時修改對應sdshdr的free與len值

    O(1)

    sdsMakeRoomFor

    對給定sds對應sdshdr的buf進行擴展

    O(N)

    sdsRemoveFreeSpace

    在不改動sds的前提下,將buf的多余空間釋放

    O(N)

    sdsAllocSize

    計算給定的sds所占的內存大小

    O(1)

    sdsIncrLen

    對給定sds的buf的右端進行擴展或縮小

    O(1)

    sdsgrowzero

    將給定的sds擴展到指定的長度,空余的部分用\0進行填充

    O(N)

    sdscatlen

    將一個C字符串追加到給定的sds對應sdshdr的buf

    O(N)

    sdscpylen

    將一個C字符串復制到sds中,需要依據sds的總長度來判斷是否需要擴展

    O(N)

    sdscatprintf

    通過格式化輸出形式,來追加到給定的sds

    O(N)

    sdstrim

    對給定sds,刪除前端/后端在給定的C字符串中的字符

    O(N)

    sdsrange

    截取給定sds,[start,end]字符串

    O(N)

    sdscmp

    比較兩個sds的大小

    O(N)

    sdssplitlen

    對給定的字符串s按照給定的sep分隔字符串來進行切割

    O(N)


    Redis中sds實現的細節(jié)解析

    [cpp]?view plain?copy
  • <span?style="font-family:Courier?New;">static?inline?size_t?sdslen(const?sds?s)?{??
  • ????struct?sdshdr?*sh?=?(void*)(s-(sizeof(struct?sdshdr)));??
  • ????return?sh->len;??
  • }??
  • ??
  • static?inline?size_t?sdsavail(const?sds?s)?{??
  • ????struct?sdshdr?*sh?=?(void*)(s-(sizeof(struct?sdshdr)));??
  • ????return?sh->free;??
  • }</span>??
  • 上述兩個函數sdslen, sdsavail分別用來計算給定的sds的字符串長度和給定的sds空余的字節(jié)數。仔細觀察會發(fā)現函數的參數是sds即char *,接著通過一行代碼就能得到給定sds所對應的sdshdr數據結構,貌似很神奇的樣子啊!

    看Redis中初始化一個sds的代碼

    [cpp]?view plain?copy
  • <span?style="font-family:Courier?New;">/*init:?C字符串,initlen:C字符串的長度*/??
  • sds?sdsnewlen(const?void?*init,?size_t?initlen)?{??
  • ????struct?sdshdr?*sh;??
  • ??
  • ????if?(init)?{??
  • ????????sh?=?zmalloc(sizeof(struct?sdshdr)+initlen+1);??
  • ????}?else?{??
  • ????????sh?=?zcalloc(sizeof(struct?sdshdr)+initlen+1);??
  • ????}??
  • ????if?(sh?==?NULL)?return?NULL;??
  • ????sh->len?=?initlen;??
  • ????sh->free?=?0;??
  • ????if?(initlen?&&?init)??
  • ????????memcpy(sh->buf,?init,?initlen);??
  • ????sh->buf[initlen]?=?'\0';??
  • ????return?(char*)sh->buf;??
  • }??
  • ??
  • /*?Create?a?new?sds?string?starting?from?a?null?termined?C?string.?*/??
  • sds?sdsnew(const?char?*init)?{??
  • ????size_t?initlen?=?(init?==?NULL)???0?:?strlen(init);??
  • ????return?sdsnewlen(init,?initlen);??
  • }</span>??
  • 核心函數是sdsnewlen,sh = zmalloc(sizeof(struct sdshdr)+initlen+1)為sdshdr數據結構分配內存,該段內存分為兩個部分:sdshdr數據結構所占的內存數sizeof(sdshdr),我們知道其值為8;initlen+1為sdshdr數據結構中buf的內存。而sdsnewlen函數的返回值是buf的首地址,這樣在看sdslen函數,通過給定的sds首地址減去sizeof(sdshdr),那么就應該是該sds所對應的sdshdr數據結構首地址,自然就能得到sh->len與sh->free。這種操作真的很神奇,這就是C語言指針的妙用,而且使用這種方式,很好的隱藏了sdshdr數據結構,對外接口全部同C字符串類似,卻達到了求取sds字符串長度時間復雜度O(1)與降低append操作頻繁申請內存的效果。


    簡單動態(tài)字符串sds空間擴展操作解析

    sds模塊的函數都比較簡單,不一一介紹,主要講解sds如何對空間進行擴展的,擴展操作主要在append操作的時候使用。

    [cpp]?view plain?copy
  • <span?style="font-family:Courier?New;">/*?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.?*/??
  • //對sdshdr的buf進行擴展??
  • sds?sdsMakeRoomFor(sds?s,?size_t?addlen)?{??
  • ????struct?sdshdr?*sh,?*newsh;??
  • ????size_t?free?=?sdsavail(s);?//查看當前sds空余的長度??
  • ????size_t?len,?newlen;??
  • ??
  • ????if?(free?>=?addlen)?return?s;?//不需要擴展??
  • ????len?=?sdslen(s);?//得到當前sds字符串的長度??
  • ????sh?=?(void*)?(s-(sizeof(struct?sdshdr)));?//得到sdshdr首地址??
  • ????newlen?=?(len+addlen);?//追加之后sds新的長度??
  • ????if?(newlen?<?SDS_MAX_PREALLOC)?//SDS_MAX_PREALLOC?(1024*1024),擴展的具體方法??
  • ????????newlen?*=?2;??
  • ????else??
  • ????????newlen?+=?SDS_MAX_PREALLOC;??
  • ????newsh?=?zrealloc(sh,?sizeof(struct?sdshdr)+newlen+1);?//重新分配內存??
  • ????if?(newsh?==?NULL)?return?NULL;//分配內存失敗??
  • ??
  • ????newsh->free?=?newlen?-?len;?//新的sds空余長度??
  • ????return?newsh->buf;??
  • }</span>??

  • 小結


    Redis的簡單動態(tài)字符串sds對比C語言的字符串char*,有以下特性:

    1) 可以在O(1)的時間復雜度得到字符串的長度

    2) 可以高效的執(zhí)行append追加字符串操作

    3) 二進制安全

    sds通過判斷當前字符串空余的長度與需要追加的字符串長度,如果空余長度大于等于需要追加的字符串長度,那么直接追加即可,這樣就減少了重新分配內存操作;否則,先用sdsMakeRoomFor函數先對sds進行擴展,按照一定的機制來決定擴展的內存大小,然后再執(zhí)行追加操作,擴展后多余的空間不釋放,方便下次再次追加字符串,這樣做的代價就是浪費了一些內存,但是在Redis字符串追加操作很頻繁的情況下,這種機制能很高效的完成追加字符串的操作。


    由于sds其他的函數比較簡單,如果有問題的可以在回復中提出。

    指出一點2.8源碼中sds作者作出的注釋有一處是錯誤的,具體就不列出了。

    最后感謝黃健宏(huangz1990)的Redis設計與實現及其他對Redis2.6源碼的相關注釋對我在研究Redis2.8源碼方面的幫助。


    來源:http://blog.csdn.net/acceptedxukai/article/details/17482611

    總結

    以上是生活随笔為你收集整理的Redis内部数据结构详解之简单动态字符串(sds)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。