Redis原始碼分析(一)——Redis資料結構-字串SDS
1. SDS簡介
- Redis中使用的字串均為『簡單動態字串』(Simple Dynamic String),簡稱SDS。
- SDS是在C字串的基礎上進行了一些包裝,使得它更符合Redis的使用場景。
- 在Redis中,C字串只用在一些無需修改的地方,如日誌列印;其他需要使用字串的地方基本上使用的都是SDS。
2. 資料結構
struct sdshdr{
int len;
int free;
char buf[];
};
len
:buf陣列中字串的實際使用量。free
:buf陣列中空閒量。buf
:儲存字元的陣列。
3. SDS的優點
Redis使用C語言編寫,而Redis不使用C語言字串是有原因的,Redis中的SDS字串與C字串相比有如下優點。
3.1 獲取字串長度效率高
C語言字串是不記錄字串長度的,所以每次獲取字串長度時,都要對字元陣列進行一次遍歷,那麼時間複雜度就為O(n)。
而SDS中採用len記錄當前字串的長度,所以統計字串長度的時間複雜度為O(1),因此效率高於C字串。
3.2 避免了緩衝區溢位
3.2.1 什麼是『緩衝區溢位』?
當使用strcat(char *dest, char *src)
拼接兩個字串時,strcat
是預設第一個字元陣列的後面是有足夠空間的,它會直接把第二個字元陣列中的字元挨個複製到第一個字元陣列的後面。
那麼問題就來了,如果這兩個字元陣列的記憶體空間是緊挨著的,那麼當執行strcat時,第二個字元陣列的就會被覆蓋掉。這就是緩衝區溢位。
所以在使用strcat拼接兩個字串前,一定要先判斷第一個字串後面是否有足夠的記憶體空間;如果不夠了,那就得手動擴容。那麼這一系列判斷+擴容操作都是需要程式設計師自己去完成的,有些麻煩。
3.2.2 Redis如何避免緩衝區溢位?
而SDS提供的所有修改字串的API中,都會判斷修改之後是否會記憶體溢位,如果會記憶體溢位,它會幫你進行記憶體擴容。
所以對於SDS而言,這一系列操作都由它來幫你完成,無需程式設計師手動判斷。
3.3 減少修改字串時記憶體重分配的次數
3.3.1 什麼是『記憶體重分配』?
- 當我們使用append擴充字串時,我們首先要擴充當前字元陣列的記憶體,然後再將第二個字元陣列中的值一一複製進來,否則就有可能出現『緩衝區溢位』。這個過程就是『記憶體重分配』。
- 當我們需要擷取字串後,我們需要釋放已經不被使用的記憶體空間,否則就可能出現『記憶體洩露』。這個過程也是『記憶體重分配』。
記憶體重分配過程會涉及複雜的演算法和系統呼叫,較為耗時。如果像C字串那樣,每次修改字串都要進行一次記憶體重分配,那麼效率是極底的,所以SDS使用了『空間預分配』和『惰性空間釋放』降低了重分配的頻率,從而提升效率。
3.3.2 SDS如何減少記憶體重分配次數?
空間預分配
當需要擴充套件SDS長度的時候,Redis不僅會給它分配所需的記憶體空間,還會分配一段額外的空間作為備用。
備用空間大小按照如下公式計算:- 如果擴充套件之後,SDS字元陣列的長度小於1M,那麼就使得備用空間的大小和字串實際長度保持一致,即:len==free;
- 如果擴充套件之後,SDS字元陣列的長度大於1M,那麼備用空間的大小就設定成1M。
那麼這樣的話,當要append時,直接使用備用空間即可,無需再次擴容啦,從而減少了記憶體重分配的次數。SDS將連續增長N次字串所需的記憶體重分配次數從『必定N次』減少到了『最多N次』。
惰性空間釋放
當需要縮短字串時,SDS不會立即釋放多於的記憶體空間,而是將其保留,修改free值。這樣的話,當下次需要擴容時,直接使用這部分記憶體空間即可,減少了記憶體重分配的次數。
3.4 二進位制安全 binary-safe
3.4.1 什麼是『二進位制安全』?
所謂『二進位制安全』就是:往SDS裡面放什麼資料,取出來還是什麼資料。SDS不會對儲存的這些資料做任何修改、限制、過濾等。
3.4.2 SDS如何保證二進位制安全?
C字串對存入的字串是有嚴格要求的:
1. 必須符合某種編碼(如ASKII)
2. 不能含有空格
而SDS對於儲存的資料沒有任何限制,因此稱為『二進位制安全』。
3.5 相容C字串
C字串要求字元陣列的末尾必須是\0,作為字串尾的標記。而SDS中的字元陣列也遵循了這一規範,所以仍然可以使用C字串相關函式,因此避免了重複程式碼。