Redis的字符串采用的是自定義的struct,名字叫做簡單動態字符串(simple dynamic string,SDS)。
結構如下:
struct sdshdr{
int len;
int free;
char buf[];
};
采用如此結構的好處是:
【1】獲取length的時候復雜度為O(1),不需要O(n);
【2】動態分配空間,避免緩沖區溢出,避免每次修改或者append都重新分配;
【3】二進制安全;
關於第一點顯而易見,第二點,為了減少修改字符串帶來的內存重分配次數,redis采用了2個措施:
1)空間預分配;假設如下sds(狀態【0】),執行命令【sdscat(s,”redis”)】往其中append一個字符串“redis”之後將變為狀態【1】,發生了什麼呢?當sds發現當前free長度無法分配新添加的字符串時,將發生一次空間分配,如果修改之後sds長度小於1MB,則會分配總長度為【修改後長度】*2+1,即為(5+5)*2+1,1為結尾符號空間。如果修改後sds總長度大於等於1MB,則會分配【修改後總長度】+1MB的長度。假設再次執行命令【sdscat(s,”redis”)】,由於當前狀態【1】free=10,有足夠空間分配新加入的字符串,則不會發生空間分配操作,變為狀態【2】;
狀態【0】
struct sdshdr{
int len=5;
int free=4;
char buf[]={'h','e','l','l','o','\0','','','',''};
};
狀態【1】:
struct sdshdr{
int len=10;
int free=10;
char buf[]={'h','e','l','l','o','r','e','d','i','s','\0',...};
};
狀態【2】:
struct sdshdr{
int len=15;
int free=5;
char buf[]={'h','e','l','l','o','r','e','d','i','s','r','e','d','i','s','\0',...};
};
2)惰性空間釋放;現在假設從狀態【2】執行命令【sdstrim(s,”re”)】,移除sds中所有的’r’,’e’,將轉換為狀態【3】; 此時並沒有釋放空間,len+free依然等於20;這樣做的目的是避免了縮短字符串時的內存重分配操作,並且未將來的字符串擴充預留了空間 ; 當然也可以使用【sdsfree】真正釋放sds;
狀態【3】:
struct sdshdr{
int len=11;
int free=9;
char buf[]={'h','e','l','l','o','d','i','s','d','i','s','\0',...};
};
關於第三點,SDS的buf屬性稱為字節數組,保存的是二進制數據;同時由於使用len字段來判斷字符串是否結束,所以是安全的,不會出現c字符串中以空格作為結尾判斷,導致字符串被截斷的問題。