Redis实现之整数集合
?
整數集合
整數集合(insert)是集合鍵的底層實現之一,當一個集合只包含整數值元素,并且這個集合的元素數量不多時,Redis就會使用整數集合作為集合鍵的底層實現。舉個栗子,如果我們創建一個只包含五個元素的集合鍵,并且集合中的所有元素都是整數值,那么這個集合鍵的底層實現就會是整數集合:
127.0.0.1:6379> SADD numbers 1 3 5 7 9 (integer) 5 127.0.0.1:6379> SMEMBERS numbers 1) "1" 2) "3" 3) "5" 4) "7" 5) "9" 127.0.0.1:6379> OBJECT ENCODING numbers "intset"
整數集合的實現
整數集合(insert)是Redis用于保存整數值的集合抽象數據結構,它可以保存類型為int16_t、int32_t或者int64_t的整數值,并且保證集合中不會出現重復元素。
intset.h
typedef struct intset {//編碼方式uint32_t encoding;//集合包含的元素數量uint32_t length;//保存元素的數組int8_t contents[]; } intset;
contents數組是整數集合的底層實現:整數集合的每個元素都是contents數組中的一個數組項,各個項在數組中按值的大小從小到大有序地排列,并且數組中不包含任何重復項。length屬性記錄了整數集合包含的元素數量,也即是contents數組的長度。雖然intset結構將contents屬性聲明為int8_t類型的數組,但實際上contents數組并不保存任何int8_t類型的值,contents數組的真正類型取決于encoding屬性的值:
- 如果encoding屬性的值為為INTSET_ENC_INT16,那么contents就是一個int16_t類型的數組了,數組里的每個項都是一個int16_t類型的整數值(最小值為-32 768,最大值為32 767)
- 如果encoding屬性的值為INTSET_ENC_INT32,那么contents就是一個int32_t類型的數組了,數組里的每個項都是一個int32_t類型的整數值(最小值為-2 147 483 648,最大值為2 147 483 647)
- 如果encoding屬性的值為INTSET_ENC_INT64,那么contents就是一個int64_t類型的數組了,數組里的每個項都是一個int64_t類型的整數值(最小值為-9 223 372 036 854 775 808,最大值為9 223 372 036 854 775 807)。
圖1-1展示了一個整數集合示例:
圖1-1? ?一個包含五個int6_t類型整數值的整數集合
- 如果encoding屬性的值為為INTSET_ENC_INT16,表示整數集合的底層實現為int16_t類型的數組,而集合保存的都是int16_t類型的整數值
- length屬性為5,表示整數集合包含5個元素
- contents數組按從小到大的順序保存著集合中的五個元素
- 因為每個集合元素都是int16_t類型的整數值,所以contents數組的大小等于sizeof(int16_t)*5=16*5=80位
圖1-2展示了另一個整數集合示例:
圖1-2? ?一個包含四個int16_t類型整數值的整數集合
- encoding屬性的值為INTSET_ENC_INT64,表示整數集合的底層實現為int64_t類型的數組,而數組中保存的都是int64_t類型的整數值
- length屬性的值為4,表示整數集合包含四個元素
- contents數組按從小到大的順序保存著集合中的四個元素
- 因為每個集合元素都是int64_t類型的整數值,所以contents數組的大小為sizeof(int64_t)*4=64*4=256位
雖然contents數組保存的四個整數值中,只有-2 675 256 175 807 981 027是真正需要用int64_t類型來保存的,而其他的1、3、5三個值都可以用int16_t類型來保存,不過根據整數集合的升級規則,當向一個底層為int16_t數組的整數集合添加一個int64_t類型的整數值時,整數集合已有的所有元素都會被轉換成int64_t類型,所以contents數組保存的四個整數值都是int64_t類型的,不僅僅是?-2 675 256 175 807 981 027
升級
每當我們要將一個新元素添加到整數集合里面,并且新元素的類型比整數集合現有所有元素的類型要長時,整數集合需要先進行升級(upgrade),然后才能將新元素添加到整數集合里面。升級整數集合并添加新元素共分為三步進行:
舉個栗子,假設現在有一個INTSET_ENC_INT16編碼的整數集合,集合中包含三個int16_t類型的元素,如圖1-3所示
圖1-3? ?一個包含三個int16_t類型的元素的整數集合
因為每個元素都占用16位空間,所以整數集合底層數組的大小為3*16=48位,圖1-4展示了整數集合的三個元素在這48位里的位置
現在,假設我們要將類型為int32_t的整數值65 535添加到整數集合里面,因為65 535的類型int32_t比整數集合當前所有元素要長,所以在將65 535添加到整數集合之前,程序需要先對整數集合進行升級。升級首先要做的是,根據新類型的長度,以及集合元素的數量(包括要添加的新元素在內),對底層數組進行空間重分配
整數集合目前有三個元素,再加上新元素65 535,整數集合需要分配四個元素的空間,因為每個int32_t整數值需要占用32位空間,所以在空間重分配之后,底層數組的大小將是32*4=128位,如圖1-5所示。雖然程序對底層數組進行了空間重分配,但數組原有的三個元素1、2、3仍然是int16_t類型,這些元素還保存在數組的前48位里面,所以程序接下來要做的就是將這三個元素轉換成int32_t類型,并將轉換后的元素放置到正確的位上面,而且在放置元素的過程中,需要維持底層數組的有序性質不變
?
圖1-5? ?進行空間重分配之后的數組
首先,因為元素3在1、2、3、65535四個元素中排名第三,所以它將被移動到contents數組的索引2位置上,也即是數組64位至95位的空間內,如圖1-6所示
圖1-6? ?對元素3進行類型轉換,并保存在適當的位置上
接著,因為元素2在1、2、3、65535四個元素中排名第二,所以它將被移動到contents元素的索引1的位置上,也即是數組的32位至63位的空間內,如圖1-7
?
圖1-7? ?對元素2進行類型轉換,并保存在適當的位上
之后,因為元素1在1、2、3、65536四個元素中排名第一,所以它將被移動到contents數組的索引0位置上,即數組的0位至31位空間內,如圖1-8所示
圖1-8? ?對元素1進行類型轉換,并保存在適當的位置上
然后,因為元素65535在1、2、3、65535四個元素中排名第四,所以它將被添加到contents數組的索引3位置上,也即時數組的96位至127位的空間內,如圖1-9所示
如圖1-9? ?添加65535到數組
最后,程序將整數集合encoding屬性的值從INTSET_ENC_INT16改為INTSET_ENC_INT32,并將length屬性的值從3改為4,設置完成之后的整數集合如圖1-10所示
?
圖1-10? ?完成添加操作之后的整數集合
因為每次向整數集合添加新元素都可能會引起升級,而每次升級都需要對底層數組中已有的元素進行類型轉換,所以向整數集合添加新元素的時間復雜度為O(N)。其他類型的升級操作,比如從INTSET_ENC_INT16編碼升級為INTSET_ENC_INT64編碼,或從INTSET_ENC_INT32編碼升級為INTSET_ENC_INT64編碼,升級的過程都和上面展示的升級過程類似
升級的好處
整數集合的升級策略有兩個好處,一個是提升整數集合的靈活性,另一個是盡可能地節約內存
提升靈活性
因為C語言是靜態類型語言,為了避免類型錯誤,我們通常不會將兩種不同類型的值放在同一數據結構里面。例如,我們一般只用int16_t類型的數組來保存int16_t類型的值,只使用int32_t類型的數組來保存int32_t類型的值,諸如此類。但是,因為整數集合可以通過自動升級底層數組來適應新元素,所以我們可以隨意地將int16_t、int32_t或int64_t類型的整數添加到集合中,而不必擔心出現類型錯誤
節約內存
要讓一個數組可以同時保存int16_t、int32_t、int64_t三種類型的值,最簡單的做法就是直接使用int64_t類型的數組作為整數集合的底層實現。不過這樣一來,部分可以用int16_t、int32_t來存儲的值因使用int64_t類型來存儲,會造成內存的浪費。而整數集合現在的做法既可以讓集合能同時保存三種不同類型的值,又可以確保升級操作只會在有需要的時候進行,這可以盡量節省內存
降級
整數集合不支持降級操作,一旦對數組進行了升級,編碼就會一直保持升級后的狀態。舉個栗子,對于圖1-11所示的整數集合來說,即使我們將集合里唯一一個真正需要使用int64_t類型來保存的元素4 294 967 295刪除了,整數集合的編碼仍然會維持使用int64_t,底層數組也仍然會使int64_t類型,如圖1-12所示
圖1-11? ?數組編碼為INTSET_ENC_INT64的整數集合
圖1-12? ?刪除4 294 967 295的整數集合
整數集合API
表1-1列出了整數集合的操作API
| 函數 | 作用 | 時間復雜度 |
| intsetNew(void) | 創建一個新的整數集合 | O(1) |
| intsetAdd(intset *is, int64_t value, uint8_t *success) | 將給定元素添加到整數集合里面 | O(N) |
| intsetRemove(intset *is, int64_t value, int *success) | 從整數集合中移除給定元素 | O(N) |
| intsetFind(intset *is, int64_t value) | 檢查給定值是否存在于集合 | 因為底層數組有序,查找可以通過二分查找法來進行,所以復雜度為O(logN) |
| intsetRandom(intset *is) | 從整數集合中隨機返回一個元素 | O(1) |
| intsetGet(intset *is, uint32_t pos, int64_t *value) | 取出底層數組在給定索引上的元素 | O(1) |
| intsetLen(intset *is) | 返回整數集合包含的元素個數 | O(1) |
| intsetBlobLen(intset *is) | 返回整數集合占用的內存字節數 | O(1) |
轉載于:https://www.cnblogs.com/beiluowuzheng/p/9734227.html
總結
以上是生活随笔為你收集整理的Redis实现之整数集合的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux 未找到efi分区怎么办?
- 下一篇: 手机红外发射器使用方法?