【Redis】數據結構 - SDS

概述

Redis底層由C語言實現, 但Redis並沒有直接使用C語言的字符串, 而是自己構建了一種名爲 SDS ( Simple Dynamic String ) 簡單動態字符串來作爲其字符串的數據結構.

在Redis中, C語言的字符串只會被用作字面量 例如

redisLog(REDIS_WARNING, "Redis is now ready to exit , bye bye ....")

其他情況 Redis中的字符串均爲SDS. 例如

redis> SET message "hello world"

該鍵值對中的鍵爲保存着字符串"message"的SDS
該鍵值對中的值爲保存着"hello world"的SDS

SDS定義

數據結構(C語言):

struct sdshdr{
    // 記錄buf數組中已使用的字節數, 即SDS保存字符串的長度
	int len;
	// 記錄buf數組中未使用的字節數
	int free;
	// 存放字符串
	char buf[];
}

兼容部分C字符串函數

SDS遵循C語言的空字符串結尾規則, buf數組保存字符串時末尾會自動添加’\0’, 且會在分配內存時考慮到末尾的結束符, 這樣做是爲了SDS可以重用一部分C字符串庫的函數.

例如

printf("%s", s->buf);
strcmp(s->buf, "hello world");
strcat(c_string, s->buf);

特性

常數複雜度獲取字符串長度

c語言獲取字符串長度需要調用 strlen()函數來遍歷字符串,計算出到結束符爲止的長度. 該操作的時間複雜度爲O(n)

SDS在len屬性中記錄着字符串的長度, 所以獲取長度的時間複雜度爲O(1)

杜絕緩衝區溢出

C語言的字符串長度固定, 容易造成緩衝區溢出, 例如運行下列代碼

char s1[6] = {'h', 'e', 'l', 'l', 'o', '\0'};
strcat(s1, " world");

s1沒有足夠的空間, 因此s1的數據會溢出到其後連續的內存中, 導致該段內存被意外的修改.

SDS的字符串擁有空間分配策略.

當SDS API需要對SDS進行修改時, API會先檢查SDS的空間是否夠用, 如果不夠用的話API會自動擴展其內存. 從而可以避免緩衝區溢出問題.

減少修改字符串時帶來的內存重新分配次數

  • 增長字符串的操作需要通過內存的重新分配從而擴大buf[]數組, 如果沒有該操作則會產生緩衝區溢出
  • 縮短字符串操作也需要通過內存重新分配從而釋放字符串不再使用的空間, 如果沒有該操作則會導致內存泄漏.

但內存重新分配涉及複雜的算法, 還可能需要執行系統調用, 因此該操作比較耗時.

redis作爲數據庫, 對速度的要求很嚴格, 且數據會被頻繁的修改, 如果每次修改都執行內存分配則會大大影響速度和性能.

因此SDS解除了字符串長度和底層數組長度之間的關聯, 即二者的大小不一定相等, 在此基礎是實現了空間預分配和惰性空間釋放兩種優化策略.

空間預分配

針對對SDS增長的操作. 在增長時會分配額外的預空間

  • 如果對SDS進行增長操作後, 其長度小於1MB. 則SDS會被分配和len大小的空間供字符串使用, 並且分配與其大小相同的空閒空間預留, 即分配後len的值將和free的值相同.
  • 如果對SDS進行增長操作後, 其長度大於等於1MB, 則程序會分配足夠的空間供字符串使用, 並且分配額外的1MB預留. 即分配後len爲字符串長度( 大於1MB), free的長度爲1MB

通過多分配預留空間, 可以在下次增長操作時避免一部分擴容操作, 從而減少內存重新分配操作, 提升效率.

惰性空間釋放

用於優化字符串縮短操作.

  • 當SDS的API需要縮短SDS保存的字符串時, API並不立即重新分配內存來回收空出來的多餘內存, 而是用free屬性將多餘的內存記錄下來, 預留給下次使用.

例如列操作

// s->buf = "XYXXABCYYY" s->len = 10 s->free = 0
sdstrim(s, "xy");   

最後的結果

s->buf 爲 "ABC\0"
s->len = 3
s->free = 7

避免了重新分配內存, 提升了效率, 但有時會造成空間浪費, 因此SDS也有相應的API在必要時可以釋放未使用的空間.

二進制安全

因爲C字符無法存儲空字符’\0’, 因爲’\0’被默認當做了字符串的結尾, 因此C字符串只能存儲文本數據, 而不能保存圖像、音頻、視頻、壓縮文件.

SDS的API都是二進制安全的, 所有SDS API都會以處理二進制的方式來處理SDS存放在BUF數組裏的數據, 即數據寫入時是怎麼樣的, 他被讀取時就是怎麼樣的.

Redis 的 SDSbuf屬性被稱爲字節數組, 因爲Redis不用該數組存放字符, 而是用來存放一系列的二進制數據.

所以Redis不僅可以保存文本數據, 還可以保存任意格式的二進制數據.

參考: 《Redis設計與實現》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章