Redis中整數小集合

整數集合(intset)是集合鍵的底層實現之一: 當一個集合只包含整數值元素, 并且這個集合的元素數量不多時, redis 就會使用整數集合作為集合鍵的底層實現。

127.0.0.1:6379>?sadd?numbers?1?2?3?4?5  (integer)?5  127.0.0.1:6379>?object?encoding?numbers  "intset"

這么做的好處是當集合中只有少量的整數元素的時候,采用之前介紹的其他數據結構,比如sds,都會占用比較大的內存,但如果僅保存為整數集合的話,則會更加經濟。

整數數組數據結構

整數數組的定義位于intset.h中,具體如下:

typedef?struct?intset?{  ????uint32_t?encoding;??//?編碼方式  ????uint32_t?length;???//?保存的元素個數  ????int8_t?contents[];??//?保存元素的數組  ?}?  ????intset;

雖然 intset 結構將 contents 屬性聲明為 int8_t 類型的數組, 但實際上 contents 數組并不保存任何 int8_t 類型的值 —— contents 數組的真正類型取決于 encoding 屬性的值:

#define?INTSET_ENC_INT16?(sizeof(int16_t))  #define?INTSET_ENC_INT32?(sizeof(int32_t))  #define?INTSET_ENC_INT64?(sizeof(int64_t))    /*?Return?the?required?encoding?for?the?provided?value.?*/  static?uint8_t?_intsetValueEncoding(int64_t?v)?{????  ????if?(v??INT32_MAX)????????  ????????return?INTSET_ENC_INT64;????  ????else?if?(v??INT16_MAX)????????  ????????return?INTSET_ENC_INT32;????  ????else  ??????return?INTSET_ENC_INT16;  }

可以看到一共會有三種類型,分別對應int_16, int_32, int_64。

整數數組中所有的元素在數組中按值的大小從小到大有序地排列, 并且數組中不包含任何重復項。

整數集合操作

創建整數集合

//?初始化空的整數集合intset?*intsetNew(void)?{  ????intset?*is?=?zmalloc(sizeof(intset));????  ????is->encoding?=?intrev32ifbe(INTSET_ENC_INT16);?//?默認創建int_16的編碼格式  ????is->length?=?0;????  ????return?is;  }

插入一個元素

/*?Insert?an?integer?in?the?intset?*/intset?*intsetAdd(intset?*is,?int64_t?value,?uint8_t?*success)?{  ????uint8_t?valenc?=?_intsetValueEncoding(value);  ????uint32_t?pos;????  ????if?(success)?*success?=?1;????//?如果超出了當前編碼格式所能表示的范圍,則升級整數集合并添加元素  ????if?(valenc?>?intrev32ifbe(is->encoding))?{????????  ????/*?This?always?succeeds,?so?we?don't?need?to?curry?*success.?*/  ????????return?intsetUpgradeAndAdd(is,value);  ????}?else?{????????//?如果元素已經存在于集合,success返回0  ????????//?如果不存在的話,?這個函數會返回元素應該插入的位置pos  ????????if?(intsetSearch(is,value,&pos))?{????????????  ????????if?(success)?*success?=?0;????????????  ????????return?is;  ????????}????????//?否則,需要重新調整集合的大小  ????????is?=?intsetResize(is,intrev32ifbe(is->length)+1);????????//?將pos之后的數據全都向后挪動一個位子  ????????if?(pos?length))?intsetMoveTail(is,pos,pos+1);  ????}    ????_intsetSet(is,pos,value);?//?添加數據到第pos位  ????is->length?=?intrev32ifbe(intrev32ifbe(is->length)+1);?//?調整元素個數  ????return?is;  }

在插入元素的時候,需要根據新元素的大小來重新確定所采用的編碼。如果新元素超出了原有編碼的表示范圍,就需要調整編碼,同時調整集合中所有其他元素的編碼格式。調整編碼是一個不可逆的過程,就是說只能從小的編碼調整到大的編碼,只能升級不能降級。

升級過程

升級整數集合并添加新元素調用的是intsetUpgradeAndAdd函數,共分為三步進行:

  • 根據新元素的類型, 擴展整數集合底層數組的空間大小, 并為新元素分配空間。

  • 將底層數組現有的所有元素都轉換成與新元素相同的類型, 并將類型轉換后的元素放置到正確的位上, 而且在放置元素的過程中, 需要繼續維持底層數組的有序性質不變。

  • 將新元素添加到底層數組里面。

/*?Upgrades?the?intset?to?a?larger?encoding?and?inserts?the?given?integer.?*/static?intset?*intsetUpgradeAndAdd(intset?*is,?int64_t?value)?{????//?當前的編碼  ????uint8_t?curenc?=?intrev32ifbe(is->encoding);????//?根據新元素的值獲得新的編碼  ????uint8_t?newenc?=?_intsetValueEncoding(value);????  ????int?length?=?intrev32ifbe(is->length);????  ????//?由于整數集合是一個有序集合,所以新的這個超出范圍的元素,要不插入頭部,要不插入尾部  ????//?當value大于0的時候,就是插入到尾部,否則插入到頭部,用參數prepend來標記  ????int?prepend?=?value?encoding?=?intrev32ifbe(newenc);????//?根據新編碼調整整數集合的大小  ????is?=?intsetResize(is,intrev32ifbe(is->length)+1);????//?從尾部向頭部進行升級,這樣在挪動其中的元素的時候,不會覆蓋原來的值  ????while(length--)????????  ??????????//?如果新元素是插入到尾部,prepend==0,?所以原來最后的元素是挪動到length位置  ????????//?如果新元素是插入到頭部,prepend==1,所有的元素都要向后挪動一個位置,將頭部空出來  ????????_intsetSet(is,length+prepend,_intsetGetEncoded(is,length,curenc));????  ????????  ????????/*?Set?the?value?at?the?beginning?or?the?end.?*/  ????if?(prepend)????????  ????//?如果prepend==1,?插入到頭部  ????????_intsetSet(is,0,value);????  ???????else  ????????//?否則,設置最后一個位置的元素為value  ????????_intsetSet(is,intrev32ifbe(is->length),value);????//?元素個數加1  ????is->length?=?intrev32ifbe(intrev32ifbe(is->length)+1);????  ????return?is;  }

而整數集合現在的做法既可以讓集合能同時保存三種不同類型的值, 又可以確保升級操作只會在有需要的時候進行, 這可以盡量節省內存。

查找元素

查找的時候,需要先判斷要查找的元素是否在當前編碼的有效范圍內,如果不在當前范圍內,可以直接返回。

另外因為整數集合是一個有序集合,可以采用二分查找的辦法,

uint8_t?intsetFind(intset?*is,?int64_t?value)?{????//?獲得目標值的編碼  ????uint8_t?valenc?=?_intsetValueEncoding(value);????//?只有目標值的編碼比當前編碼小,才繼續執行查找過程  ????return?valenc?encoding)?&amp;&amp;?intsetSearch(is,value,NULL);  }//?如果找到這個元素,返回1,同時pos表示這個值在整數集合里邊的位置  //?如果沒有找到這個元素,返回0,?同時pos表示這個值可以插入的位置  static?uint8_t?intsetSearch(intset?*is,?int64_t?value,?uint32_t?*pos)?{????  int?min?=?0,?max?=?intrev32ifbe(is-&gt;length)-1,?mid?=?-1;  ????int64_t?cur?=?-1;????  ????  ????/*?The?value?can?never?be?found?when?the?set?is?empty?*/  ????//?如果集合的長度為0,?直接返回0  ????if?(intrev32ifbe(is-&gt;length)?==?0)?{????????  ????if?(pos)?*pos?=?0;????????  ????return?0;  ????}?else?{????????  ????/*?Check?for?the?case?where?we?know?we?cannot?find?the?value,  ?????????*?but?do?know?the?insert?position.?*/  ????????//?如果目標值大于當前最大值,肯定找不到,返回0,?同時待插入的位置pos為length  ????????if?(value?&gt;?_intsetGet(is,intrev32ifbe(is-&gt;length)-1))?{????????????  ????????if?(pos)?*pos?=?intrev32ifbe(is-&gt;length);????????????  ????????return?0;  ????????}?else?if?(value?=?min)?{????????//?得到中間位置  ????????mid?=?((unsigned?int)min?+?(unsigned?int)max)?&gt;&gt;?1;????????//?得到中間位置的值  ????????cur?=?_intsetGet(is,mid);????????  ????????if?(value?&gt;?cur)?{  ????????????min?=?mid+1;  ????????}?else?if?(value?<div></div>

? 版權聲明
THE END
喜歡就支持一下吧
點贊14 分享