從源碼看redis的'set'結構

sadd 命令用來往 set 結構中存入數據java

> sadd a 1
(integer) 1
複製代碼

smembers能夠查到存儲的內容git

> smembers a
1) "1"
複製代碼

sadd命令執行追蹤

sadd的執行入口在 saddCommand,若是key不存在那麼第一件事情就是確認底層的存儲結構github

Code.SLICE.source("robj *setTypeCreate(sds value) {" +
      " if (isSdsRepresentableAsLongLong(value,NULL) == C_OK)" +
      " return createIntsetObject();" +
      " return createSetObject();" +
      "}")
      .interpretation("看set中要添加的值是否可以轉成long long類型,若是能夠,set的類型爲IntSet,不然使用hash table");
複製代碼

肯定好結構以後,能夠往裏面去增長redis

  • 若是本來是 hashtable,那麼直接插入便可;
  • 若是本來是intset,則須要看新插入的元素是否知足intset的結構,不然轉成hashtable存儲
Code.SLICE.source("else if (subject->encoding == OBJ_ENCODING_INTSET) {" +
        " if (isSdsRepresentableAsLongLong(value,&llval) == C_OK) {" +
        " uint8_t success = 0;" +
        " subject->ptr = intsetAdd(subject->ptr,llval,&success);" +
        " if (success) {" +
        " /* Convert to regular set when the intset contains" +
        " * too many entries. */" +
        " if (intsetLen(subject->ptr) > server.set_max_intset_entries)" +
        " setTypeConvert(subject,OBJ_ENCODING_HT);" +
        " return 1;" +
        " }" +
        " } else {" +
        " /* Failed to get integer from object, convert to regular set. */" +
        " setTypeConvert(subject,OBJ_ENCODING_HT);" +
        "" +
        " /* The set *was* an intset and this value is not integer" +
        " * encodable, so dictAdd should always work. */" +
        " serverAssert(dictAdd(subject->ptr,sdsdup(value),NULL) == DICT_OK);" +
        " return 1;" +
        " }" +
        " }")
        .interpretation("set的另一種數據結構,intset ,只要當前數據還可以轉換成 longlong,那麼繼續在set中增長,不然將結構轉換成 hashtable")
        .interpretation("1: 往intset添加成功以後,若是集合的元素個數已經超過了 配置的 set_max_intset_entries ,那麼轉換成 hashtable");
複製代碼

在往intset中插入的時候,須要確保不存存儲同樣的元素,所以會先查找是否有同樣值的元素bash

Code.SLICE.source("int min = 0, max = intrev32ifbe(is->length)-1, mid = -1;")
        .interpretation("先記下最小值和最大值的下標");
Code.SLICE.source(" 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. */" +
        " if (value > _intsetGet(is,intrev32ifbe(is->length)-1)) {" +
        " if (pos) *pos = intrev32ifbe(is->length);" +
        " return 0;" +
        " } else if (value < _intsetGet(is,0)) {" +
        " if (pos) *pos = 0;" +
        " return 0;" +
        " }" +
        " }")
        .interpretation("處理邊界狀況")
        .interpretation("1: 若是集合中是空的,直接在開始插入便可")
        .interpretation("2: 若是新插入的值小於當前最小的值,在開頭插入便可")
        .interpretation("3: 若是插入新值大於當前最大的值,在結尾插入便可");
Code.SLICE.source("while(max >= min) {" +
        " mid = ((unsigned int)min + (unsigned int)max) >> 1;" +
        " cur = _intsetGet(is,mid);" +
        " if (value > cur) {" +
        " min = mid+1;" +
        " } else if (value < cur) {" +
        " max = mid-1;" +
        " } else {" +
        " break;" +
        " }" +
        " }")
        .interpretation("二分查找,找到插入的位置,這裏要麼找到現有值元素的位置,要麼找到要插入的位置");
複製代碼

總結

  1. set 底層使用了兩種結構 intset和hashtable ;
  2. intset 內部是按照升序排列;
  3. intset根據數值大小會分紅不一樣的數據結構,方便節省空間

附錄

sadd命令源碼完整的追蹤過程數據結構

Redis開發與運維運維

Redis的設計與實現ui

相關文章
相關標籤/搜索