一 實現原理
1.1 c語言字符串
以空字符串結尾的字符數組,比如hello在C語言中,經過一系列算法分配內存後,再產生出圖中結構代表字符串'hello!~'。
使用長度爲N+1的字符數組來表示字符串,最後總加一個'\0'代表結尾。
1.2 php字符串
底層爲C,結構如代碼所示:
1.3 redis字符串
和php的字符串有類似之處,redis作者封裝了一個名爲SDS的結構體來表示字符串,如圖所示:
那它爲啥要這樣做呢,我們在下面小結探討~,先說一下結構中各個屬性的作用:
-
len:
標識該字符串長度( 結合PHP的字符串結構體想想有什麼好處~ )
-
free:
標識該redis字符串未使用空間大小,它有着自己的分配策略(下面簡單介紹)
-
buf:
類似一個C語言的字符串,以'\0'結尾,方便重用C語言的一些字符串函數(說白了就是當做C語言的字符串用,但是\0後面會有長度free的空間,對於C函數來說不影響)
二 對比
2.1 查詢複雜度
2.1.1 c語言
由於C語言的字符串結構使然,它只能通過遍歷加計數來獲取字符串長度,很簡單,O(N)
2.1.2 PHP、Redis
這兩個一起說,是因爲它們都在自己的字符串結構體中內置了長度的屬性,直接O(1)獲取該屬性的值即可得到字符串長度
2.2 摳門C之申請與釋放
C作爲可操作內存的一門語言,它是比較摳門的,不會動態爲我們分配內存。我們需要手動去申請內存儲存數據,在我們不需要用到某些內存的時候,也要去手動將其釋放(垃圾回收、GC)。
2.2.1 緩衝區溢出 忘記申請
redis爲什麼要這麼設計呢,我們先簡單瞭解一下C語言中的 緩衝區溢出 問題
我們想在hello!~後追加 得到 hello!~ world~! 但是str2意外被修改
2.2.2 內存泄漏 忘記釋放
標題我們說忘記釋放,和申請分配相對應,如果我們在C中想要做到縮短字符串的操作,比如我們把str1改爲hello之後,沒有去釋放~!所佔用的空間,就會造成 內存泄漏 ,產生空佔內存。
2.2.3 Redis、PHP如何做
PHP作爲一門“高級語言”,有自己的動態分配和GC,Redis也一樣。上面我們瞭解到,C語言的字符串操作相對比較麻煩,那麼它的優點呢?比較明顯的是節省內存,我們也從圖片中得知,PHP和Redis的字符串結構相對於C來說有其他附加的屬性,它們都會佔用一定的空間。反之,我佔用這麼多空間,圖啥?一句話:空間換時間。
Redis作爲一種高性能的緩存服務,它需要對客戶端的請求做到快速響應。字符串的更改操作對於Redis來說也比較頻繁,如果像C那樣每次都需要申請內存和GC,對性能來說影響是比較大的,更不用說像strlen之類的操作了,直接將O(N)變成了O(1),典型的實現空間換時間。
那麼它具體是怎麼做的呢?
2.3 空間換時間
2.3.1 預分配
當我們對某個string類型的key執行set後,如果字符串的長度增長,Redis不僅對字符串分配相應長度的空間,還會進行預分配給一定的未使用空間,該未使用空間的長度由free屬性標識。機制如下:
-
如果修改之後len長度小於1MB,就分配和len長度一樣的空間。
-
如果修改之後len長度大於1MB,就分配1MB空間。
舉例:如果str修改之後字符串大小爲30MB,那麼實際分配長度爲30MB(len)+1MB(free)+1byte(\0),假如我們繼續修改str 長度爲30MB+500KB,那Redis就不需要重新申請內存空間,直接利用free的1MB空間即可。嗯~ 空間換來了時間
2.3.2 懶釋放
當我們對某個string類型的key執行set後,如果字符串的長度減少,Redis將字符串直接放進當前key對應的空間中,多出來的空間不會被立即釋放,多出來的空間由free屬性標識,以便即將到來的字符串增長操作。舉個例子:
set str helloworld;
set str hello;
set str helloredis;
執行第二、三個命令發生了什麼呢
helloworld\0 佔用11個字符空間,此時free爲0
hello\0 佔用6個字符空間,此時free爲5
hellophp\0 佔用9個空間,php直接利用free的空間,不需要重新申請內存,此時free爲2
2.4 二進制安全
講這點肯定是和C有區別滴,C語言的字符串結尾用\0 空字符來判定。假設有個文本 redis\0!,在C中!是會被忽略的。所以C中的字符串並不能保存比較特殊的數據。得益於Redis的字符串結構體,我們用len屬性來判斷字符串是否結束,所以數據是存的什麼樣,取出來還是原來的樣子。
三 總結
C字符串 | Redis字符串 |
計算長度需要遍歷 | 直接取len的值 |
手動申請釋放內存 | len和free配合Redis機制不需要考慮C中此類問題 |
二進制不安全 | 二進制安全 |
頻繁操作慢,每次內存分配 | 頻繁操作快,不需要每次分配內存 |