表达式类型的实现数据结构_Redis系列(九)底层数据结构之五种基础数据类型的实现...
- 前言
- 定義
- 字符串對象
- int
- raw
- embstr
- 浮點(diǎn)數(shù)如何保存?
- 編碼轉(zhuǎn)換條件
- 總結(jié)
- 列表對象
- 總結(jié)
- 集合對象
- intset
- hashtable
- 總結(jié)
- 有序集合對象
- ziplist 編碼
- skiplist 編碼
- 總結(jié)
- 散列對象
- ziplist 編碼
- hashtable 編碼
- 總結(jié)
- 全文總結(jié)
- 參考文章
- 聯(lián)系我
前言
Redis 已經(jīng)是大家耳熟能詳?shù)臇|西了,日常工作也都在使用,面試中也是高頻的會涉及到,那么我們對它究竟了解有多深刻呢?
我讀了幾本 Redis 相關(guān)的書籍,嘗試去了解它的具體實(shí)現(xiàn),將一些底層的數(shù)據(jù)結(jié)構(gòu)及實(shí)現(xiàn)原理記錄下來。
本文將介紹 Redis 中 五種基礎(chǔ)數(shù)據(jù)類型 的實(shí)現(xiàn)方法。 這五種基本類型基本覆蓋了我們業(yè)務(wù)中使用的 80%的場景,對面試也覆蓋至少 90%.(其中重點(diǎn)當(dāng)然是有序集合以及散列結(jié)構(gòu)咯).
定義
在前面的八篇文章中,我們詳細(xì)的介紹了 Redis 中的 8 種基本數(shù)據(jù)結(jié)構(gòu),但是眾所周知,Redis 常用的數(shù)據(jù)類型有五種。包括,字符串,列表,集合,有序集合,哈希。
而這五種數(shù)據(jù)類型,底層就是用前面介紹的數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)的,當(dāng)然,并不是直接一對一的綁定關(guān)系,而是采用了精妙的設(shè)計(jì),構(gòu)建了一個對象系統(tǒng)。
熟悉 OOP 編程的讀者,可能很快就能想到為什么要這么設(shè)計(jì)了,對象系統(tǒng)帶來的好處是非常多的,但是并不在這一篇文章中講。這里只是提到對象系統(tǒng),讓大家對于五種數(shù)據(jù)類型為什么可以用一些花里胡哨的方法來實(shí)現(xiàn),有一個初步的了解。
接下來將逐一分析五種數(shù)據(jù)類型的底層實(shí)現(xiàn)數(shù)據(jù)結(jié)構(gòu),及實(shí)現(xiàn)方式(編碼)之間的切換條件。
注:后續(xù)提到五種數(shù)據(jù)類型,用 xx 對象來指代。比如 字符串對象,列表對象。提到的底層數(shù)據(jù)結(jié)構(gòu),用全稱來講。
字符串對象
涉及到的數(shù)據(jù)結(jié)構(gòu),SDS, 強(qiáng)烈建議閱讀本系列第一篇文章。
字符串對象的底層實(shí)現(xiàn)有三種可能:int, raw, embstr.
int
如果一個字符串對象,保存的值是一個整數(shù)值,并且這個整數(shù)值在 long 的范圍內(nèi),那么 redis 用整數(shù)值來保存這個信息,并且將字符串編碼設(shè)置為 int.
比如:
raw
如果字符串對象保存的是一個字符串, 并且長度大于 32 個字節(jié),它就會使用前面講過的SDS(簡單動態(tài)字符串)數(shù)據(jù)結(jié)構(gòu)來保存這個字符串值,并且將字符串對象的編碼設(shè)置為raw.
embstr
如果字符串對象保存的是一個字符串, 但是長度小于 32 個字節(jié),它就會使用embstr來保存了,embstr編碼不是一個數(shù)據(jù)結(jié)構(gòu),而是對 SDS 的一個小優(yōu)化,當(dāng)使用 SDS 的時候,程序需要調(diào)用兩次內(nèi)存分配,來給 字符串對象 和 SDS 各自分配一塊空間,而embstr只需要一次內(nèi)存分配,因?yàn)樗枰目臻g很少,所以采用 連續(xù)的空間保存,即將 SDS 的值和 字符串對象的值放在一塊連續(xù)的內(nèi)存空間上。這樣能在短字符串的時候提高一些效率。
比如:
浮點(diǎn)數(shù)如何保存?
redis 的字符串?dāng)?shù)據(jù)類型是支持保存浮點(diǎn)數(shù),并且支持對浮點(diǎn)數(shù)進(jìn)行加減操作,但是 redis 在底層是把浮點(diǎn)數(shù)轉(zhuǎn)換成字符串值,之后走上面三種編碼的規(guī)則的。對浮點(diǎn)數(shù)進(jìn)行操作時,也是從字符串轉(zhuǎn)換成浮點(diǎn)數(shù)進(jìn)行計(jì)算,然后再轉(zhuǎn)換成字符串進(jìn)行保存的。
編碼轉(zhuǎn)換條件
這塊的知識其實(shí)是很符合我們的認(rèn)知的,比如 int編碼只可以保存整數(shù),那么當(dāng)我們對一個 int 編碼的字符串對象,修改它的值,它自然就會使用 raw 編碼了。
但是有一個特性,Redis 沒有為embstr編碼提供任何的修改操作,這也就是為什么它只是個編碼而不是一個數(shù)據(jù)結(jié)構(gòu)的原因。
所以在 Redis 中,embstr編碼的值其實(shí)是 只讀的,只要發(fā)生修改,立刻將編碼轉(zhuǎn)換成 raw.
總結(jié)
字符串對象底層的數(shù)據(jù)結(jié)構(gòu)或者說編碼有三種,分別是 int, raw, embstr. 他們之間的使用條件如下:
編碼 | 使用條件 --- | --- int | 可以用 long 保存的整數(shù) embstr | 字符串長度小于 32 字節(jié)(或者浮點(diǎn)數(shù)轉(zhuǎn)換后滿足) raw | 長度大于 32 的字符串
列表對象
涉及到的數(shù)據(jù)結(jié)構(gòu),壓縮列表, 雙向鏈表, 快速列表, 強(qiáng)烈建議閱讀本系列的第 二,三,四 篇文章。
在 Redis 3.2 版本之前,列表對象底層由 壓縮列表和雙向鏈表配合實(shí)現(xiàn),當(dāng)元素?cái)?shù)量較少的時候,使用壓縮列表,當(dāng)元素?cái)?shù)量增多,就開始使用普通的雙向鏈表保存數(shù)據(jù)。
但是這種實(shí)現(xiàn)方式不夠好,雙向鏈表中的每個節(jié)點(diǎn),都需要保存前后指針,這個內(nèi)存的使用量 對于 Redis 這個內(nèi)存數(shù)據(jù)庫來說極其不友好。
因此在 3.2 之后的版本,作者新實(shí)現(xiàn)了一個數(shù)據(jù)結(jié)構(gòu),叫做 quicklist. 所有列表的底層實(shí)現(xiàn)都是這個數(shù)據(jù)結(jié)構(gòu)了。它的底層實(shí)現(xiàn)基本上就是將 雙向鏈表和壓縮列表進(jìn)行了結(jié)合,用雙向的指針將壓縮列表進(jìn)行連接,這樣不僅避免了壓縮列表存儲大量元素的性能壓力,同時避免了雙向鏈表連接指針占用空間過多的問題。
具體的原理講解請 閱讀對應(yīng)的文章,這里不再贅述。
總結(jié)
編碼 | 使用條件 --- | --- quicklist | 所有數(shù)據(jù)
集合對象
涉及到的數(shù)據(jù)結(jié)構(gòu):intset, dict(hashtable), 強(qiáng)烈建議閱讀本系列第五,第六篇文章。
集合對象的編碼可以是 intset 或者 hashtable(字典) .
intset
當(dāng)集合中的所有元素都是整數(shù),且元素的數(shù)量不大于 512 個的時候,使用 intset 編碼。
intset 編碼時,底層使用 intset數(shù)據(jù)結(jié)構(gòu)。
hashtable
當(dāng)元素不符合全部為整數(shù)值且元素個數(shù)小于 512時,集合對象使用的編碼方式為 hashtable.
字典的每一個鍵都是一個字符串對象,其中保存了集合里的一個元素,字典的值全部被設(shè)置為 NULL.
總結(jié)
編碼 | 使用條件 --- | --- intset | 所有元素都是整數(shù)且元素個數(shù)小于 512 hashtable | 其他數(shù)據(jù)
有序集合對象
涉及到的數(shù)據(jù)結(jié)構(gòu),壓縮列表, 跳躍表, 字典, 強(qiáng)烈建議閱讀本系列 第三篇,第六篇,第七篇文章。
有序集合對象的編碼可以是 ziplist 以及skiplist.
ziplist 編碼
當(dāng)使用 ziplist 編碼時,有序集合對象的實(shí)現(xiàn)數(shù)據(jù)結(jié)構(gòu)為ziplist(聽起來像句廢話), 每個集合的元素 (key-value), 使用兩個緊挨著的壓縮列表的節(jié)點(diǎn)來表示,第一個節(jié)點(diǎn)保存集合元素的成員 (member), 第二個節(jié)點(diǎn)保存集合元素的分支 (score).
在壓縮列表的內(nèi)部,集合元素按照分值從小到大進(jìn)行排序。
skiplist 編碼
當(dāng)使用 skiplist 編碼的時候,內(nèi)部使用zset 來實(shí)現(xiàn)數(shù)據(jù)的保存,zset的定義如下:
typedef struct zset{zskiplist *zsl;dict *dict; }zset;為什么需要同時使用跳躍表以及字典呢?
其實(shí)如果我們細(xì)想,單獨(dú)使用字典或者跳躍表,都是可以實(shí)現(xiàn)有序集合的所有功能的,但是性能太差勁了。
- 當(dāng)我們只使用字典來實(shí)現(xiàn),我們可以以 O(1) 的時間復(fù)雜度獲取成員的分值,但是由于字典是無序的,當(dāng)我們需要進(jìn)行范圍性操作的時候,需要對字典中的所有元素進(jìn)行排序,這個時間復(fù)雜度至少需要 O(nlogn).
- 當(dāng)我們只使用跳躍表來實(shí)現(xiàn),我們可以在 O(logn) 的時間進(jìn)行范圍排序操作,但是如果要獲取到某個元素的分值,時間復(fù)雜度也是 O(logn).
因此,將字典和跳躍表結(jié)合進(jìn)行使用,可以在 O(1) 的時間復(fù)雜度下完成查詢分值操作,而對一些范圍操作,使用跳躍表可以達(dá)到 O(logn) 的是纏綿復(fù)雜度。
可以看到,我在上一次的例子中,添加了一個很長的 key 之后,有序集合的編碼方式就成為了skiplist.
總結(jié)
編碼 | 使用條件 --- | --- ziplist | 元素?cái)?shù)量少于 128 且 所有元素成員的長度小于 64 字節(jié) skiplist | 不滿足上述條件的其他情況
散列對象
涉及到的數(shù)據(jù)結(jié)構(gòu),壓縮列表, 字典, 強(qiáng)烈建議閱讀本系列 第三篇,第六篇文章。
哈希對象的編碼可以是ziplist或者h(yuǎn)ashtable.
ziplist 編碼
ziplist 編碼下的哈希對象,使用了壓縮列表作為底層實(shí)現(xiàn)數(shù)據(jù)結(jié)構(gòu),用兩個連續(xù)的壓縮列表節(jié)點(diǎn)來表示哈希對象中的一個鍵值對。實(shí)現(xiàn)方式類似于上面的有序集合的場景。
如圖中所示,當(dāng)我放入了兩個簡單的鍵值對,此時哈希對象的編碼為 ziplist.
hashtable 編碼
這是對 hashtable 最直觀的應(yīng)用了~
哈希結(jié)構(gòu)本身在結(jié)構(gòu)上和字典 (hashtable) 就頗為相似,因此哈希對象中的每一個鍵值對都是字典中的一個鍵值對。
- 字典的每一個鍵都是一個字符串對象,對象中保存了鍵值對的鍵。
- 字典的每一個值都是一個字符串對象,對象中保存了鍵值對的值。
如圖中所示,當(dāng)我在上一個示例中額外加入一個很長的值,那么編碼方式就來到了hashtable.
總結(jié)
編碼 | 使用條件 --- | --- ziplist | 鍵值對的鍵和值的長度都小于 64 字節(jié),且 鍵值對個數(shù)小于 512. hastable | 不滿足上述條件的其他條件
全文總結(jié)
其實(shí)在前面的幾篇文章寫完之后,也就是在所有的底層數(shù)據(jù)結(jié)構(gòu)介紹完之后,所謂的Redis 的五種基礎(chǔ)數(shù)據(jù)類型的底層實(shí)現(xiàn)原理就已經(jīng)沒有了難度。
所有用到的底層數(shù)據(jù)結(jié)構(gòu)都知道了,剩下的無非是個排列組合問題以及各種實(shí)現(xiàn)方式之間的切換條件,然后這個條件也僅僅是了解性知識,強(qiáng)行記住也沒有必要。
這里把五種基礎(chǔ)數(shù)據(jù)類型的可能的編碼列出來方便理解及記憶。
基礎(chǔ)數(shù)據(jù)類型 | 可能的編碼方式 --- | --- 字符串 | int, raw, embstr 列表 | 之前是 ziplist 和 linkedlist, 現(xiàn)在全是 quicklist 了。 集合 | intset 或者 hashtable 有序集合 | ziplist 或者 skiplist, skiplist 編碼中使用了跳躍表+字典 散列 | ziplist 或者 hashtable
至于他們的轉(zhuǎn)換條件,如下:
參考文章
《Redis 的設(shè)計(jì)與實(shí)現(xiàn)(第二版)》
《Redis 深度歷險:核心原理和應(yīng)用實(shí)踐》
完。
聯(lián)系我
最后,歡迎關(guān)注我的個人公眾號【 呼延十 】,會不定期更新很多后端工程師的學(xué)習(xí)筆記。 也歡迎直接公眾號私信或者郵箱聯(lián)系我,一定知無不言,言無不盡。
以上皆為個人所思所得,如有錯誤歡迎評論區(qū)指正。
歡迎轉(zhuǎn)載,煩請署名并保留原文鏈接。
聯(lián)系郵箱:huyanshi2580@gmail.com
更多學(xué)習(xí)筆記見個人博客或關(guān)注微信公眾號 < 呼延十 >------>呼延十
總結(jié)
以上是生活随笔為你收集整理的表达式类型的实现数据结构_Redis系列(九)底层数据结构之五种基础数据类型的实现...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python自动化测试数据驱动_Pyth
- 下一篇: mysqlsql varchar类型只取