redis專題十七:再聊一聊redis的簡單動態字串
前面開篇,我們聊到了redis的常見資料結構,也熟悉set msg "hello world"這樣的命令。本篇,再聊一聊基本資料型別中的String.
Redis沒有直接使用C語言的字串(以空字串結尾的字元陣列),而是自己構建了簡單的動態字串(SDS:simple dynamic string)的抽象型別。
1. SDS的定義
sds.h/sdshdr結構如下:
typedef char *sds; struct sdshdr { // 記錄 buf 陣列中已使用位元組的數量 // 等於 SDS 所儲存字串的長度 int len; // 記錄 buf 陣列中未使用位元組的數量int free; // 位元組陣列,用於儲存字串 char buf[]; };
- len=0時,說明SDS沒有分配任何未使用的空間
- len=5,代表SDS儲存了一個5位元組長的字串
- buf時一個字元陣列,如'R','E','D','I','S', 最後一個位元組儲存空字元'\0'
SDS同樣以空字元結尾,但是空字元的1位元組空間不算在len屬性的長度。並且這個分配額外1位元組空間的操作由SDS函式自動完成。
以空字元結尾的好處可以方便直接使用C字串函式庫裡面的函式。
2. SDS和C字串的區別
首先應該先了解到,C語言使用長度為N+1的字元陣列來表示長度為N的字串,字元陣列最後一位是空字元'\0'。
2.1 獲取字串長度的複雜度
C字串 並不記錄自己的長度資訊,要獲取其長度,只能遍歷所有字元,直到遇到空字元為止,複雜度O(n)。
SDS在len屬性中記錄了SDS本身的長度,所以複雜度為O(1)。設定和更新SDS長度的工作由SDS的API完成,使用SDS不需要任何手動修改長度的工作。
這樣一來,確保獲取字串長度的工作不會成為redis的瓶頸。
2.2 杜絕緩衝區溢位
C字串不記錄自身長度帶來的一個問題就是容易造成緩衝區溢位(buffer overflow)。
舉例:C的strcat函式: char *strcat(char *dest, char *src)
在做字元拼接的時候,已經為dest分配了足夠的記憶體,可以容納src的所有內容,一旦這個不成立,就會發生緩衝區溢位,導致儲存的內容被意外的修改。
SDS的空間分配策略就解決了這個問題。當SDS的API需要對SDS進行修改時,會先檢查SDS的空間是否滿足修改所需要的要求,不滿足,會自動擴容至執行修改所需要的大小,然後才執行修改操作。所以使用SDS不需要手動修改SDS所需空間的大小,也不會出現上述緩衝區溢位的問題。
2.3 減少修改字串時,記憶體帶來的記憶體重分配次數
以C字串而言:
- 如果執行字串拼接,程式需要在執行之前先通過記憶體重分配來擴充套件底層陣列的空間大小,否則可能會出現緩衝區溢位
- 如果是縮短字串,程式需要在執行之前通過記憶體重分配來釋放字串不再使用的那部分空間,否則可能會產生記憶體洩漏。
如果字串頻繁被修改,每次都需要執行一次記憶體重分配,光是這個步驟就會消耗一大部分時間。為了解決這個問題,SDS通過未分配空間解除了字串長度和底層陣列長度的關聯。在SDS中,buf陣列的長度不一定是字元數量加1,因為包括了未被使用的位元組,就是我們上述提到的free屬性。
對未分配空間,SDS有空間預分配和惰性空間釋放兩種策略。
2.3.1 空間預分配
空間預分配用來優化字串增長。當發生修改,不僅會為SDS分配修改所需要的空間,還會為SDS分配額外未使用的空間。
- 如果修改後,SDS的len小於1M,那麼程式分配free的空間和len相同。即如果修改之後len為15位元組,則buf實際長度15+15+1 = 31位元組
- 如果修改後,SDS的len大於1M,程式分配free空間1M。即如果修改後len為10M,則buf實際長度10M + 1M +1 byte
2.3.2 惰性空間釋放
惰性空間釋放主要用於優化字串縮短。當執行修改時,不立即通過記憶體分配策略來回收多出來的位元組,而是使用free屬性將其記錄下來,等待將來使用。當然,SDS也提供了相關的API,可以讓我們真正釋放掉未使用的空間。
2.4 二進位制安全
C字串必須符合一定的編碼規範,如ASCII,且除了字串末尾,不能包含空字元,否則會影響長度的判斷,所以C也之只能儲存文字相關的陣列。
而SDS都會以處理二進位制的方式來處理buf中的資料,程式不會對其進行任何限制過濾,所以redis還可以儲存非文字以外的二進位制資料。
2.5 相容部分C函式
SDS的buf末尾設定空字元,可以方便使用C的一些函式,而不用自己寫一套類似的了。
以上,做一個對比總結:
C字串 | SDS |
獲取字串長度複雜度O(n) | 獲取字串長度複雜度O(1) |
API可能會造成緩衝區溢位 | API不會造成緩衝區溢位 |
修改字串長度N次必然有N次記憶體重分配 | 修改字串長度N次最多有N次記憶體重分配 |
只能儲存文字資料 | 可以儲存除文字外的其他二進位制資料 |
可以使用所有<string.h>庫中的函式 | 可以使用部分<string.h>庫中的函式 |
以上就是本篇內容,其他SDS函式可參考部落格:https://blog.csdn.net/u010765526/article/details/89071207
3. 總結
Redis只會用C字串作為字面量,即一些不會對字串進行修改的地方,如列印日誌。在大多數情況下,都使用SDS作為字串表示。
除了用來儲存資料庫中的字串值外,SDS還被用做緩衝區,如AOF中的緩衝區,以及客戶端狀態中的輸入緩衝區。