redis數據結構-string
redis最常見數據結構之一就是string,當然這不是簡單的C字符串,它是一種簡單動態字符串,在redis中這種數據類型既能包含C字符串的功能同時又能保持redis的高性能。
更過關於redis操作和學習教程請進入碼神營地官網:www.icodegod.com
-
數據結構
redis中的string數據結構是一個結構體,在redis 2.8中是這麼定義的:其中len就是對應buff數組中的實際字符串長度也即已使用空間,free就是該buff數組剩餘空間大小即未分配空間,單位都是字節,對應的數據結構圖如下:
從圖可以看到buf中還是遵從C語言字符串風格,使用’\0’來標誌字符串結束,但是這個結束符卻不算做該字符串對象總長度,len標誌了buf的字符串真是長度,這樣做避免計算buf字符串長度進行遍歷操作,可以很方便的得知該字符串對象長度。在redis 4新版本中是這麼定義的:
這裏區分了sds類型,按照具體的string長度來精確劃分使用不同的結構體類型,目的只有一個,那就是爲了節省內存空間。比如裏面使用的__attribute__ ((packed)),用來告知編譯器取消編譯器默認內存對齊方式,這裏的 packed 是一字節對齊,當然這個使用__attribute__ 是GCC特有語法,GCC編譯器是非緊湊模式內存對齊的,此處一字節就成爲了緊湊內存對齊了,當然也都是爲了速度考慮。
可以看到該結構體中的幾個組成部分:len表示已使用長度,alloc表示總空間大小,在sds.c中還計算了aviallen的大小,這個大小等於alloc-len,flag標誌類型,該部分佔一個字節,但是隻有3個位使用,因爲5種類型,3bytes就可以搞定,剩餘5bytes是未使用,最後buf就是一個可變長動態數組,用於存儲字符串。當然內部還有支持該buf擴容的api函數,以及一系列字符串操作函數。 -
空間分配
從sds結構體中可以看到有一個buf的指針,指向一個字符串數組,當我們進行redis字符串對象的拷貝,拼接等操作時,可能當前的數組大小並不能滿足需要,那麼此時就需要考慮使用sds api來實現動態空間分配,這個分配規則如下:
1.如果修改後的sds結構體中的len大小< 1 MB,那麼空閒空間free分配大小則爲len大小。
如果len是5 bytes,free爲 0 bytes,想要拼接一個字符串’hello’,那麼此時還需要5字節的額外空間來滿足需要,那麼擴容前整個sds總共大小爲:5+0+1=6 bytes,因爲buf中還有一字節的’\0’,採用小容量擴容方案擴容後大小爲:10+10+1 = 21 bytes。如圖:
2.若修改後len > 1 MB,那麼空閒空間free則爲1 MB。
在sds.h中可以看到有一個宏定義:
#define SDS_MAX_PREALLOC (1024*1024)
該宏定義定義了最大預分配空間大小爲1MB。
-
空間釋放
sds釋放空間和大多數經典的數據結構釋放方式一樣,採用的是惰性釋放,buf中釋放的空間會放在free中用作下次使用,就像STL中的內存池一樣,STL中內存池維持着一個16個大小的鏈表,有空間釋放時就回收放到內存池空閒空間,用作後續空間分配。如將上述中的’hello’字符串刪除,那麼空間結構變化如下: -
與C語言字符串的比較
#####1.獲取字符串長度1).redis的獲取字符串方式:因爲sds有一個len字段記錄了該字符串的長度,所以很容易以O(1)時間複雜度獲取到。
2).而C語言字符串則需要使用strlen函數來遍歷字符串計算長度,這樣的時間複雜度就是O(n),相比較而言,redis的效率會高很多。#####2.緩衝區安全
1).redis在進行字符串拼接,拷貝等操作之前會先檢查剩餘空間大小是否能滿足需要,如果不能滿足需要則會使用擴容規則來進行擴容,這樣不會因爲字符串操作空間不足而導致緩衝區溢出,產生安全隱患。
2).C語言的字符串操作函數進行拷貝和拼接時沒有進行安全檢測,判斷空間是否滿足,這樣操作可能會導致緩衝區溢出。#####3.字符串操作導致內存分配
1).redis中修改字符串長度比如進行字符串縮減,使用安全的api來進行內存預估,從而減少內存分配次數。
2).C語言中字符串在底層實現是一個字符串數組,如果想在該數組中進行刪減、替換、填充等操作,有N次操作就會有N次內存重新分配,因爲字符串數組必須是連續的。
#####4.數據類型
1).redis的sds的buf可以存儲文本和二進制數據。
2).C語言的字符串只能存儲文本。#####5.庫函數
1).redis只可以使用C語言string.h庫函數中的一部分函數。
2).C語言中的庫函數都可以用。