Redis内部数据结构详解之简单动态字符串(sds)
本文所引用的源碼全部來自Redis2.8.2版本。
Redis中簡單動態(tài)字符串sds數據結構與API相關文件是:sds.h, sds.c。
轉載請注明,本文出自:http://blog.csdn.net/acceptedxukai/article/details/17482611
預備知識
下面介紹有關sizeof計算參數所占字節(jié)數的部分實例,方便下面對sds數據結構地址的計算理解
[cpp]?view plain?copy
簡單解釋下上述sizeof的結果值,前兩個等于4是因為指針;第三個值等于12是因為len占4個字節(jié),char str[5]實際應該占5個字節(jié),但是由于計算機內存對齊的原因其實際占8個字節(jié);最后一個等于4,因為char str[]沒有實際長度,不被分配內存。
了解sizeof之后還需要了解stdarg.h中的va_list, va_start,va_end,va_copy的知識,這個在網上有很多就不多解釋了。
簡單動態(tài)字符串sds與char*對比
sds在Redis中是實現字符串對象的工具,并且完全取代char*.
char*的功能比較單一,不能實現Redis對字符串高效處理的需求,char*的性能瓶頸主要在:計算字符串長度需要使用strlen函數,該函數的時間復雜度是O(N),而在Redis中計算字符串長度的操作十分頻繁,O(N)的時間復雜度完全不能接受,sds實現能在O(1)時間內得到字符串的長度值;同時,在處理字符串追加append操作時,如果使用char*則需要多次重新分配內存操作。
簡單動態(tài)字符串sds數據結構
[cpp]?view plain?copy
通過增加len字段,就可以實現在O(1)時間復雜度內得到字符串的長度,增加free字段,在需要append字符串時,如果free的值大于等于需要append的字符串長度,那么直接追加即可,不需要重新分配內存。sizeof(sdshdr) = 8.?
簡單動態(tài)字符串sds中函數API
| 函數名稱 | 作用 | 復雜度 |
| sdsnewlen | 創(chuàng)建一個指定長度的sds,接受一個指定的C字符串作為初始化值 | O(N) |
| sdsempty | 創(chuàng)建一個只包含空字符串””的sds | O(N) |
| sdsnew | 根據給定的C字符串,創(chuàng)建一個相應的sds | O(N) |
| sdsdup | 復制給定的sds | O(N) |
| sdsfree | 釋放給定的sds | O(1) |
| sdsupdatelen | 更新給定sds所對應的sdshdr的free與len值 | O(1) |
| sdsclear | 清除給定sds的buf,將buf初始化為””,同時修改對應sdshdr的free與len值 | O(1) |
| sdsMakeRoomFor | 對給定sds對應sdshdr的buf進行擴展 | O(N) |
| sdsRemoveFreeSpace | 在不改動sds的前提下,將buf的多余空間釋放 | O(N) |
| sdsAllocSize | 計算給定的sds所占的內存大小 | O(1) |
| sdsIncrLen | 對給定sds的buf的右端進行擴展或縮小 | O(1) |
| sdsgrowzero | 將給定的sds擴展到指定的長度,空余的部分用\0進行填充 | O(N) |
| sdscatlen | 將一個C字符串追加到給定的sds對應sdshdr的buf | O(N) |
| sdscpylen | 將一個C字符串復制到sds中,需要依據sds的總長度來判斷是否需要擴展 | O(N) |
| sdscatprintf | 通過格式化輸出形式,來追加到給定的sds | O(N) |
| sdstrim | 對給定sds,刪除前端/后端在給定的C字符串中的字符 | O(N) |
| sdsrange | 截取給定sds,[start,end]字符串 | O(N) |
| sdscmp | 比較兩個sds的大小 | O(N) |
| sdssplitlen | 對給定的字符串s按照給定的sep分隔字符串來進行切割 | O(N) |
Redis中sds實現的細節(jié)解析
[cpp]?view plain?copy
上述兩個函數sdslen, sdsavail分別用來計算給定的sds的字符串長度和給定的sds空余的字節(jié)數。仔細觀察會發(fā)現函數的參數是sds即char *,接著通過一行代碼就能得到給定sds所對應的sdshdr數據結構,貌似很神奇的樣子啊!
看Redis中初始化一個sds的代碼
[cpp]?view plain?copy
核心函數是sdsnewlen,sh = zmalloc(sizeof(struct sdshdr)+initlen+1)為sdshdr數據結構分配內存,該段內存分為兩個部分:sdshdr數據結構所占的內存數sizeof(sdshdr),我們知道其值為8;initlen+1為sdshdr數據結構中buf的內存。而sdsnewlen函數的返回值是buf的首地址,這樣在看sdslen函數,通過給定的sds首地址減去sizeof(sdshdr),那么就應該是該sds所對應的sdshdr數據結構首地址,自然就能得到sh->len與sh->free。這種操作真的很神奇,這就是C語言指針的妙用,而且使用這種方式,很好的隱藏了sdshdr數據結構,對外接口全部同C字符串類似,卻達到了求取sds字符串長度時間復雜度O(1)與降低append操作頻繁申請內存的效果。
簡單動態(tài)字符串sds空間擴展操作解析
sds模塊的函數都比較簡單,不一一介紹,主要講解sds如何對空間進行擴展的,擴展操作主要在append操作的時候使用。
[cpp]?view plain?copy
小結
Redis的簡單動態(tài)字符串sds對比C語言的字符串char*,有以下特性:
1) 可以在O(1)的時間復雜度得到字符串的長度
2) 可以高效的執(zhí)行append追加字符串操作
3) 二進制安全
sds通過判斷當前字符串空余的長度與需要追加的字符串長度,如果空余長度大于等于需要追加的字符串長度,那么直接追加即可,這樣就減少了重新分配內存操作;否則,先用sdsMakeRoomFor函數先對sds進行擴展,按照一定的機制來決定擴展的內存大小,然后再執(zhí)行追加操作,擴展后多余的空間不釋放,方便下次再次追加字符串,這樣做的代價就是浪費了一些內存,但是在Redis字符串追加操作很頻繁的情況下,這種機制能很高效的完成追加字符串的操作。
由于sds其他的函數比較簡單,如果有問題的可以在回復中提出。
指出一點2.8源碼中sds作者作出的注釋有一處是錯誤的,具體就不列出了。
最后感謝黃健宏(huangz1990)的Redis設計與實現及其他對Redis2.6源碼的相關注釋對我在研究Redis2.8源碼方面的幫助。
來源:http://blog.csdn.net/acceptedxukai/article/details/17482611
總結
以上是生活随笔為你收集整理的Redis内部数据结构详解之简单动态字符串(sds)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: php 实现进制相互转换
- 下一篇: linux cmake编译源码,linu