帶你深入瞭解Redis字串資料結構
阿新 • • 發佈:2020-10-18
1 前言
Redis資料庫裡面的每個鍵值對都是物件組成的,其中:- 資料庫鍵總是一個字串物件(string object);
- 而資料庫鍵的值則可以是字串物件(string)、列表物件(list)、雜湊物件(hash)、集合物件(set)、有序集合物件這五種物件中的一種。
2 正文
Redis沒有直接使用C語言傳統的字串表示,而是自己構建了一種名為簡單動態字串(simple dynamic string, SDS)的抽象型別,並將SDS用作Redis的預設字串表示。2.1 SDS的底層資料結構
struct sdshdr{ //記錄buf陣列中已使用位元組的數量 //等於SDS所儲存字串的長度 int len; //記錄buf陣列中未使用位元組的數量 int free; //位元組陣列,用於儲存字串 char buf[]; };SDS示例
2.2 SDS物件與C語言字串物件的異同點:
相同點:
1、以空字元 '\0' 結尾,併為空字元分配額外的空間(不算在字元長度內)不同點:
1、C語言字串物件底層的資料結構是字元陣列,SDS物件底層資料結構是位元組陣列 2、SDS設定len儲存字串長度,C語言字串通過遍歷陣列獲得字串長度 3、SDS設定free記錄buf陣列未使用位元組的長度2.3 SDS物件的優點
1、常數複雜度獲取字串長度
C字串並不記錄自身的長度資訊,獲取一個C字串的長度,程式必須遍歷整個字串,對遇到的每個字元進行技術,直到遇到代表字串結尾的空字元為止,這個操作的複雜度為O(N)。 SDS在len屬性中記錄了SDS本身的長度,獲取一個SDS長度的複雜度僅為O(1)。2、杜絕緩衝區溢位
C字串不記錄自身的長度帶來的另一個問題是容易造成緩衝區溢位。 而當SDS API需要對SDS進行修改時,API會先檢查SDS空間是否滿足修改所需的要求,如果不滿足的話,API會自動將空間擴充套件至執行所需的大小,然後才執行實際的修改操作。3、減少修改字串時帶來的記憶體重分配次數
- 空間預分配:空間預分配用於優化SDS的字串增長操作。當SDS需要空間擴充套件時,程式不僅會為SDS分配修改所必須要的空間,還會為SDS分配額外的未使用空間。未使用空間數量由如下公示決定:
- 如果對SDS進行修改之後,SDS的長度(len的值)小於1mb,那麼就預分配和原陣列長度大小的未使用空間(即free = len)。
- 如果對SDS進行修改之後,SDS的長度(len的值)大於1mb,那麼就預分配1mb的未使用空間。舉個例子,如果進行修改之後,SDS的len將變成30mb,那麼程式分配1mb的未使用空間,buf陣列的實際長度將為30mb + 1mb + 1byte。
- 通過空間預分配策略,Redis可以減少連續執行字串增長操作所需的記憶體分配次數。
- 惰性空間釋放:當執行SDS字元陣列的縮減時,程式並不立即回收縮短後多出來的位元組,而是使用free屬性將這些位元組的數量記錄起來,並等待將來使用。通過惰性空間釋放策略,SDS避免了縮短字串所需的記憶體重分配操作,併為將來可能有的增長操作提供了優化。與此同時,SDS也提供了相應的API,在有需要時真正去釋放SDS的未使用空間,所以不用擔心該策略造成記憶體浪費。
4、二進位制安全
不同於C字串的只能儲存符合某種編碼(比如ASCII)字元的字元陣列,Redis所以SDS API會以二進位制的方式來處理SDS存放在buf數組裡的資料,程式不會對陣列做任何的限制、過濾,陣列寫入時是什麼樣的,它被讀取是就是什麼樣,這也是為什麼將SDS的buf屬性稱為位元組陣列的原因。Redis不僅可以儲存文字資料,還可以儲存任意格式的二進位制資料。5、相容部分C字串函式
SDS遵循C字串以空字元結尾的慣例,是為了讓那些儲存文字資料的SDS可以重用一部分<string.h>庫定義的函式。 舉個例子,如果我們有一個儲存“Redis”文字資料的SDS值sds,那麼我們就可以重用<string.h>/strcasecmp函式,使用它來對比另一個C字串: strcasecmp(sds->buf, "hello world"); 這樣Redis就不用自己專門去寫一個函式來對比SDS值和C字串值了。3 總結
- Redis只會使用C字串作為字面量,在大多數情況下,Redis使用SDS作為字串表示。
- 比起C字串,SDS具有以下優點:
- 常數複雜度獲取字串長度
- 杜絕緩衝區溢位
- 減少修改字串時帶來的記憶體重分配次數
- 二進位制安全
- 相容部分C字串函式