Redis系統當中,針對字串進行的更完善的封裝,建立了一個動態字串,並建構了大量的實用api。相關的實作程式碼為sds.h及sds.c,以下為我的原始碼閱讀筆記。內容較多,逐步更新
- #
typedef char *sds; struct __attribute__ ((__packed__)) sdshdr5 { usigned char flags; char buf[]; }; struct __attribute__ ((__packed__)) sdshdr8 { uint8_t len; uint8_t alloc; unsigned char flags; char buf[]; }; struct __attribute__ ((__packed__)) sdshdr16 { uint16_t len; uint16_t alloc; unsigned char flags; char buf[]; }; struct __attribute__ ((__packed__)) sdshdr32 { uint32_t len; uint32_t alloc; unsigned char flags; char buf[]; }; struct __attribute__ ((__packed__)) sdshdr64 { uint64_t len; uint64_t alloc; unsigned char flags; char buf[]; }; #define SDS_TYPE_5 0 #define SDS_TYPE_8 1 #define SDS_TYPE_16 2 #define SDS_TYPE_32 3 #define SDS_TYPE_64 4 #define SDS_TYPE_MASK 7 #define SDS_TYPE_BITS 3 #define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T))); #define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T)))) #define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS)
以上是動態字串的結構體聲明及define聲明的函數。動態字串一共有5種類型,分別為不同長度的字串所實用。我在這裡稱之為動態字串的頭部。
sdshdr5:長度為小於32的字串
#sdshdr8:長度為小於256的字串
#sdshdr16:長度為小於2^16的字串
#sdshdr32:長度為小於2^32的字串。這裡有一點注意,若是機器的LONG_MAXbu不等於LLONG_MAX,則回傳sdshdr64類型。
sdshdr64:其他所有長度都實用此類。
sdshdr5這個類型不同於其他類型,它缺少len成員與alloc成員,它的判斷與處理都比較特別。但官方在程式碼有過一段註釋,如下:
/* Note: sdshdr5 is never used, we just access the flags byte directly.
* However is here to document the layout of type 5 SDS strings. */
註釋中說明,這個類型從未被使用,所以我們在這裡姑且不考慮它,而實際上,它的處理操作本質上與其他類型並沒有什麼區別。方便起見,我們以通用的類型進行研究。
結構體當中,有四個類,分別為len、alloc、flags與buf。
len:字串的長度。
alloc:字串記憶體總大小,注意,alloc不同於len。 len是實際字串的長度,而alloc,則是實際分配的記憶體大小(不包含sds頭與結尾的'\0'的大小)。為了減少字串內容增加時反覆的重新申請內存,redis當中會申請更多內存以備使用。當字串大小小於1MB的時候,申請兩倍大小的記憶體使用,當字串大小大於等於1MB的時候,多申請1MB的記憶體以備使用。詳細的設定可以看之後的sdsMakeRoomFor函數的解析。
flags:作為區分不同類型的標記使用,暫時只使用了低3位元來標記,高5位元暫未使用,也供以後增加新功能時使用。上面程式碼中define宣告的SDS_TYPE_*類型為對應的標記內容,用於區分不同的字串類型。例如flags等於SDS_TYPE_8時,則可以從字串開始位元組在向前17位元組,或字串頭部開始取得8位元組資料取得目前字串的實際長度。等於SDS_TYPE_16時,從字串開始位元組向前33位元組,或字串頭部開始取得16自己取得目前字串的實際長度。 flags與頭部資訊的配合使用,將在之後的函數解析裡面大量出現。
buf:实际存储字符串内容的数组,同传统数组一样,结尾需要'\0'字符。
在sdshdr的声明当中,我们可以看到 __attribute__ ((__packed__)) 关键字,这将使这个结构体在内存中不再遵守字符串对齐规则,而是以内存紧凑的方式排列。所以可以从字符串位置开始向前一个字节便可以获取flags信息了,比如buf[-1]。具体__attribute__ ((__packed__))与字符串对齐的内容请查看另一篇博客。
SDS_HDR_VAR函数则通过结构体类型与字符串开始字节,获取到动态字符串头部的开始位置,并赋值给sh指针。SDS_HDR函数则通过类型与字符串开始字节,返回动态字符串头部的指针。使用方式为可在之后的代码当中看到,具体define声明中的双'#'号的使用方式与意义,请看草另一篇博客。
sds比起传统的字符串,有着自己优势跟便利之处。
1、内存预分配:sds通过预先分配了更多的内存量,来避免频繁的申请、分配内存,无端的浪费了性能
2、惰性释放:sds作为普通字符串使用之时,可以通过在不同字节打上'\0'字符,来代表字符串的截断及减少长度,而不是需要清空多余的字节并释放它们,那些内存再之后的操作当中可以当做空闲内存继续使用。
3、二进制安全:作为非字符串使用存储数据之时,通过善用头部的len属性,可以存储一些包含'\0'字符的数据。当然,一定要善用len属性,api当中,如长度更新的函数,同样通过'\0'字符来判断结尾!
接下来开始介绍sds相关的api函数,第一批是声明、定义在sds.h文件内的静态函数,这些函数都是针对动态字符串头部的属性的获取与修改,简单易懂
//获取动态字符串长度 static inline size_t sdslen(const sds s) { unsigned char flags = s[-1];//获取头部信息中的flags属性,因内存紧密相连,可以直接通过这种方式获取 switch(flags&SDS_TASK_MASK) {//获取类型,SDS_TASK_MASK为7,所以flags&SDS_TASK_MASK等于flags case SDS_TYPE_5: return SDS_TYPE_5_LEN(flags);//SDS_TYPE_5类型的长度获取稍微不同,它的长度被定义在flags的高5位当中,具体可查看之后的sdsnewlen函数,或者下面的sdssetlen函数 case SDS_TYPE_8: return SDS_HDR(8,s)->len;//SDS_HDR函数通过类型与字符串开始字节获取头部,以此获取字符串的长度 case SDS_TYPE_16: return SDS_HDR(16,s)->len; case SDS_TYPE_32: return SDS_HDR(32,s)->len; case SDS_TYPE_64: RETURN SDS_HDR(64,S)->len; } return 0; } //获取动态字符串的剩余内存 static inline size_t sdsavail(const sds s) { unsigned char flags = s[-1];//获取flags switch(flags&SDS_TYPE_MASK) { case SDS_TYPE_5://SDS_TYPE_5直接返回0, return 0; case SDS_TYPE_8: { SDS_HDR_VAR(8,s);//通过SDS_HDR_VAR函数,将头部指针放置在sh变量 return sh->alloc - sh->len;//总内存大小 - 字符串长度,获取可用内存大小 } case SDS_TYPE_16: { SDS_HDR_VAR(16,s); return sh->alloc - sh->len; } case SDS_TYPE_32: { SDS_HDR_VAR(32,s); return sh->alloc - sh->len; } case SDS_TYPE_64: { SDS_HDR_VAR(64,s); return sh->alloc - sh-len; } } return 0; } //重置字符串长度 static inline void sdssetlen(sds s, size_t newlen) { unsigned char flags = s[-1];//获取flags switch(flags&SDS_TASK_MASK) { //SDS_TYPE_5的长度设置较为特殊,长度信息写在flags的高5位 case SDS_TYPE_5: { unsigned char *fp = ((unsigned char*)s)-1; *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS); } break; //其他类型则是统一修改len属性的值 case SDS_TYPE_8: SDS_HDR(8,s)->len = newlen; break; case SDS_TYPE_16: SDS_HDR(16,s)->len = newlen; break; case SDS_TYPE_32: SDS_HDR(32,s)->len = newlen; break; case SDS_TYPE_64: SDS_HDR(64,s)->len = newlen; break; } } //按照指定数值,增加字符串长度 static inline void sdsinclen(sds s, size_t inc) { unsigned char flags = s[-1];//获取flags switch(flags&SDS_TYPE_MASK) { //SDS_TYPE_5类型使用上面的函数,获取长度、更新、设置 case SDS_TYPE_5: { unsigned char *fp = ((unsigned char*)s)-1; unsigned char newlen = SDS_TYPE_LEN(flags)+inc; *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS); } break; //其他类型则直接通过SDS_HDR函数,更新len值 case SDS_TYPE_8: SDS_HDR(8,s)->len += inc; break; case SDS_TYPE_16: SDS_HDR(16,s)->len += inc; break; case SDS_TYPE_32: SDS_HDR(32,s)->len += inc; break; case SDS_TYPE_64: SDS_HDR(64,s)->len += inc; break; } } //获取动态字符串的总内存 static inline size_t sdsalloc(const sds s) { unsigned char flags = s[-1];//获取flags switch(flags&SDS_TASK_MASK) { //SDS_TYPE_5直接通过SDS_TYPE_5_LEN函数返回 case SDS_TYPE_5: return SDS_TYPE_5_LEN(flags); //其他类型则返回头部信息中的alloc属性 case SDS_TYPE_8: return SDS_HDR(8,s)->alloc; case SDS_TYPE_16: return SDS_HDR(16,s)->alloc; case SDS_TYPE_32: return SDS_HDR(32,s)->alloc; case SDS_TYPE_64: return SDS_HDR(64,s)->alloc; } return 0; } //重置字符串内存大小 static inline size_t sdssetalloc(sds s, size_t newlen) { unsigned cahr flags = s[-1];//获取flags switch(flags&SDS_TASK_MASK) { case SDS_TYPE_5: //官方注释,SDS_TYPE_5不做任何操作 /*Nothing to do, this type has no total allocation info. */ break; //其他类型直接修改头部信息中的alloc属性 case SDS_TYPE_8: SDS_HDR(8,s)->alloc = newlen; break; case SDS_TYPE_16: SDS_HDR(16,s)->alloc = newlen; break; case SDS_TYPE_32: SDS_HDR(32,s)->alloc = newlen; break; case SDS_TYPE_64: SDS_HDR(64,s)->alloc = newlen; break; } }
上述的几个函数,sdslen,sdsavail,sdssetlen,sdsinclen,sdsalloc,sdssetalloc函数,都是基本的头部属性操作函数。代码的难度也不大,可以直观的阅读、理解。
接下来的sds的相关api,数量有点多,之后的dict、zskiplist也是有大量api,挑部分代码较多,需要逐行理解的函数来记录、分析。
sdsnewlen:
//创建一个新的sds对象 sds sdsnewlen(const void *init, size_t initlen) { void *sh; sds s; char type = sdsReqType(initlen);//根据初始化长度获取对应结构体类型 if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;//若长度为0的则初始化为SDS_TYPE_8类型 int hdrlen = sdsHdrSize(type); unsigned char *fp; sh = s_malloc(hdrlen+initlen+1);//申请足够的内存,头部大小+初始化大小+结尾符 if (!init) memset(sh, 0, hdrlen+initlen+1); if (sh == NULL) return NULL; s = (char*)sh+hdrlen;//获取字符串起始字节 fp = ((unsigned char*)s)-1;//获取flags字节 switch(type) { //SDS_TYPE_5又是特立独行了,有自己的初始化方案,我都懒得说明了。。。 case SDS_TYPE_5: { *fp = type | (initlen << SDS_TYPE_BITS); break; } //其他类型通过SDS_HDR_VAR函数获取头部信息,并逐步初始化 case SDS_TYPE_8: { SDS_HDR_VAR(8,s); sh->len = initlen; sh->alloc = initlen; *fp = type; break; } case SDS_TYPE_16: { SDS_HDR_VAR(16,s); sh->len = initlen; sh->alloc = initlen; *fp = type; break; } case SDS_TYPE_32: { SDS_HDR_VAR(32,s); sh->len = initlen; sh->alloc = initlen; *fp = type; break; } case SDS_TYPE_64: { SDS_HDR_VAR(64,s); sh->len = initlen; sh->alloc = initlen; *fp = type; break; } } //根据init与initlen,将内容复制给字符串 if (initlen && init) memcpy(s, init, initlen); //打上结尾符 s[initlen] = '\0'; return s; }
sdsnewlen根据参数给予的init字符串与initlen初始长度,生成并返回一个动态字符串。代码都打上了注释,阅读已经没什么难度了。
相关推荐:
以上是Redis原始碼解析的詳細內容。更多資訊請關注PHP中文網其他相關文章!

PHP用於構建動態網站,其核心功能包括:1.生成動態內容,通過與數據庫對接實時生成網頁;2.處理用戶交互和表單提交,驗證輸入並響應操作;3.管理會話和用戶認證,提供個性化體驗;4.優化性能和遵循最佳實踐,提升網站效率和安全性。

PHP在數據庫操作和服務器端邏輯處理中使用MySQLi和PDO擴展進行數據庫交互,並通過會話管理等功能處理服務器端邏輯。 1)使用MySQLi或PDO連接數據庫,執行SQL查詢。 2)通過會話管理等功能處理HTTP請求和用戶狀態。 3)使用事務確保數據庫操作的原子性。 4)防止SQL注入,使用異常處理和關閉連接來調試。 5)通過索引和緩存優化性能,編寫可讀性高的代碼並進行錯誤處理。

在PHP中使用預處理語句和PDO可以有效防範SQL注入攻擊。 1)使用PDO連接數據庫並設置錯誤模式。 2)通過prepare方法創建預處理語句,使用佔位符和execute方法傳遞數據。 3)處理查詢結果並確保代碼的安全性和性能。

PHP和Python各有優劣,選擇取決於項目需求和個人偏好。 1.PHP適合快速開發和維護大型Web應用。 2.Python在數據科學和機器學習領域佔據主導地位。

PHP在電子商務、內容管理系統和API開發中廣泛應用。 1)電子商務:用於購物車功能和支付處理。 2)內容管理系統:用於動態內容生成和用戶管理。 3)API開發:用於RESTfulAPI開發和API安全性。通過性能優化和最佳實踐,PHP應用的效率和可維護性得以提升。

PHP可以輕鬆創建互動網頁內容。 1)通過嵌入HTML動態生成內容,根據用戶輸入或數據庫數據實時展示。 2)處理表單提交並生成動態輸出,確保使用htmlspecialchars防XSS。 3)結合MySQL創建用戶註冊系統,使用password_hash和預處理語句增強安全性。掌握這些技巧將提升Web開發效率。

PHP和Python各有優勢,選擇依據項目需求。 1.PHP適合web開發,尤其快速開發和維護網站。 2.Python適用於數據科學、機器學習和人工智能,語法簡潔,適合初學者。

PHP仍然具有活力,其在現代編程領域中依然佔據重要地位。 1)PHP的簡單易學和強大社區支持使其在Web開發中廣泛應用;2)其靈活性和穩定性使其在處理Web表單、數據庫操作和文件處理等方面表現出色;3)PHP不斷進化和優化,適用於初學者和經驗豐富的開發者。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

VSCode Windows 64位元 下載
微軟推出的免費、功能強大的一款IDE編輯器

SublimeText3漢化版
中文版,非常好用

Dreamweaver Mac版
視覺化網頁開發工具

mPDF
mPDF是一個PHP庫,可以從UTF-8編碼的HTML產生PDF檔案。原作者Ian Back編寫mPDF以從他的網站上「即時」輸出PDF文件,並處理不同的語言。與原始腳本如HTML2FPDF相比,它的速度較慢,並且在使用Unicode字體時產生的檔案較大,但支援CSS樣式等,並進行了大量增強。支援幾乎所有語言,包括RTL(阿拉伯語和希伯來語)和CJK(中日韓)。支援嵌套的區塊級元素(如P、DIV),

Atom編輯器mac版下載
最受歡迎的的開源編輯器