爲什麼使用 SDS(Simple Dynamic String,動態字符串)
- 常數複雜度獲取字符串長度 O(1)
- 杜絕緩衝區溢出
- 減少修改字符串時帶來的內存重分配次數(最多擴容 n 次,空間預分配、惰性空間釋放)
- 二進制安全,SDS API 會以處理二進制的方式來處理 SDS 存放在 buf 數組裏的數據( buf 裏保存的是二進制數據)
知識點
[注] 相關源碼保存位置:src/redis.h、src/sds.h、src/sds.c。
-
Redis 只會使用 C 字符串作爲字面量
-
在 redis 中字符串叫 SDS(Simple Dynamic String,動態字符串)
-
SDS 結構如下 – 是一個帶長度信息的字節數組。
struct SDS<T> { T capacity; // 數組容量,1byte T len; // 數組長度,1byte byte flags; // 特殊標識位,1byte byte[] content; // 數組內容 }
-
字符串存儲方式分爲
- embstr 長度 44byte 及以下
- raw 長度 45byte 及以上
-
embstr 最大長度爲 44byte 的原因如下
- 所有的 Redis 對象都有下面的這個結構頭,此結構佔據 16byte 的存儲空間
struct RedisObject { int4 type; // 4bits int4 encoding; // 4bits int24 lru; // 24bits int32 refcount; // 4bytes void *ptr; // 8bytes,64-bit system } robj;
- 此外 SDS 結構體至少要佔用
3byte
的空間,所以分配一個字符串的最小空間佔用爲 19byte (16+3) - 內存分配器 jemalloc/tcmalloc 等分配內存大小的單位都是 2、4、8、16、32、64 等等,爲了能容納一個完整的 embstr 對象,jemalloc 最少會分配 32byte 的空間,如果字符串再稍微長一點,那就是 64byte 的空間。如果總體超出了 64byte,Redis 認爲它是一個大字符串,不再使用 emdstr 形式存儲,而該用 raw 形式。
- SDS 結構體中的 content 中的字符串是以字節
\0
結尾的字符串,之所以多出這樣一個字節,是爲了便於直接使用 glibc 的字符串處理函數,以及爲了便於字符串的調試打印輸出。
所以 embstr 最大能容納的字符串長度就是 44byte (64-16-3-1)。
- 所有的 Redis 對象都有下面的這個結構頭,此結構佔據 16byte 的存儲空間
擴容策略
- 字符串在長度小於 1M 之前,擴容空間採用加倍策略,也就是保留 100% 的冗餘空間。
- 當長度超過 1M 之後,爲了避免加倍後的冗餘空間過大而導致浪費,每次擴容只會多分配 1M 大小的冗餘空間。
C 字符串和 SDS 之間的區別
C 字符串 | SDS |
---|---|
獲取字符串長度的複雜度爲 O(N) | 獲取字符串長度的複雜度爲 O(1) |
API 是不安全的,可能會造成緩衝區溢出 | API 是安全的,不會造成緩衝區溢出 |
修改字符串長度 N 次必然需要執行 N 次內存重分配 | 修改字符串長度 N 次,最多需要執行 N 次內存重分配 |
只能保存文本數據 | 可以保存文本或者二進制數據 |
可以使用所有 <string.h> 庫中的函數 | 可以使用一部分 <string.h> 庫中的函數 |