Redis 設計與實現之SDS -- 閱讀筆記

一、簡單動態字符串(SDS)

簡單動態字符串(simple dynamic string, SDS) 是 Redis 實現的一個用於保存字符串的數據結構,Redis 沒有使用C 語言傳統的字符串表示。

比如:

redis> set msg "hello, world"
創建一個鍵值對
鍵值對的 鍵 是一個字符串對象,對象的底層實現是一個保存着字符串"msg"的SDS
鍵值對的 值 是一個字符串對象,對象的底層實現是一個保存着字符串"hello, world" 的 SDS

SDS 的定義

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

比如保存一個 “Redis” 字符串:

 ------
|sdshdr|
 ------
| free |
|  0   |
 ------
|  len |
|   5  |
 ------      
| buf  |   ----->   | 'R' | 'e' | 'd' | 'i' | 's' | '\0' |
 ------ 


free 值爲0,表示這個 SDS 沒有分配任未使用空間,如果free > 0, 這個 SDS 爲 buf 數組分配對應值的未使用空間,比如 free = 5, 會在 buf 數組的 "\0" 後分配5字節未使用的空間
len  值爲5,表示這個 SDS 保存了一個5字節長的字符串

SDS 和 C 字符串的區別

SDS 比 C 字符串更適用於 Redis 的原因?

1、C 字符串不記錄自身的長度,導致獲取長度需要遍歷字符串, 複雜度爲O(N)。而 SDS 結構的 len 屬性記錄了 SDS 本身的長度,複雜度爲O(1)
2、當使用 char *strcat(chart *dest, const chart *src) 函數時,如果 dest 沒有分配足夠的內存容納 src, 將產生緩衝區溢出的問題,導致溢出到相鄰的字符的空間中,修改了相鄰字符的內容
3、SDS 空間分配策略會檢查 SDS 的空間是否滿足所需的需求,然後通過 SDS API 自動將空間擴展到所需的大小,就不會出現緩衝區溢出問題
4、減少修改字符串時帶來的內存重分配次數,如果是增長字符串的操作,執行操作之前會通過內存重分配來擴展底層數組的空間大小,如果是縮短字符串的操作,執行操作之後通過內存重分配來釋放字符串不再使用的部分空間。內存重分配涉及複雜的算法,是一個耗時操作

C 字符串 與 SDS 區別

C 字符串 SDS
獲取字符串長度複雜度O(N) 獲取字符串長度複雜度O(1)
API 不安全,容易緩衝區溢出 API 安全,緩衝區無溢出
修改字符串長度N次需要執行 N 次內存重分配 修改字符串長度N次<= N次內存重分配
只能保持文本數據 保持文本及二進制數據
使用所有庫函數 只能使用部分庫函數

SDS 空間預分配和惰性空間釋放策略


1、空間預分配

空間預分配用於優化 SDS 的字符串增長操作,其實就是當需要對 SDS 空間擴展時,除了分配所需空間外,還會爲 SDS 分配額外的未使用空間。額外分配的空間有兩種情況:

1、修改 SDS 後, SDS 的長度(len屬性) < 1MB, 將分配和 len 大小一樣的未使用空間,len 的值和 free 值相同
(如修改後 SDS = 13 字節,程序分配了 13 字節未使用空間,SDS 的 buf 數組長度 = 13 + 13 + 1 = 27 字節)
2、修改 SDS 後,SDS 的長度 > 1MB, 將分配 1MB 的未使用空間
(如修改後 SDS = 20MB,SDS 的 buf 數組長度 = 20MB + 1MB + 1byte(保存空字符))

2、惰性空間釋放

惰性空間釋放用於優化 SDS 的字符串縮短操作,當 SDS API 需要縮短 SDS 保存的字符串長度時,不立即使用內存重分配回收,而是使用 free 記錄這些字節的數量。不釋放多出來的字節空間,如果這時候對 SDS 進行增長操作,這些未使用空間就可以使用。

總結

Redis 使用 C 字符串作爲字面量,在大多數情況下,使用 SDS (簡單動態字符串) 作爲字符串表示,還有對比了 C 字符串與 SDS 的區別,並指出 SDS 的優勢等

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