鍍金池/ 教程/ 大數(shù)據(jù)/ Redis 數(shù)據(jù)結(jié)構(gòu) intset
Redis 數(shù)據(jù)淘汰機制
積分排行榜
小剖 Memcache
Redis 數(shù)據(jù)結(jié)構(gòu) intset
分布式鎖
從哪里開始讀起,怎么讀
Redis 數(shù)據(jù)結(jié)構(gòu) dict
不在浮沙筑高臺
Redis 集群(上)
Redis 監(jiān)視器
源碼閱讀工具
Redis 日志和斷言
內(nèi)存數(shù)據(jù)管理
Redis 數(shù)據(jù)結(jié)構(gòu)綜述
源碼日志
Web 服務器存儲 session
消息中間件
Redis 與 Lua 腳本
什么樣的源代碼適合閱讀
Redis 數(shù)據(jù)結(jié)構(gòu) sds
Memcached slab 分配策略
訂閱發(fā)布機制
Redis 是如何提供服務的
Redis 事務機制
Redis 集群(下)
主從復制
Redis 應用
RDB 持久化策略
Redis 數(shù)據(jù)遷移
Redis 事件驅(qū)動詳解
初探 Redis
Redis 與 Memcache
AOF 持久化策略
Redis 數(shù)據(jù)結(jié)構(gòu) redisOb
作者簡介
Redis 數(shù)據(jù)結(jié)構(gòu) ziplist
Redis 數(shù)據(jù)結(jié)構(gòu) skiplist
Redis 哨兵機制

Redis 數(shù)據(jù)結(jié)構(gòu) intset

intset 和 dict 都是 sadd 命令的底層數(shù)據(jù)結(jié)構(gòu),當添加的所有數(shù)據(jù)都是整數(shù)時,會使用前者;否則使用后者。特別的,當遇到添加數(shù)據(jù)為字符串,即不能表示為整數(shù)時,Redis 會把數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)換為 dict,即把 intset 中的數(shù)據(jù)全部搬遷到 dict。

本片展開的是 intset,dict 的文章可以參看之前寫的《Redis 數(shù)據(jù)結(jié)構(gòu) dict》。

intset 結(jié)構(gòu)體

intset 底層本質(zhì)是一個有序的、不重復的、整型的數(shù)組,支持不同類型整數(shù)。

typedef struct intset {
// 每個整數(shù)的類型
uint32_t encoding;
// intset 長度
uint32_t length;
// 整數(shù)數(shù)組
int8_t contents[];
} intset;

結(jié)構(gòu)體中 intset.contents 數(shù)組沒有指定長度,這樣是為了方便分配釋放內(nèi)存。encoding 能下面的三個值:分別是 16,32 和 64 位整數(shù):

/* Note that these encodings are ordered, so:
* INTSET_ENC_INT16 < INTSET_ENC_INT32 < INTSET_ENC_INT64. */
#define INTSET_ENC_INT16 (sizeof(int16_t))
#define INTSET_ENC_INT32 (sizeof(int32_t))
#define INTSET_ENC_INT64 (sizeof(int64_t))

intset 搜索

intset 是有序的整數(shù)數(shù)組,可以用二分搜索查找。

static uint8_t intsetSearch(intset *is, int64_t value, uint32_t *pos) {
   int min = 0, max = intrev32ifbe(is->length)-1, mid = -1;
   int64_t cur = -1;
   /* The value can never be found when the set is empty */
   // 集合為空
      if (intrev32ifbe(is->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. */
      // value 比最大元素還大
      if (value > _intsetGet(is,intrev32ifbe(is->length)-1)) {
      if (pos) *pos = intrev32ifbe(is->length);
      return 0;
      // value 比最小元素還小
      } else if (value < _intsetGet(is,0)) {
      if (pos) *pos = 0;
      return 0;
      }
   }
   // 熟悉的二分查找
   while(max >= min) {
      mid = (min+max)/2;
      cur = _intsetGet(is,mid);
   if (value > cur) {
      min = mid+1;
   } else if (value < cur) {
      max = mid-1;
   } else {
      break;
      }
   }
   if (value == cur) {
   if (pos) *pos = mid;
      return 1;
   } else {
   if (pos) *pos = min;
      return 0;
   }
}

intset 插入

intset 實現(xiàn)中比較有意思的是插入算法部分。

/* 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;
    /* Upgrade encoding if necessary. If we need to upgrade, we know that
    * this value should be either appended (if > 0) or prepended (if < 0),
    * because it lies outside the range of existing values. */
    // 需要插入整數(shù)的所需內(nèi)存超出了原有集合整數(shù)的范圍,即內(nèi)存類型不同,
    // 則升級整數(shù)類型
    if (valenc > intrev32ifbe(is->encoding)) {
    /* This always succeeds, so we don't need to curry *success. */
        return intsetUpgradeAndAdd(is,value);
    // 正常,分配內(nèi)存,插入
    } else {
    // intset 內(nèi)部不允許重復
    /* Abort if the value is already present in the set.
    * This call will populate "pos" with the right position to insert
    * the value when it cannot be found. */
    if (intsetSearch(is,value,&pos)) {
    if (success) *success = 0;
        return is;
    }
    // realloc
    is = intsetResize(is,intrev32ifbe(is->length)+1);
    // 遷移內(nèi)存,騰出空間給新的數(shù)據(jù)。intsetMoveTail() 完成內(nèi)存遷移工作
    if (pos < intrev32ifbe(is->length)) intsetMoveTail(is,pos,pos+1);
    }
    // 在騰出的空間中設置新的數(shù)據(jù)
        _intsetSet(is,pos,value);
    // 更新intset size
        is->length = intrev32ifbe(intrev32ifbe(is->length)+1);
    return is;
}

整數(shù)數(shù)組有可能遇到需要升級的時候,譬如往 int32_t 數(shù)組插入一個 ing64_t 整數(shù)的時候。當插入數(shù)據(jù)的內(nèi)存占用比原有數(shù)據(jù)大的時候,intsetUpgradeAndAdd() 會被調(diào)用。

/* 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 頭插,value>0 尾插
    int prepend = value < 0 ? 1 : 0;
    // realloc
    /* First set new encoding and resize */
    is->encoding = intrev32ifbe(newenc);
    is = intsetResize(is,intrev32ifbe(is->length)+1);
    // 逆向處理,防止數(shù)據(jù)被覆蓋,一般的插入排序步驟
    /* Upgrade back-to-front so we don't overwrite values.
    * Note that the "prepend" variable is used to make sure we have an empty
    * space at either the beginning or the end of the intset. */
    while(length--)
        _intsetSet(is,length+prepend,_intsetGetEncoded(is,length,curenc));
    // value<0 放在集合開頭,否則放在集合末尾。
    // 因為,此函數(shù)是對整數(shù)所占內(nèi)存進行升級,意味著value 不是在集合中最大就是最?。?    /* Set the value at the beginning or the end. */
    if (prepend)
        _intsetSet(is,0,value);
    else
        _intsetSet(is,intrev32ifbe(is->length),value);
    // 更新set size
        is->length = intrev32ifbe(intrev32ifbe(is->length)+1);
    return is;
}