原文連結:https://www.changxuan.top/?p=1109
簡介
Redis 中自定義的字串結構。
字串是 Redis 中最常用的一種資料型別,在 Redis 中專門封裝了一個字串結構體——簡單動態字串(Simple Dynamic String, SDS)。其結構體如下:
struct sdshdr {
// 記錄 buf 陣列中已使用位元組的數量既 SDS 中所儲存字串的長度
int len;
// 記錄 buf 陣列中未使用位元組的數量
int free;
// 位元組陣列,用於儲存字串。
char buf[];
}
當 len
的值為 8 時,表示在 buf
陣列中儲存了一個 8 位元組長的字串;當 free
的值為 2 時,表示在 buf
陣列中還有兩個位元組的空間未使用。如果為 0 ,則表示當前 buf
陣列的空間已經全部分配完畢;buf
則是一個 char
型別的陣列。SDS 遵循了C字串以空字元結尾的慣例,即儲存在 buf
中的字串末尾都會緊跟一個空字元 \0
,這個空字元對於使用者來說是透明的,它並不會被計入 len
中。
優點
為什麼要在 Redis 中要自定義字串的資料結構?
1 時間複雜度
首先,由上面程式碼我們可以知道通過 SDS 獲取字串的長度的時間複雜度為 O(1)。而如果使用 C 字串每次獲取字串長度時的時間複雜度則為 O(N)。即當我們使用 STRLEN
命令獲取某個鍵值的長度時不用擔心效能問題。
2 緩衝區溢位
其次,可以避免緩衝區溢位問題。例如,兩個C字串在記憶體中緊挨著,如果沒有提前給前一個字串分配足夠空間的情況下就使用 strcat
函式在其末尾追加新的字串。那麼新拼接的字串就會溢位到後一個字串的空間中,從而導致後一個字串的內容發生改變。但是在 SDS 中,對內容進行修改之前會先檢查其記憶體空間是否滿足要求,如果不滿足要求,則會自動將空間擴充套件至所需要的大小。擴充套件空間大小的操作對於使用者來說也是透明的。
另外,為了避免可能由於頻繁的修改字串內容,而導致產生較為耗時的記憶體重分配問題。SDS 通過以空間換時間的方式即未使用空間來儘量避免這種問題。在 SDS中實現了空間預分配和惰性空間釋放兩種優化策略。
優化策略
空間預分配
當 SDS 中的字串變長時,程式先判斷當前閒置空間是否滿足需求。如果不滿足,則按照空間預分配的策略對空間進行擴充套件。Redis 不僅僅只分配所需要的空間大小,則是根據規則多分配一些空間。當 SDS 修改後的新值長度小於 1MB(len
的長度)。那麼程式將會分配和 len
同樣大小的閒置空間,即 len = free
。buf
陣列的實際長度則是 len + free + 1
位元組。如果修改後的新值大於等於 1MB,程式則會分配 1MB 的未使用空間。
如此一來,就不需要每次增加字串長度時必須對記憶體重新分配,從而提高了系統效能。
惰性空間釋放
當 SDS 中的字串變短時,程式並不是直接進行記憶體重分配回收多餘的空間,而是使用 free
記錄下來。如果將來再變長時,可以直接使用。
通過惰性空間釋放,避免了縮短字串時產生的記憶體重分配操作。
3 二進位制安全
由於C字串的特殊性,在一些場景中會出現問題。如,一個字串中存在多個空字元,那麼C字串只能識別出第一個空字元之前的內容。且C字串只能儲存文字資料。
而 SDS 的 API 都是二進位制安全的,所有的 API 都會以處理二進位制的方式來處理 SDS 存放在 buf
陣列中的資料,以保證資料寫入前與讀取後的一致性。
4 相容部分C字串函式
避免了重複造輪子的問題。
SDS API
函式 | 作用 | 備註 |
---|---|---|
sdsnew | 建立一個包含給定 C 字串的 SDS | |
sdsempty | 建立一個不包含任何內容的空 SDS | |
sdsfree | 釋放給定的 SDS | |
sdslen | 返回 SDS 已使用的空間位元組數 | |
sdsavail | 返回SDS 未使用的空間位元組數 | |
sdsdump | 建立一個給定 SDS 的副本 | |
sdsclear | 清空 SDS 儲存的字串內容 | |
sdscat | 將給定的C字串拼接到 SDS字串末尾 | |
sdscatsds | 將給定的SDS字串拼接到另一個SDS字串的末尾 | |
sdscpy | 將給定的C字串複製到 SDS中,並覆蓋SDS中原有的字串 | |
sdsgrowzero | 用空字元將SDS擴充套件至給定長度 | |
sdsrange | 保留SDS給定區間內的資料 | |
sdstrim | 接受一個 SDS 和一個 C字串作為引數,從 SDS 中移除所有在C字串中出現過的字元 | |
sdscmp | 對比兩個 SDS 是否相同 |