Redis 數據結構字符串底層剖析(簡單動態字符串)

引言

Redis沒有直接使用C語言傳統的字符串表示,而是自己構建了一種名爲簡單動態字符串(simple dynamic string,SDS)的抽象類型,並將SDS用作Redis的默認字符串表示.

在Redis 裏面,C字符串只會作爲字符串面量(string literal)用在一些無須對字符串值進行修改的地方。

在一個可以被修改的字符串值裏面,Redis就會使用SDS來表示字符串值,比如:Redis數據庫裏面,包含字符串值得健值對在底層得實現都是由SDS實現的以及AOF的緩衝區等(注:在一些無需更改的地方字符串是用C字符串)

基本結構

 

🤔思考:

(1)爲什麼不直接使用C字符串,而要自己實現一個SDS呢?

肯定是C字符串整體上滿足不了需求嘛,因爲C語言使用的簡單的字符串表示方式,並不能滿足Redis對字符串在安全性、效率性以及功能方面的要求

(2)SDS遵循了C字符串以空格符結尾的慣例,爲什麼要這樣設計呢?

好處就是SDS可以直接重用一部分C字符串函數,不用自己去實現

 

獲取字符串長度

C字符串不記錄自身的長度信息,所以爲了獲取一個C字符串的長度,程序必須遍歷整個字符串,對遇到的每個字符進行計數,知道遇到代表字符串結尾的空字符爲止,時間複雜度爲O(N)

Redis中的SDS因爲記錄的字符串長度,所以獲取長度信息 時間複雜度爲O(1),因此,在反覆獲取長度的時候能大大減少性能消耗

緩存區溢出問題

C語言不記錄自身長度還可能造成緩衝區溢出(buffer overflow)

但SDS的空間分配策略考慮到這點,當SDS的API需要對SDS進行操作修改時候,API會先檢查SDS的空間是否滿足修改需求,如果不滿足的話,API會自動將SDS的空間擴展至需要執行修改所需的大小,然後才執行相關操作

🤔思考

每次重新分配空間都只是按需分配,那和C語言每次重新分配新空間進行存儲沒多大區別呀?性能好像也沒增加多少呀?

所以如何減少空間重新分配也在Redis考慮之中

空間預分配策略

空間預分配就是用與優化SDS的字符串增長操作:當SDS的API對一個SDS進行修改並需要對SDS進行空間擴展的時候,程序不僅僅會爲SDS分配修改所必須的空間,還會爲SDS分配額外的使用空間

額外空間分配數量公式:

如果SDS進行修改之後

(1)SDS的長度(也即是len屬性的值)將小於1MB,那麼程序分配和len屬性同樣大小的未使用空間,這時len屬性的值江河free屬性的值相同

(2)SDS的長度大於等於1MB,那麼程序將會分配1MB的未使用空間

這樣將N次字符串所需內存重分配次數從必須N次變爲最多N次(注:會用空間浪費,典型的空間換時間策略)

惰性空間釋放(有額外分配空間,那回收怎麼辦?)

惰性空間釋放用於優化SDS的字符串縮短操作,當SDS的API需要縮短保存的字符串,程序並不立即使用內存重分配來回收縮短後多出來的字節,而是使用free屬性將這些字節的數量保存起來,並等待將來使用

當然SDS也提供了相應的API,讓我們可以在需要的時候,真正釋放SDS的使用空間,所以不用擔心惰性空間釋放策略造成的內存浪費

二進制安全

C中字符串只能保存文本數據,不能保存圖片、音頻等這樣的二進制數據

Redis的SDS的API都是二進制安全的(binary-safe),所有SDS API都會以處理二進制方式來處理SDS存放在buf數組裏面的數據,不進行任何限制、過濾或者假設(這也是爲什麼buf屬性稱爲字節數組的原因)

因此Redis 不僅可以保存文本數據,還可以保存任意格式的二進制數據

 

總結:

Redis只會使用C字符串作爲字面量,大多數情況下,Redis使用SDS(Simple Dynamic String,簡單動態字符串)作爲字符串表示

優點:

(1)獲取字符串長度時間複雜度降爲O(1)

(2)減少了緩存區溢出

(3)額外空間預分配策略減少了內存重分配次數

(4)二進制安全

(5)兼容部分C語言字符串函數

 

 

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