redis源碼學習_整數集合
redis裏面的整數集合保存的都是整數,有int_16、int_32和int_64這3種類型,和C++中的set容器差不多。
同時具備如下特點:
1、set裏面的數不重復,均為唯一。
2、set裏面的數是從小到大有序的,這在後面的intsetAdd函數中可以看到。
然後由於我們可以同時存儲int_16、int_32和int_64這3種類型,一開始只能為一種類型。假設為int_32,那麽我們要插入一個int_16類型的數,只需要找到位置直接插入就可以了;但是我們要插入一個int_64類型的數,我們需要先升級,然後再插入。之所以要升級是為了可以有足夠的空間存下位數更多的整數,一開始不直接搞成int_64是為了節省內存空間,按需升級非常靈活,既可以節省空間,又可以同時存在不同類型(int_16、int_32和int_64)的整數,一舉兩得!
主要總結一下intset.c和intset.h裏面的關鍵結構體和函數。
先來看一下intset的結構體吧
1 typedef struct intset { 2 3 /* 4 雖然 intset 結構將 contents 屬性聲明為 int8_t 類型的數組, 但實際上 contents 數組的真正類型取決於 encoding 屬性的值: 5 如果 encoding 屬性的值為 INTSET_ENC_INT16 , 那麽 contents 就是一個 int16_t 類型的數組, 數組裏的每個項都是一個 int16_t類型的整數值 (最小值為 -32,768 ,最大值為 32,767 )。6 如果 encoding 屬性的值為 INTSET_ENC_INT32 , 那麽 contents 就是一個 int32_t 類型的數組, 數組裏的每個項都是一個 int32_t類型的整數值 (最小值為 -2,147,483,648 ,最大值為 2,147,483,647 )。 7 如果 encoding 屬性的值為 INTSET_ENC_INT64 , 那麽 contents 就是一個 int64_t 類型的數組, 數組裏的每個項都是一個 int64_t類型的整數值 (最小值為 -9,223,372,036,854,775,808 ,最大值為9,223,372,036,854,775,807 )。8 */ 9 // 編碼方式 10 uint32_t encoding; 11 // 集合包含的元素數量 12 uint32_t length; 13 // 保存元素的數組 14 int8_t contents[]; 15 } intset;
函數intsetAdd是裏面精華部分,在看它之前我們先看一下它用到的一些函數
_intsetValueEncoding:得到實際的類型,即對應的是int_16、int_32和int_64中的哪一個
1 /* Return the required encoding for the provided value. 2 * 3 * 返回適用於傳入值 v 的編碼方式 4 * 5 * T = O(1) 6 */ 7 static uint8_t _intsetValueEncoding(int64_t v) { 8 if (v < INT32_MIN || v > INT32_MAX) 9 return INTSET_ENC_INT64; 10 else if (v < INT16_MIN || v > INT16_MAX) 11 return INTSET_ENC_INT32; 12 else 13 return INTSET_ENC_INT16; 14 }
_intsetSet:在指定位置上面插數
1 /* Set the value at pos, using the configured encoding. 2 * 3 * 根據集合的編碼方式,將底層數組在 pos 位置上的值設為 value 。 4 * 5 * T = O(1) 6 */ 7 static void _intsetSet(intset *is, int pos, int64_t value) { 8 9 // 取出集合的編碼方式 10 uint32_t encoding = intrev32ifbe(is->encoding); 11 12 // 根據編碼 ((Enc_t*)is->contents) 將數組轉換回正確的類型 13 // 然後 ((Enc_t*)is->contents)[pos] 定位到數組索引上 14 // 接著 ((Enc_t*)is->contents)[pos] = value 將值賦給數組 15 // 最後, ((Enc_t*)is->contents)+pos 定位到剛剛設置的新值上 16 // 如果有需要的話, memrevEncifbe 將對值進行大小端轉換 17 if (encoding == INTSET_ENC_INT64) { 18 ((int64_t*)is->contents)[pos] = value; 19 memrev64ifbe(((int64_t*)is->contents)+pos); 20 } else if (encoding == INTSET_ENC_INT32) { 21 ((int32_t*)is->contents)[pos] = value; 22 memrev32ifbe(((int32_t*)is->contents)+pos); 23 } else { 24 ((int16_t*)is->contents)[pos] = value; 25 memrev16ifbe(((int16_t*)is->contents)+pos); 26 } 27 }
關於裏面的memrevXXXifbe函數就是個宏
1 #if (BYTE_ORDER == LITTLE_ENDIAN) 2 #define memrev16ifbe(p) 3 #define memrev32ifbe(p) 4 #define memrev64ifbe(p) 5 #else 6 #define memrev16ifbe(p) memrev16(p) //高低位對換 7 #define memrev32ifbe(p) memrev32(p) //高低位對換 8 #define memrev64ifbe(p) memrev64(p) //高低位對換 9 #endif
補充一下大小端的知識:
大端模式:是指數據的低位保存在內存的高地址中,而數據的高位,保存在內存的低地址中;
小端模式:是指數據的低位保存在內存的低地址中,而數據的高位保存在內存的高地址中。
在裘宗燕翻譯的《程序設計實踐》裏,這對術語並沒有翻譯為“大端”和小端,而是“高尾端”和“低尾端”,這就好理解了:如果把一個數看成一個字符串,比如11223344看成"11223344",末尾是個‘\0‘,‘11‘到‘44‘個占用一個存儲單元,那麽它的尾端很顯然是44,前面的高還是低就表示尾端放在高地址還是低地址,它在內存中的放法非常直觀,如下圖:
“高/低尾端”比“大/小端”更不容易讓人迷惑。在這兩對形容詞中,恰好“高”和“大”對應,“低”和“小”對應;既然高尾端對應的是大端,低尾端對應的是小端,那麽當你再見到大端和小端這一對術語,就可以在腦中把它們轉化成高尾端和低尾端,這時憑著之前的理解,甚至不用回憶,想著高低的字面含義就能回想起它們的含義。
理解之後,總結一下,記憶的方法是:
(數據看成字符串)大端——高尾端,小端——低尾端
稍一思索什麽是“高”、什麽是"低","尾端"又是什麽,問題迎刃而解,再不用擔心被“大端”和“小端”迷惑。用這種方式,是時候放棄原先的死記硬背和容易把自己繞進去而發生迷惑的理解了。
16bit寬的數0x1234在Little-endian模式CPU內存中的存放方式(假設從地址0x4000開始存放)為:
內存地址 |
0x4000 |
0x4001 |
存放內容 |
0x34 |
0x12 |
而在Big-endian模式CPU內存中的存放方式則為:
內存地址 |
0x4000 |
0x4001 |
存放內容 |
0x12 |
0x34 |
32bit寬的數0x12345678在Little-endian模式CPU內存中的存放方式(假設從地址0x4000開始存放)為:
內存地址 |
0x4000 |
0x4001 |
0x4002 |
0x4003 |
存放內容 |
0x78 |
0x56 |
0x34 |
0x12 |
而在Big-endian模式CPU內存中的存放方式則為:
內存地址 |
0x4000 |
0x4001 |
0x4002 |
0x4003 |
存放內容 |
0x12 |
0x34 |
0x56 |
0x78 |
如何判斷系統是大端還是小端呢?
1 #include <stdlib.h> 2 #include <stdio.h> 3 int main(int argc, char **argv) 4 { 5 union { 6 short s; 7 char c[sizeof(short)]; 8 } un; 9 un.s = 0x0102; 10 if(sizeof(short)==2) { 11 if(un.c[0]==1 && un.c[1] == 2) 12 printf("big-endian\n"); 13 else if (un.c[0] == 2 && un.c[1] == 1) 14 printf("little-endian\n"); 15 else 16 printf("unknown\n"); 17 } else 18 printf("sizeof(short)= %d\n",sizeof(short)); 19 exit(0); 20 }
扯遠了,我們再回來接著看啊~~~
intsetUpgradeAndAdd:升級函數,這算是set裏面比較難的函數了。
1 /* Upgrades the intset to a larger encoding and inserts the given integer. 2 * 3 * 根據值 value 所使用的編碼方式,對整數集合的編碼進行升級, 4 * 並將值 value 添加到升級後的整數集合中。 5 * 6 * 返回值:添加新元素之後的整數集合 7 * 8 * T = O(N) 9 */ 10 static intset *intsetUpgradeAndAdd(intset *is, int64_t value) { 11 12 // 當前的編碼方式 13 uint8_t curenc = intrev32ifbe(is->encoding); 14 15 // 新值所需的編碼方式 16 uint8_t newenc = _intsetValueEncoding(value); 17 18 // 當前集合的元素數量 19 int length = intrev32ifbe(is->length); 20 21 // 根據 value 的值,決定是將它添加到底層數組的最前端還是最後端 22 // 註意,因為 value 的編碼比集合原有的其他元素的編碼都要大 23 // 所以 value 要麽大於集合中的所有元素,要麽小於集合中的所有元素 24 // 因此,value 只能添加到底層數組的最前端或最後端 25 int prepend = value < 0 ? 1 : 0; 26 27 /* First set new encoding and resize */ 28 is->encoding = intrev32ifbe(newenc); 29 30 is = intsetResize(is,intrev32ifbe(is->length)+1); 31 32 /* Upgrade back-to-front so we don‘t overwrite values. 33 * Note that the "prepend" variable is used to make sure we have an empty 34 * space at either the beginning or the end of the intset. */ 35 while(length--) 36 _intsetSet(is,length+prepend,_intsetGetEncoded(is,length,curenc)); 37 38 /* Set the value at the beginning or the end. */ 39 // 設置新值,根據 prepend 的值來決定是添加到數組頭還是數組尾 40 if (prepend) 41 _intsetSet(is,0,value); 42 else 43 _intsetSet(is,intrev32ifbe(is->length),value); 44 45 // 更新整數集合的元素數量 46 is->length = intrev32ifbe(intrev32ifbe(is->length)+1); 47 48 return is; 49 }
intsetSearch:找數函數,找插入數的位置,有就不插,沒有才插,用了二分查找,呵呵!
1 static uint8_t intsetSearch(intset *is, int64_t value, uint32_t *pos) { 2 int min = 0, max = intrev32ifbe(is->length)-1, mid = -1; 3 int64_t cur = -1; 4 5 /* The value can never be found when the set is empty */ 6 if (intrev32ifbe(is->length) == 0) { 7 if (pos) *pos = 0; 8 return 0; 9 } else { 10 /* Check for the case where we know we cannot find the value, 11 * but do know the insert position. */ 12 // 因為底層數組是有序的,如果 value 比數組中最後一個值都要大 13 // 那麽 value 肯定不存在於集合中, 14 // 並且應該將 value 添加到底層數組的最末端 15 if (value > _intsetGet(is,intrev32ifbe(is->length)-1)) { 16 if (pos) *pos = intrev32ifbe(is->length); 17 return 0; 18 // 因為底層數組是有序的,如果 value 比數組中最前一個值都要小 19 // 那麽 value 肯定不存在於集合中, 20 // 並且應該將它添加到底層數組的最前端 21 } else if (value < _intsetGet(is,0)) { 22 if (pos) *pos = 0; 23 return 0; 24 } 25 } 26 27 // 在有序數組中進行二分查找 28 while(max >= min) { 29 mid = (min+max)/2; 30 cur = _intsetGet(is,mid); 31 if (value > cur) { 32 min = mid+1; 33 } else if (value < cur) { 34 max = mid-1; 35 } else { 36 break; 37 } 38 } 39 40 // 檢查是否已經找到了 value 41 if (value == cur) { 42 if (pos) *pos = mid; 43 return 1; 44 } else { 45 if (pos) *pos = min; 46 return 0; 47 } 48 }
intsetMoveTail:移動函數
1 static void intsetMoveTail(intset *is, uint32_t from, uint32_t to) { 2 3 void *src, *dst; 4 5 uint32_t bytes = intrev32ifbe(is->length)-from; 6 7 uint32_t encoding = intrev32ifbe(is->encoding); 8 9 //這裏的移動可就是批量移動的了,見後面的memmove 10 if (encoding == INTSET_ENC_INT64) { 11 src = (int64_t*)is->contents+from; 12 dst = (int64_t*)is->contents+to; 13 bytes *= sizeof(int64_t); 14 } else if (encoding == INTSET_ENC_INT32) { 15 src = (int32_t*)is->contents+from; 16 dst = (int32_t*)is->contents+to; 17 bytes *= sizeof(int32_t); 18 } else { 19 src = (int16_t*)is->contents+from; 20 dst = (int16_t*)is->contents+to; 21 bytes *= sizeof(int16_t); 22 } 23 24 memmove(dst,src,bytes); 25 }
看了前面那麽多,我們在看函數intsetAdd,那就太簡單了
1 intset *intsetAdd(intset *is, int64_t value, uint8_t *success) { 2 3 // 計算編碼 value 所需的長度 4 uint8_t valenc = _intsetValueEncoding(value); 5 uint32_t pos; 6 7 // 默認設置插入為成功 8 if (success) *success = 1; 9 10 /* Upgrade encoding if necessary. If we need to upgrade, we know that 11 * this value should be either appended (if > 0) or prepended (if < 0), 12 * because it lies outside the range of existing values. */ 13 if (valenc > intrev32ifbe(is->encoding)) { 14 /* This always succeeds, so we don‘t need to curry *success. */ 15 return intsetUpgradeAndAdd(is,value); 16 } else { 17 /* Abort if the value is already present in the set. 18 * This call will populate "pos" with the right position to insert 19 * the value when it cannot be found. */ 20 if (intsetSearch(is,value,&pos)) { 21 if (success) *success = 0; 22 return is; 23 } 24 25 //將 value 添加到整數集合中並為 value 在集合中分配空間 26 is = intsetResize(is,intrev32ifbe(is->length)+1); 27 28 if (pos < intrev32ifbe(is->length)) intsetMoveTail(is,pos,pos+1); 29 } 30 31 // 將新值設置到底層數組的指定位置中 32 _intsetSet(is,pos,value); 33 34 // 增一集合元素數量的計數器 35 is->length = intrev32ifbe(intrev32ifbe(is->length)+1); 36 37 // 返回添加新元素後的整數集合 38 return is; 39 }
再看一個刪除函數intsetRemove,也是上面各種操作的結合體,也比較簡單了
1 intset *intsetRemove(intset *is, int64_t value, int *success) { 2 uint8_t valenc = _intsetValueEncoding(value); 3 uint32_t pos; 4 if (success) *success = 0; 5 6 7 if (valenc <= intrev32ifbe(is->encoding) && intsetSearch(is,value,&pos)) { 8 uint32_t len = intrev32ifbe(is->length); 9 10 /* We know we can delete */ 11 if (success) *success = 1; 12 13 /* Overwrite value with tail and update length */ 14 if (pos < (len-1)) intsetMoveTail(is,pos+1,pos); 15 16 is = intsetResize(is,len-1); 17 18 is->length = intrev32ifbe(len-1); 19 } 20 21 return is; 22 }
最後我們再求個長度就結束了吧 zzzZZZ……
1 size_t intsetBlobLen(intset *is) { 2 return sizeof(intset)+intrev32ifbe(is->length)*intrev32ifbe(is->encoding); 3 }
關於大小端的參考材料:
http://blog.csdn.net/zhaoshuzhaoshu/article/details/37600857/
http://www.cnblogs.com/wi100sh/p/4899460.html
redis源碼學習_整數集合