[redis] 數據結構 -- 字符串

爲什麼使用 SDS(Simple Dynamic String,動態字符串)

  1. 常數複雜度獲取字符串長度 O(1)
  2. 杜絕緩衝區溢出
  3. 減少修改字符串時帶來的內存重分配次數(最多擴容 n 次,空間預分配、惰性空間釋放)
  4. 二進制安全,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)。

擴容策略

  • 字符串在長度小於 1M 之前,擴容空間採用加倍策略,也就是保留 100% 的冗餘空間。
  • 當長度超過 1M 之後,爲了避免加倍後的冗餘空間過大而導致浪費,每次擴容只會多分配 1M 大小的冗餘空間。

C 字符串和 SDS 之間的區別

C 字符串 SDS
獲取字符串長度的複雜度爲 O(N) 獲取字符串長度的複雜度爲 O(1)
API 是不安全的,可能會造成緩衝區溢出 API 是安全的,不會造成緩衝區溢出
修改字符串長度 N 次必然需要執行 N 次內存重分配 修改字符串長度 N 次,最多需要執行 N 次內存重分配
只能保存文本數據 可以保存文本或者二進制數據
可以使用所有 <string.h> 庫中的函數 可以使用一部分 <string.h> 庫中的函數
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章