Redis數據結構(1):SDS(簡單動態字符串)


  Redis 沒有直接使用 C 語言傳統的字符串表示(以空字符結尾的字符數組,以下簡稱 C 字符串), 而是自己構建了一種名為簡單動態字符串(simple dynamic string,SDS)的抽象類型, 並將 SDS 用作 Redis 的默認字符串表示。

  在 Redis 里面, C 字符串只會作為字符串字面量(string literal), 用在一些無須對字符串值進行修改的地方, 比如打印日志.

  當 Redis 需要的不僅僅是一個字符串字面量, 而是一個可以被修改的字符串值時, Redis 就會使用 SDS 來表示字符串值

1.SDS的定義

struct sdshdr {

    // 記錄 buf 數組中已使用字節的數量, 不包括 '\0' 的長度
    // 等於 SDS 所保存字符串的長度
    int len;

    // 記錄 buf 數組中未使用字節的數量
    int free;

    // 字節數組,用於保存字符串
    char buf[];

};

  buf [ ] 除了保存字符串的字符外, 還會在末尾保存一個空字符 '\0' , 空字符不計算在 len 屬性之中.

  遵循空字符結尾的好處是可以重用一部分C字符串的函數.  

2.SDS與C字符串的區別

2.1 常數復雜度獲取字符串長度

  C字符串不記錄自身的長度信息, 獲取字符串長度時會遍歷字節數組, 直到遇到空字符為止. 復雜度為 O(N)

  SDS直接通過 len 屬性獲取字符串長度. 復雜度為O(1)

2.2 杜絕緩沖區溢出

  C字符串不記錄自身長度, 修改字符串時不會判斷本身是否擁有足夠的內存空間, 當內存空間不足時, 則會造成緩沖區的溢出.

  SDS對字符串進行修改時,先檢查內存空間是否滿足修改的需要, 若不滿足, 則自動擴展SDS的內存空間. 所以使用SDS既不需要手動修改內存空間的大小, 也不會出現緩沖區溢出的情況.

2.3 空間預分配

  第一次創建字符串對象時, SDS不會分配冗余空間, 即 len = 0

  當SDS的API修改SDS時, 則會為其分配冗余空間.

  1. 當修改后的SDS的 len 屬性小於1MB時, 則為其分配和 len 同樣大小的冗余空間, 即 free = len, 此時 buf [ ] 的實際長度 = len(實際長度) + free(冗余空間) + 1(空字符)
  2. 當修改后的SDS的 len 屬性大於等於1MB時, 則為其分配1MB的冗余空間.  buf [ ] 的實際長度 = len(實際長度) + free(1MB) + 1(空字符)

2.4 惰性空間釋放

  SDS的API縮短SDS的字符串時, 不會立即使用內存分配回收縮短后多出來的字節, 而是記錄在 free 屬性中, 並等待將來使用.

2.5 二進制安全

  C字符串中的字符必須符合某種編碼(比如ASCII),並且除了字符串的末尾之外,字符串里面不能包含空字符,否則最先被程序讀入的空字符將被誤認為是字符串結尾,這些限制使得C字符串只能保存文本數據,而不能保存像圖片、音頻、視頻、壓縮文件這樣的二進制數據。

  SDS的API都是二進制安全的.所有SDS API都會以處理二進制的方式來處理SDS存放在buf數組里的數據,程序不會對其中的數據做任何限制、過濾、或者假設,數據在寫入時是什么樣的,它被讀 取時就是什么樣。

  這也是我們將SDS的buf屬性稱為字節數組的原因——Redis不是用這個數組來保存字符,而是用它來保存一系列二進制數據。

3. 字符串的不同編碼方式

  傳送門 : Redis 數據編碼方式詳解

  為什么會有不同的編碼方式,為了解釋這種現象,我們首先來了解一下 Redis 對象頭結構體,所有的 Redis 對象都有下面的這個結構頭:

struct RedisObject {
    int4 type; // 4 bits
    int4 encoding; // 4 bits
    int24 lru; // 24 bits
    int32 refcount; // 4 bytes
    void *ptr; // 8 bytes,64-bit system
} robj;

 

  不同的對象具有不同的類型 type(4bit),同一個類型的 type 會有不同的存儲形式 encoding(4bit),為了記錄對象的 LRU 信息,使用了 24 個 bit 來記錄 LRU 信息。

  每個對象都有個引用計數,當引用計數為零時,對象就會被銷毀,內存被回收。ptr 指針將指向對象內容 (body) 的具體存儲位置。

  這樣一個 RedisObject 對象頭需要占據 16 字節的存儲空間。

 

3.1 embstr

  從Redis 3.0版本開始字符串引入了EMBSTR編碼方式,長度小於OBJ_ENCODING_EMBSTR_SIZE_LIMIT(39)的字符串將以EMBSTR方式存儲。

  EMBSTR方式的意思是 embedded string ,字符串的空間將會和redisObject對象的空間一起分配,兩者在同一個內存塊中。

  Redis中內存分配使用的是jemalloc,jemalloc分配內存的時候是按照 8,16,32,64 作為chunk的單位進行分配的。

  為了保證采用這種編碼方式的字符串能被jemalloc分配在同一個chunk中,該字符串長度不能超過64

  故字符串長度限制OBJ_ENCODING_EMBSTR_SIZE_LIMIT = 64 - sizeof('0') - sizeof(robj)為16 - sizeof(struct sdshdr)為8 = 39。

  采用這個方式可以減少內存分配的次數,提高內存分配的效率,降低內存碎片率

3.2 raw

  從len字段可以判斷並不不依賴於'0',故可以用與保存二進制對象

  從free字段可以判斷其空間分配是采用預分配的方式,避免字符串修改時頻繁分配釋放內存。

3.3 int

  INT編碼方式以整數保存字符串數據,僅限能用long類型值表達的字符串。

  當robj中的LRU值沒有意義的時候(實例沒有設置maxmemory限制或者maxmemory-policy設置的淘汰算法中不計算LRU值時),

  0-10000之間的OBJ_ENCODING_INT編碼的字符串對象將進行共享。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM