1,sds定義
sds是simple dynamic string的縮寫,意爲簡單動態字符串。
定義爲:
struct sdshdr {
long len; //記錄buf數組已使用的字節數量,即sds保存的字符串的長度,不含'\0'
long free; //記錄buf數組中未使用字節的數量
char buf[0]; //字節數組,用於保存字符串
};
如圖:
len:4,表示這個數組保存的字符串的長度是4。
free:4,表示這個數組的未使用空間是4。
buf:是一個字符數組,前4個字節保存的是字符'L'、‘u’、‘o’、‘L’,最後一個字節保存的是字符串結束標識‘\0’。
sds遵循C字符串以空字符結尾的慣例,保存空字符的1字節不計算在sds的len中,並且爲空字符分配額外的1字節空間,以及添 加空字符到字符串末尾等操作,是由sds函數自動完成的。這樣遵循空字符結尾這一慣例的好處是,sds可以直接重用一部分c字符串函數庫裏面的函數。
2,sds與c字符串的區別
既然sds中保存的也是字符串,爲什麼不直接使用字符串,而還要封裝成一個結構體呢?
sds的三點優勢:
- 常數複雜度獲取字符串的長度,通過len屬性獲取長度,複雜度是O(1),字符串循環判斷,是O(n)。
- 杜絕緩衝區溢出。
- 減少修改字符串時帶來的內存分配次數。
3,杜絕緩衝區溢出
C字符串不記錄自身長度帶來的另一問題就是容易造成緩衝區溢出。而sds API需要對sds進行修改時,API會先檢查sds的空間是否滿足修改要求,如果不滿足的話,API會自動將sds的空間擴展至需要的大小,然後執行下面的動作。
4,減少修改字符串時帶來的內存分配次數
由於C字符串不記錄長度,所以對於一個包含N個字符的C字符串來說,這個字符串的底層實現總是一個N+1個字符長的數組,所以每次增長或縮短一個C字符串,都要對這個數組進行一次內存重分配。
而sds中,buf數組的長度不一定是字符數量加一,數組裏面可以包含未使用的字節,通過free 屬性,實現了空間預分配和惰性空間釋放兩種優化策略。
1) 空間預分配。
空間預分配用於優化sds的字符串增長操作:當sds的API 對一個sds 進行修改,並且需要對sds進行空間擴展的時候,不僅會爲sds 分配修改所必須的空間,還會爲sds 分配額外的未使用空間。
其中,額外分配的未使用空間數量由以下公式決定:
A,如果對sds 進行修改後,sds的長度(len值)將小於1MB,那麼分配和len 同樣大小的未使用空間,這時sds 的len和free 的值相同。比如,進行修改後,sds 的len 將變成7字節,那麼也會分配7字節的未使用空間,sds 的buf數組的實際長度將變成
7+ 7 + 1字節(額外的1字節保存空字符)。
B,如果對sds 進行修改後,sds的長度(len值)將大於等於1MB,那麼分配1MB的未使用空間,即free的值是1MB。比如。如果修改後,sds 的len將變成30MB,那麼會分配1MB 的未使用空間,sds 的buf數組的實際長度將變成30MB + 1MB + 1字節(額外的1字節保存空字符)。
2) 惰性空間釋放
惰性空間釋放用於優化sds 的字符串縮短操作:當sds的API 需要縮短sds 保存的字符串時,程序並不立即使用內存分配來回收縮短後多出來的字節,而是使用free 屬性將這些字節數量記錄起來,等待將來使用。
5,二進制安全
A,C字符串中的字符必須符合某種編碼(一般是ASCII編碼),並且除了字符串的末尾外,字符串裏面不能有空字符。
B,sds 的API 都是二進制安全的,所有的sds API都會以處理二進制的方式來處理sds 存放在buf數組裏的數據,不會對數據有限制。即redis 不是用這個數組來保存字符,而是用它來保存一系列二進制數據。因爲sds 使用len屬性的值來而不是空字符來判斷字符串是否結束。
6,總結