一、簡單動態字符串(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 的優勢等