整數集合(intset)是集合鍵的底層實現之一, 當乙個集合只包含了整數值元素, 並且這個集合的元素數量不多時, redis就會使用整數集合作為集合鍵的底層實現。
舉個例子,如果建立乙個只包含5個元素的集合鍵, 並且集合中的所有元素都是整數值, 那麼這個集合鍵的底層實現就會是整數集合:
127.0.0.1:6379> sadd numbers 1 3 5 7 9
(integer) 5
127.0.0.1:6379> object encoding numbers
"intset"
整數集合(intset)是redis用於儲存整數值的集合抽象資料結構, 它可以儲存型別為int16_t、int32_t或int64_t的整數值, 並且保證集合中不會出現重複元素。
每隔intset.h/intset集合集合表示乙個整數集合:
typedef struct intset intset;
contents陣列是整數集合的底層實現:整數集合的每個元素都是content陣列的乙個陣列項(item), 各個項在陣列中按值的大小從小到大有序的排列, 並且陣列中不包含任何重複項。
length屬性記錄了整數集合包含的元素數量, 也即是contents陣列的長度。
雖然intset結構將contents屬性宣告為int8_t型別的陣列, 但實際上contents陣列並不儲存任何int8_t型別的值, contents陣列的真正型別取決於encoding屬性的值
下圖展示乙個整數集合例項:
intset
encoding intset_enc_int16
length 5
contents
→-6370
-518
23314632
下圖展示了另乙個整數集合示例:
intset
encoding intset_enc_int64
length 4
contents
→-267525617580798102713
5
雖然contents陣列中儲存的四個整數值中, 只有-2675256175807981027是真正需要用int64_t型別來儲存的, 而其他1、3、5三個值都可以用int16_t型別來儲存, 不過根據整數集合的公升級規則, 當向乙個底層為int16_t陣列的集合新增乙個int64_t型別的整數值時, 整數集合已有的所有元素都會被轉換成int64_t型別,所以contents陣列儲存的四個整數值都是int64_t型別的,不僅僅是-2675256175807981027。
每當我們要將乙個新元素新增到整數集合裡,並且新元素的型別比整數集合現有所有元素的型別都要長時, 整數集合需要先今昔公升級(upgrade),然後才能將新元素新增到整數集合裡。
公升級整數集合並新增新元素共分為三步進行:
1、根據新元素的型別,擴充套件整數集合底層資料的空間大小,並為新元素分配空間。
2、將底層陣列現有的所有元素都轉換成與新元素相同的型別, 並將型別轉換後的元素放置到正確的位上,且在放置元素的過程中, 需要繼續維持底層陣列的有序性質不變。
3、將新元素新增到底層陣列裡面。
舉個例子,假設現有乙個intset_enc_int16編碼的整數集合,集合中包含三個int16_t型別的元素,如下圖所示:
intset
encoding intset_enc_int16
length 3
contents→1
23 因為每個元素都占用16為空間, 所以整數集合的底層陣列的大小是3 * 16 = 48位。下圖展示了整數集合的三個元素在這48位里的位置。
位0至15位
16至31位
32至47位元素1
23 現假設需要將型別位int32_t的整數值65535新增到整數集合裡, 因為65535的型別int32_t比整數集合當前所有元素的型別都要長, 所以在將65535新增到整數集合之前,程式需要先對整數集合進行公升級。
首先是根據新的型別長度, 以及集合元素的數量(包括要新增的新元素在內),對底層陣列進行空間重分配。
整數集合目前有三個元素, 在加上新元素65535,整數集合需要分配四個元素的空間,因為每個int32_t整數值需要占用32位空間,所以在空間重分配之後,底層陣列的大小將是32*4=128位, 如下圖所示。
位0至15位
16至31位
32至47位
48至127位元素1
23(新分配空間)
雖然程式對底層資料進行了空間重分配, 但是陣列原有的三個元素1、2 、3仍然是int16_t型別,這些元素還儲存在陣列的前48位,所以程式接下來要做的就是將這三個元素轉換成int32_t型別,並將轉換後的元素放置到正確的位上面, 而且在放置過程中,需要維持底層陣列有序性質不變。
首先,因為元素3在1、2、3、65535四個元素中排名第三, 所以它將被移動到contents陣列的索引位置2位置上, 也即是陣列64至95位的空間內, 如下圖所示。
位0至15位
16到31位
32至47位
48至63位
64至95位
96至127位元素1
2(int16_t)3
(新分配空間)
(int32_t)3
(新分配空間)
接著,因為元素2在1、2、3、65535四個元素中排名第二,所以它將被移動到contents陣列的索引1的位置上, 也就是陣列的32位至63位的空間上,如下圖所示:
位0至15位
16至31位
32至63位
64至95位
96至127位元素1
(int16_t)2
(int32_t)2
3(新分配空間)
之後,因為元素1在1、2、3、65335四個元素的排名第一,所以它將被移動到contents陣列的索引0位置上, 即陣列的0位至31位的空間內, 如下圖所示:
位0至31位
32至63位
64位至95位
96位至127位元素1
23(新分配空間)
然後, 因為元素65535在1、2、3、65335四個元素中排名第四, 所以它將被新增到contents陣列的索引3位置上, 也即是陣列的96位至127位的空間內,如下圖所示
位0至31位
32位至63位
64位至95位
96位至127位元素1
23(新增新元素)65535
最後,程式將整數集合encoding屬性值從intset_enc_int16改為intset_enc_int32, 並將length屬性值從3改為4, 設定完成後的整數集合如下所示:
intset
encoding intset_enc_int32
length 4
contents→1
2365535
因為每次向整數集合新增新元素都可能會引起公升級, 而每次公升級都需要對底層陣列中已有的所有原件進行型別轉換, 所以向整數集合新增新元素的時間複雜度位o(n)。
因為引發公升級的新元素的長度總是比整數集合現有所有元素的長度都要大,所以這個新元素的值要麼大於所有現有元素,要麼小於所有現有元素:提公升靈活性
因為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型別的空間去儲存它們,從而出現浪費記憶體的情況。
而整數集合現在的做法既可以讓集合同時儲存三種不同型別的值,又可以確保公升級操作只會在需要的時候進行,這可以盡量節省記憶體。
整數集合不支援降級操作,一旦對陣列進行了公升級,編碼就會一直保持公升級後的狀態。
Redis學習筆記 原始碼閱讀 整數集合
整數集合 intset 是乙個有序的 儲存整型資料的結構,當redis集合型別的元素都是整數並且都處在64位有符號整數範圍之內時,使用該結構體儲存。在兩種情況下,底層編碼會發生轉換。整數集合在redis中可以儲存int16 t int32 t int64 t型別的整型資料,並且可以保證集合中不會出現...
Redis閱讀筆記 集合物件
集合物件的編碼可以是intset或hashtable。intset編碼的集合物件使用整數集合作為底層實現,集合物件包含的所有元素都被儲存再整數集合裡。舉個例子,以下 將建立如下所示的intset編碼集合物件 127.0.0.1 6379 sadd numbers 1 3 5 integer 3red...
Redis 整數集合
整數集合時集合鍵的底層實現之一,適用於只包含整數值,且數量不多的使用場景。可用於儲存int16 t,int32 t,int64 t的整數值。整數集合公升級 整數集合在儲存數值時,如果新的元素長度比久的大,那需要公升級。步驟如下 根據新元素的型別,擴充套件整數集合底層陣列空間大小,為新元素分配空間。將...