「Redis」字串

壹言發表於2020-10-11

原文連結: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 修改後的新值長度小於 1MBlen 的長度)。那麼程式將會分配和 len 同樣大小的閒置空間,即 len = freebuf 陣列的實際長度則是 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 是否相同

相關文章