Redis的數(shù)據(jù)結(jié)構(gòu)與對象——簡單動態(tài)字符串SDS

前言:

redis是用C寫的,但是并沒有使用C語言的傳統(tǒng)字符串,C語言傳統(tǒng)字符串是以\0為結(jié)尾的字符數(shù)組。而是使用簡單動態(tài)字符串(simple dynamic string,SDS)作為redis默認字符串的表示。SDS除了是字符串對象的底層實現(xiàn),還作為緩沖區(qū)(buffer):AOF模塊中的AOF緩沖區(qū),客戶端狀態(tài)中的輸入緩沖區(qū)等等。

一、定義

每個sds.h/sdshdr結(jié)構(gòu)體表示一個SDS值:

struct sdshdr {

int len;// 記錄buf數(shù)組中已經(jīng)使用字節(jié)的數(shù)量,等于SDS所保存字符串的長度。

int free;// 記錄buf數(shù)組中未使用字節(jié)的數(shù)量。

char buf [ ];// 字符數(shù)組,用于保存字符串。

}




其中:

1、buf數(shù)組最后一個字節(jié)仍然會是\0

2、SDS遵守 C字符串已空字符串結(jié)尾的慣例,空字符串不記在len屬性上。添加\0空字符串到所要保存的字符串末尾都是SDS函數(shù)自動完成的。遵守這一慣例的好處是,SDS可以重用一部分C字符串數(shù)據(jù)庫的函數(shù)。


二、SDS與C字符串的區(qū)別

2.1、常數(shù)復雜度獲取字符串長度

? ? C字符串獲取字符串長度的時間復雜度是O(N),而SDS獲取字符串長度只需要訪問len屬性,時間復雜度是O(1)。確保了STRLEN等獲取字符串的操作不會成為限制redis性能的瓶頸。

2.2、杜絕緩沖區(qū)溢出

? ? C字符串本身不記錄自身的長度,另一個問題就是容易造成緩沖區(qū)溢出。比如strcat函數(shù),一般寫C語言都會先分配連個字符串長度和的內(nèi)存空間,但是如果你忘了,會造成緩沖區(qū)溢出。

? ? SDS則杜絕了這種現(xiàn)象,API會先檢查SDS的空間是否滿足所需的修改,如果不滿足,API會自動擴展SDS的空間 。

2.3、減少修改字符串時帶來的內(nèi)容重分配次數(shù)

? ? 這是最為關(guān)鍵的一個原因。redis會經(jīng)常修改字符串。因為C字符串的長度和底層數(shù)據(jù)的長度之間存在N+1的關(guān)聯(lián),如果每次增長或者縮短一個C字符串,程序都總要對這個保存C字符串的數(shù)組進行一次內(nèi)存分配。比如:

1、增長一個字符串a(chǎn)ppend,執(zhí)行操作之前要內(nèi)存重分配來擴展底層數(shù)組大小,如果忘了還會造成緩沖區(qū)溢出。

2、階段操作trim,程序需要通過內(nèi)存重分配還釋放掉那些不再使用的內(nèi)存空間。

由于內(nèi)存重分配設計非常復雜的算法,并且可能需要執(zhí)行系統(tǒng)調(diào)用,所以通常是一個非常耗時的操作。如果操作較次數(shù)較少還好,但是redis作為數(shù)據(jù)庫,經(jīng)常被用于速度要求嚴苛、數(shù)據(jù)頻繁修改的場合,如果繼續(xù)使用C字符串,那單單是內(nèi)存分配就會占去修改字符串所用時間的一大部分,如果這種修改非常頻繁,可能還會對性能造成瓶頸。

2.3.1、空間預分配

????用于優(yōu)化SDS的字符串增長操作。當SDS的API對SDS需要進行空間擴展的時候,程序不僅會為SDS分配所需的空間,還會為SDS分配額外的未使用空間

? ? 公式如下:

? ? 1、如果最終長度小于1MB,那么會額外分配和len相同的空間

? ? 2、如果最終長度大于1MB,那么會額外分配1MB的空間

? ? 通過這種預分配策略,SDS將連續(xù)增長N詞字符串所需的內(nèi)存重分配次數(shù)從 必定N次減少為最多N次。

2.3.2、惰性空間釋放

????用于優(yōu)化SDS的字符串縮短操作。SDS的API對SDS需要進行空間擴展的時候,程序不會立即縮短SDS保存的字符串,而是使用free將這些字節(jié)的數(shù)量記錄下來,等待以后再使用。

? ? 通過惰性空間釋放策略,SDS避免了縮短字符串帶來的內(nèi)存重分配操作,為將來可能有的增長操作留下了優(yōu)化空間。同時,不必擔心這些沒有使用的內(nèi)存會被浪費掉,其實還有專門的SDS的API用于釋放這些內(nèi)存。

2.4、二進制安全

? ? C字符串由于末尾必定是\0,所以只能用于存儲文本數(shù)據(jù),不能用于存儲二進制數(shù)據(jù)。

????SDS的所有API都是二進制安全的,都會以處理二進制的方式來處理SDS存放在buf數(shù)組的數(shù)據(jù),比如len屬性的值就不是以空字符\0來判斷字符串的結(jié)束。

2.5、兼容部分C字符串函數(shù)

? ? SDS的API依然會將末尾設置為\0,所以redis可以兼容一些C語言的字符串函數(shù),比如strcat等。這樣可以避免不必要的代碼重復。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容