Redis實現之字典跳躍表整數集合

來源:互聯網
上載者:User

標籤:length   類型轉換   操作   語言   idt   時間複雜度   blob   ade   結構   

 

整數集合

整數集合(insert)是集合鍵的底層實現之一,當一個集合只包含整數值元素,並且這個集合的元素數量不多時,Redis就會使用整數集合作為集合鍵的底層實現。舉個栗子,如果我們建立一個只包含五個元素的集合鍵,並且集合中的所有元素都是整數值,那麼這個集合鍵的底層實現就會是整數集合:

127.0.0.1:6379> SADD numbers 1 3 5 7 9(integer) 5127.0.0.1:6379> SMEMBERS numbers1) "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),然後才能將新元素添加到整數集合裡面。升級整數集合并添加新元素共分為三步進行:

  1.  根據新元素的類型,擴充整數集合底層數組對的空間大小,並為新元素分配空間
  2. 將底層數組現有的所有元素都轉換成與新元素相同的類型,並將類型轉換後的元素放到正確的位置上,而且在放置元素的過程中,需要繼續維持底層數組的有序性質不變
  3. 將新元素添加到底層數組裡面

舉個栗子,假設現在有一個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

表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)

Redis實現之字典跳躍表整數集合

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.