Redis5種數據結構的運用及實現

1.數據結構

1.1字符串

? ? ? ?字符串類型的值實際可以是字符串、數字(整數,浮點數),甚至是二進制(圖片、視頻),但是值不能超過512MB。
1)常用命令

  • 設置值set(O(1)):set key value [ex seconds] [px milliseconds] [nx|xx]
       set命令有幾個選項:
           [ex seconds]:為鍵設置秒級過期時間;
           [px milliseconds]:為鍵設置毫秒級過期時間;
           [nx]:鍵必須不存在才可以設置成功;
           [xx]:鍵必須存在才可以設置成功;
    還有setex和setnx分別對應ex和nx作用;
  • 獲取值get(O(1)):get key若鍵不存在,返回nil空;
  • 批量設置值mset(O(k),k為鍵的個數):mset key value [key value ..]
  • 批量獲取值mget(O(k),k為鍵的個數):mget key [key ..]
  • 計數(O(1)) incr decr incrby decrby incrbyfloat
      (incr key)用于對值做自增操作,返回結果分三種:值不是整數,返回錯誤(error);值是整數,返回自增后的結果;鍵不存在,按照值為0自增,返回1,并創建此鍵值對;
      (decr)自減;(incrby)自增指定數字;(decrby)自減指定數字;(incrbyfloat)自增浮點數。

2)不常用命令:

  • 追加值(O(1)) append key value 向字符串尾部追加值;
  • 字符串長度(O(1)) strlen key 返回字符串長度;
  • 設置并返回原值(O(1)) getset key value 設置值并返回原值;
  • 設置指定位置的字符(O(1)) setrange key offset value
  • 獲取部分字符(O(n),n為字符串長度) getrange key start end,start和end分別為開始和結束偏移量,從0開始計算

3)內部編碼:int 8字節長整形;embstr (基于SDS實現)小于等于39個字節的字符串;raw(基于SDS實現) 大于39個字節的字符串

4)典型使用場景:緩存數據,計數,共享Session,限速;

1.2哈希

鍵指向一個映射關系field-value;
1)命令

  • 設置值O(1) hset key field value,成功返回1,反之返回0。此外提供hsetnx(對應setnx);
  • 獲取值O(1) hget key field 不存在返回nil;
  • 刪除O(k) field hdel key field [field…] 返回成功刪除field的個數;
  • 計算field個數O(1) hlen key
  • 批量設置或獲取O(k)
    hmget key field [field…]
    hmset key field value [field value…]
  • 判斷field是否存在O(1) hexists key field
  • 獲取所有field/value/field-value O(n) hkeys key/hvals key/hgetall key。在使用hgetall時,如果哈希元素個數比較多,會存在阻塞Redis的可能。
  • 計數O(1) hincrby key field value hincrbyfloat效果等同于incrby/incrbyfloat
  • 計算value的字符串長度O(1) hstrlen key field
  1. 內部編碼
      Ziplist(壓縮列表):當哈希類型元素個數小于hash-max-ziplist-entries配置(默認512)、同時所有值都小于hash-max-ziplist-value配置(默認64字節)時,使用ziplist作為哈希的內部實現,以緊湊結構實現多個元素的連續存儲,所有在節省內存方面比hashtable好。
      Hashtable(哈希表):當哈希類型無法滿足ziplist條件時,Redis會使用hashtable作為哈希的內部實現,因為ziplist此時讀寫效率會下降,而hashtable讀寫效率時O(1)的,但會消耗更多內存。

1.3列表

列表中的元素是有序的,可以通過索引下標獲取某個元素或者某個范圍內的元素列表;列表中的元素可以是重復的;最多可以存儲232-1個元素;可以對列表兩端插入和彈出。
1)命令

a)添加操作

  • 從右邊/左邊插入元素O(k) rpush/lpush key value [value …]
  • 向某個元素前/后插入元素O(n) linsert key before|after pivot value。命令會找到等于pivot的元素并在其前或者后插入新元素value;
    (返回值均為操作后列表的長度)

b)查找

  • 獲取指定范圍內的元素列表 lrange key start end。索引下標從左到右分別是0到N-1;end選項包含了自身
  • 獲取列表指定索引下標的元素O(n) lindex key index
  • 獲取列表長度O(1) llen key

c)刪除

  • 從列表左側彈出元素O(1) lpop key
  • 從列表右側彈出元素O(1) rpop key
  • 刪除指定元素O(n) lrem key count value;從列表中找到等于value的元素進行刪除,根據count不同分為三種情況:
    ①count>0,從左到右,刪除最多count個元素;
    ②count<0,從右到左,刪除最多count絕對值個元素;
    ③count=0,刪除所有;
  • 按照索引范圍修剪列表O(n) ltrim key start end;只保留從start到end的元素;

d)修改

  • 修改指定索引下標的元素O(n) lset key index newValue

e)阻塞操作O(1)
  blpop key [key …] timeout
  brpop key [key …] timeout
  blpop/brpop是lpop和rpop的阻塞版本,其中key [key …]代表多個列表的鍵;timeout代表阻塞時間。若列表為空,timeout=0則一直阻塞或者等待timeout時間后返回;若列表不為空,客戶端立即返回,
  需要注意的是:1.如果是多個鍵,brpop會從左至右依次遍歷鍵,一旦有一個鍵能彈出元素,客戶端立即返回;2.如果多個客戶端對用一個鍵指向brpop,那么最先執行的客戶端可以獲取到彈出的值;

2)內部編碼
  ziplist,同哈希,元素個數小于list-max-ziplist-entries配置,且每個元素值小于list-max-ziplist-value配置,采用壓縮列表作為內部實現來減少內存使用;
  linkedlist(鏈表),無法滿足ziplist條件時,采用linkedlist;

1.4集合

集合用于保存多個字符串元素,但集合不允許有重復元素,且元素無序,不能通過下標獲取元素;Redis除了支持集合內部從增刪改查,還支持多個集合的交、并、差。
1)命令

a)集合內操作

  • 添加元素O(k)sadd key element [element …],返回成功添加元素的個數;
  • 刪除元素O(k) srem key element [element …],返回成功刪除元素的個數;
  • 計算元素個數O(1) scard key
  • 判斷元素是否在集合中O(1) sismember key element,給定元素在集合中返回1,反之0;
  • 隨機從集合中返回指定個數元素O(count) srandmember key [count],count默認為0;
  • 從集合中隨機彈出元素O(1) spop key [count]
  • 獲取所有元素O(n) smembers key,重操作,可能會阻塞其他操作;

b)集合間操作

  • 求集合的交集 sinter key [key …]
  • 求集合的并集 sunion key [key …]
  • 求多個集合的差集 sdiff key [key …]
  • 將交集、并集、差集的結果保存
    sinterstore destination key [key …]
    sunionstore destination key [key …]
    sdiffstore destination key [key …]
    集合間的運算在元素較多的情況下比較耗時,可以通過上述命令保存結果;

2)內部編碼
  intset(整數集合),當集合中元素都是整數且元素個數小于set-max-inset-entries配置時采用,可以減少內存使用;
  hashtable(哈希表),無法滿足intset條件時采用

1.5 有序集合

有序集保留了集合不能有重復成員的特性,同時對有序集合中的元素進行了排序。
1)命令

a)集合內

  • 添加成員O(log(n)) zadd key score member [score member];返回結果代表成功添加成員的個數;同時zadd有4個選項:
    ①nx:member必須不存在;
    ②xx:member必須存在;
    ③ch:返回此次操作后,有序集合元素和分數發生變化的個數;
    ④incr:對score做增加,相當于zincrby;

  • 計算成員個數O(1) zcard key

  • 計算某個成員的分數 zscore key member,成員不存在返回nil;

  • 計算成員排名 zrank key member/zrerank key member;zrank從低到高返回排名,zrerank反之;

  • 刪除成員 zrem key member [member …]

  • 增加成員分數 zincrby key increment member

  • 返回指定排名范圍的成員 zrange/zrevrange key start end [withscores],zrange從低到高,zrevrange反之,withscores同時返回分數;

  • 返回指定分數范圍的成員
    zrangebyscore key min max [withscores] [limit offset count]
    zrevrangebyscore key max min [withscores] [limit offset count]
      [limit offset count]可以限制輸出的起始位置和個數;min和max還支持開區間(括號)和閉區間(中括號),-inf和+inf分別代表無限小和無限大;

  • 返回指定分數范圍成員個數 zcount key min max

  • 刪除指定排名內的升序元素 zremrangebyrank key start end

  • 刪除指定分數范圍的成員 zremrangebyscore key min max
    b)集合間操作

  • 交集,zinterstore destination numkeys key [key …] [weights weight [weight …]] [aggregate sum|min|max]
    -destination:交集計算結果保存到的鍵名;
    -numkeys:需要做交集計算鍵的個數;
    -key [key …]:需要做交集計算的鍵;
    -weights weight [weight …]:每個鍵的權重,做交集運算時,每個鍵中的分數自己乘以這個權重,默認是1;
    -aggregate sum|min|max:計算成員交集后,分值可以按照和、最小值、最大值做匯總,默認是sum;

  • 并集,zunionstore destination numkeys key [key …] [weights weight [weight …]] [aggregate sum|min|max]
    參數與zinterstore是一致的;

2)內部編碼
  ziplist(壓縮列表):當有序集合的元素個數小于zset-max-ziplist-entries配置(默認128),同時每個元素的值都小于zset-max-ziplist-value配置,會采用ziplist來減少內存使用;
  skiplist(跳躍表):當ziplist條件不滿足時采用;

1.6 鍵管理

1)單個鍵管理

  • 鍵重命名 rename/renamenx key newKey,使用renamenx防止重命名時覆蓋已有的鍵,若重命名時新名的鍵已存在,則renamenx返回0;
    在重命名鍵期間會執行del命令刪除舊的鍵,如果對應鍵的值比較大,會存在阻塞Redis的可能;key和newKey相同,Redis3.2之前會返回錯誤,Redis3.2會返回OK;

  • 隨機返回一個鍵 randomkey

  • 鍵過期
    expire key seconds:鍵在seconds秒后過期;
    expireat key timestamp:建在秒級時間戳timestamp后過期;
    如果expire key的鍵不存在,返回0;
    如果過期時間為負值,鍵會立即被刪除,猶如使用del命令一樣;
    persist命令可以將鍵的過期時間清除;
    對于字符串類型鍵,執行set命令會去掉過期時間;
    Redis不支持二級數據結構內部元素的過期功能;

  • 遷移鍵
    move key db,用于在Redis內部進行數據遷移,由于Redis內部可以有多個數據庫,彼此在數據上相互隔離;
    dump key + restore key ttl value,在源Redis上,dump命令會將鍵值序列化,格式采用RDB;在目標Redis上,restore將序列化后的鍵值反序列化復原,ttl參數表示過期時間,ttl=0表示沒有過期時間。整個遷移過程是非原子性的且遷移過程是開啟了兩個客戶端連接。

  • migrate host port key|"" destination-db timeout [copy] [replace] [keys key [key …]],實際上migrate是將dump、restore、del三個命令進行組合,從而簡化了操作流程。Migrate具有原子性,不需要開啟多個Redis實例,只需要在源Redis上執行命令,數據的傳輸直接在源Redis和目標Redis上完成的;目標Redis完成restore后會發送OK給源Redis,源Redis接到后會根據migrate對應的選項來決定是否刪除源Redis上對應的鍵。
    -host:目標Redis的IP地址;
    -port:目標Redis的端口;
    -key|””:在Redis3.0.6之前migrate只支持一個鍵,所有此處為待遷移的鍵,在3.0.6之后支持多個鍵,此處為””空字符串;
    -destination-db:目標Redis的數據庫索引;
    -timeout:遷移的超時時間,單位為毫秒;
    -[copy]:添加此選項,遷移后并不刪除源鍵;
    -[replace]:添加此選項,不管目標Redis是否存在該鍵都回正常遷移并進行數據覆蓋;
    -[keys key[key …]]:遷移多個鍵

2)遍歷鍵
全量遍歷鍵 keys pattern
漸進式遍歷 scan cursor [match pattern] [count number]
為解決keys命令存在的問題,采用漸進式遍歷的方式來解決keys命令可能帶來的阻塞問題,每次scan命令的時間復雜度為O(1)
cursor 是必須參數,是一個游標,每次遍歷完會返回當前游標的值,從0開始,直到游標值為0遍歷結束
match 是可選參數,作用是進行模式匹配
count number 是可選參數,作用是表明每次要遍歷的鍵的個數,默認值為10,可適當增大;
即執行一次scan默認返回10個鍵和下一次scan需要的cursor,當返回的cursor=0,則所有的鍵均已經被遍歷.

3)數據庫管理

  • 切換數據庫 select dbIndex
    通過數字來區分不同數據庫,默認配置16個數據庫(0-15)。分布式中只能使用0號數據庫,因為Redis是單線程的,多個數據庫仍要共享一個CPU,且不同業務使用不同數據庫,當某一業務出現慢查詢會影響全局難以排查。可以用不同端口多個Redis實例來代替。
  • 清除數據庫 flushdb/flushall
    flushdb只清除當前數據庫,flushall清除所有數據庫。如果鍵值比較多,可能存在阻塞Redis的可能。

2.數據結構實現

2.1 簡單字符串(simple dynamic string,SDS)

? ? ? ?Redis沒有直接采用C語言傳統的字符串表示,而是自己構造了簡單動態字符串(SDS)的抽象類型作為默認字符串表示,embstr和raw都是基于此實現。每個sds.h/sdshdr結構表示一個SDS值:

 struct sdshde {
     //記錄buf數組中已使用字節的數量
     //等于SDS所保存字符串的長度
    int len;
     //記錄buf數組中未使用字節的數量
    int free;
     //字符串數組,用于保存字符串數組
    char buf[];
};

? ? ? ?SDS遵循C字符串以空字符'\0'結尾的慣例,保存空字符的1字節空間不計算在SDS的len屬性內,并且為空字符分配額外的1字節空間,以及添加空字符到字符串末尾等都是SDS函數自動完成的。
SDS與C字符串的區別

  • O(1)獲取字符串長度:C字符串并不記錄自身長度信息,獲取長度需遍歷字符串,對每個字符進行計算直到遇到空字符結尾,復雜度是O(n),而SDS自身記錄了長度屬性len,復雜度是O(1);
  • 杜絕緩沖區溢出:C字符不記錄自身長度帶來的另一個問題就是容易造成緩沖區溢出,在對字符串進行擴展時由于空間分配已經完成,所以擴展會對額外空間進行占用,會有修改已存在變量值的可能性;而SDS進行修改時會先檢查空間是否滿足修改所需的要求,不滿足會先自動擴展空間,然后才執行修改,也就不存在緩存區溢出的問題;
  • 減少修改字符串時帶來的內存重分配次數:C字符串的長度和底層數組的長度之間存在關聯,每次對字符串的修改(增長/縮短)都需要內存重分配,不然會造成緩沖區溢出/內存泄露; SDS通過未使用空間解除了字符串長度和底層數組長度之間的關聯,SDS通過未使用空間實現了空間預分配和惰性空間釋放;
    空間預分配
    用于優化SDS的字符串增長操作:對一個SDS的字符串進行擴展(如拼接等操作),且需要空間擴展,程序不僅會為SDS分配修改所必須要的空間,還會額外分配未使用空間,當修改后SDS的len<1Mb,則free=len,當修改后SDS的len>=1Mb,則len=1Mb,如果未使用空間足夠支持擴展則直接擴展字符串而無需進行空間擴展;
    惰性空間釋放
    用于優化SDS的字符串縮短操作:對一個SDS的字符串進行縮短時,程序并不立即使用內存重分配來回收多余字節,而是使free屬性記錄起來,并等待將來使用;
  • 二進制安全:C字符串的字符必須符合某種編碼,并且除末尾外不能包含空字符,因此只能存儲文本數據,不能存儲圖片、音頻、視頻、壓縮文件等二進制數據;而SDS是二進制安全的,會以處理二進制的方式來處理SDS存放在buf數組里面的數據,不會對數據進行任何限制、過濾,Redis在buf數組中保存的是二進制數據,并通過len屬性來判斷是否結束;
  • 兼容部分C字符串函數:SDS一樣遵循C字符串以空字符結尾的慣例,在buf分配空間時多分配一個字節來容納空字符,也就可以重用一部分<string.h>庫定義的函數,如strcasecmp進行字符串比較;

2.2 鏈表

? ? ? ?當一個列表鍵包含過多元素/包含的元素較長時,Redis采用鏈表作為列表鍵的底層實現。 除了鏈表鍵外,發布與訂閱、慢查詢、監視器等也用到了鏈表,服務器本身還使用鏈表來保存多個客戶端的狀態信息。

typedef struct listnode{
       //前置節點
       struct listNode *prev;
       //后置節點
       struct listNode *next;
       //節點的值
       void *value;
};

? ? ? ?雖然僅僅使用多個listNode結構就可以組成鏈表,但使用adlist.h/list來持有鏈表更方便:

typedef struct list{
       //表頭
       listNode *head;
       //表尾
       listNode *tail;
       //鏈表所包含的節點數
        unsigned long len;
       //節點復制函數
       void *(*dup)(void *ptr)
       //節點值釋放函數
       void (*free)(void *ptr);
       //節點值對比函數
       void (*match) (void *ptr,void *key);
   }

Redis的鏈表實現特性:

  • 雙端:鏈表節點帶有prev和next指針,獲取某個節點的前置和后置節點都是O(1);
  • 無環:表頭節點的prev指針和表尾的next指針都指向NULL,對鏈表的訪問以NULL為終點;
  • 帶表頭表尾指針:獲取表頭表尾節點的復雜度為O(1);
  • 帶鏈表長度計數器:使用len屬性來對list持有的鏈表節點進行計數,獲取節點數量的復雜度為O(1);
  • 多態:鏈表節點使用void*指針來保存節點值,可通過list結構的dup、free、match三個屬性為節點值設置類型特定函數,所以鏈表可以用于保存各種不同類型的值。

2.3 字典

? ? ? ?字典用于保存鍵值對的抽象數據結構,同時字典也是哈希鍵的底層實現之一。Redis的字典采用哈希表作為底層實現,一個哈希表里面包含多個哈希表節點,一個哈希表節點代表一個鍵值對。
哈希表

typedef struct dictht {
    //哈希表數組,數組中元素指向一個dictEntry結構的指針,每個dictEntry結構保存一個鍵值對  
    dictEntry **table;
    //哈希表大小,即table數組的大小
    unsigned long size;
    //哈希表大小掩碼,用于計算索引值 sizemask = size - 1;
    unsigned long sizemask;
    //該哈希表已有節點的數量
    unsigned long used;
}

哈希表節點

typedef struct dictEntry{
    // 鍵
    void *key;
    // 值,可以使一個指針、uint64_t整數或者int64_t整數
    union{
        void *val;
        uint64_tu64;
        int64_ts64;
    } v;
    // 指向下個哈希表節點,形成鏈表,將多個哈希值相同的鍵值對鏈接在一起,解決鍵沖突
    struct disEntry *next;
} dictEntry;

字典

typedef struct dict{
    //類型特定函數,指向dictType的指針,dictType結構保存了一簇用于操作特定類型鍵值對的函數  
    dictType *type;  
    //私有數據,保存了需要傳給特定函數的可選參數  
    void *privdate;
    //哈希表,ht[0]即為哈希表,ht[1]只在rehash時使用
    dictht ht [2];
    //rehash索引,記錄rehash目前的進度,當rehash不在進行時,值為-1;
    int rehashidx;
} dict;

typedef struct dictType{
    //計算哈希值
    unsigned int (*hashFunction) (const void *key);
    //復制鍵函數
    void *(*keyDup) (void *privdate, const void *key);
    //復制值的函數
    void *(*valDup) (void *privdate,const void *obj);
    //對比鍵的函數
   int (*keyCompare) (void *privdate,const void *key1, const void *key2);
    //銷毀鍵的函數  
    void (*keyDestructor) (void *privdate,void *key);
    //銷毀值的函數
    void (*valDestructor) (void *privdate,void *obj);
}

哈希算法
? ? ? ?根據鍵計算出哈希值和索引值,再根據索引值將包含鍵值對的哈希表節點放置到哈希表數組的指定索引上。
使用字典設置的哈希函數,計算鍵key的哈希值
hash = dict -> type -> hashFunction(key);
使用哈希表的sizemask屬性和哈希值,計算出索引值

index = hash & dict->ht[x].sizemask  

鍵沖突
? ? ? ?當有兩個或以上數量的鍵被分配到哈希數組同一個索引上面時,鍵發生沖突;Redis哈希表采用拉鏈法解決鍵沖突。
漸進式rehash
? ? ? ?隨著鍵值對的增多和減少,為維持負載因子(已保存節點數/哈希表大小),需要對哈希表大小進行相應的擴展和收縮,如果直接將ht[0]的鍵值對rehash到ht[1]由于數據量過大可能對服務器性能造成影響。所以通過漸進式rehash分多次,將rehash鍵值對所需的工作平攤到字典的每個增刪改查操作上,避免了集中式rehash帶來的龐大計算量,步驟如下:

  • 為ht[1]分配空間,如是擴展,則大小為大于等于h[0].used*2的2次冪;如是收縮,則大小為大于等于h[0].used的2次冪;
  • 字典同時維持2個哈希表ht[0]和ht[1],并維持一個索引計數器rehashidx,設置為0,表示rehash工作正式開始;
  • 在rehash期間,每次對字典執行增刪改查時,程序除執行指定操作外,會將ht[0]中所有鍵值對重新計算哈希值和索引值,放置到ht[1]指定位置上,然后rehashidx加1;
  • 隨著字典操作不斷執行,最終ht[0]所有鍵值對都遷移到ht[1]后,釋放ht[0],將ht[1]設置為ht[0],并在ht[1]創建空白哈希表,rehashidx置為-1;

2.4 跳躍表

? ? ? ?跳躍表是一種有序數據結構,通過在每個節點中維持多個指向其他節點的指針,從而達到快速訪問節點的目的。支持平均O(logN),最壞O(N)的查找。跳躍表只在有序集合和集群節點內部數據結構中被用到, 通由跳躍表節點zskiplistNode和保存跳躍表節點信息的zskiplist兩個結構定義。

zskiplist結構包含以下屬性:

  • header:指向跳躍表表頭節點;
  • tail:指向跳躍表表尾節點;
  • level:記錄目前跳躍表內,層數最大的那個節點的層數(表頭節點層數不計算在內);
  • length:記錄跳躍表的長度,即目前包含的節點數(表頭節點層數不計算在內);

zskiplistNode結構包含以下屬性:

  • 層(level):Lx代表第x層(1-32,創建節點時隨機生成),每層都包含指向其他節點的指針,層數量越多,訪問其他節點的速度越快,還包含2個屬性:前進指針和跨度,前進指針用于訪問位于表尾方向的其他節點,跨度記錄了前進指針所指節點與當前節點的距離;
  • 后退指針:節點中用BW標記節點的后退指針,指向位于當前節點的前一個節點;
  • 分值:一個double類型的浮點數,跳躍表中分值按各自所保存的分值從小到大排列;
  • 成員對象:指向一個字符串對象,存著SDS值,每個節點保存的節點對象必須唯一;

2.5 整數集合

? ? ? ?整數集合(intset)是Redis用于保存整數值的集合抽象數據結構,保存類型為int16_t、int32_t和int64_t的整數值。

typedef struct intset {
    //編碼方式
    uint32_t encoding;
    //集合包含的元素個數
    uint32_t length;
    //保存元素的數組,類型由encoding屬性的值確定,元素從小到大排列,無重復元素
    int8_t contents[];
}

升級
? ? ? ?當添加新元素,且新元素類型比現有元素類型都長時,集合需要先進行升級:

  • 根據新元素的類型,擴展整數集合底層數組空間的大小,為新元素分配空間;
  • 將現有元素轉換為新元素相同的類型,并將轉換后的元素放到正確位置,保持底層數組有序性不變;
  • 添加新元素到底層數組;

? ? ? ?所以向整數集合添加新元素的時間復雜度為O(N),同時采用升級還能提升整數集合的靈活性,可以隨意添加不同類型的整數而不用擔心出現類型錯誤,也盡可能的節約了內存,升級操作只會在需要的時候進行,而不是一開始就預先分配大量的空間。
降級
? ? ? ?整數集合不支持降級,一旦對數組進行了升級,編碼會一直保持升級后的狀態。

2.6 壓縮列表

? ? ? ?壓縮列表是Redis為了節約內存而開發的,由一系列特殊編碼的連續內存塊組成的順序型數據結構。

屬性 類型 長度 用途
zlbytes uint32_t 4字節 記錄整個壓縮列表占用的內存字節數:內存重分配和計算zlend位置是使用
zltail uint32_t 4字節 記錄壓縮列表表尾階段距離壓縮列表起始地址有多少個字節
zllen uint16-t 2字節 記錄壓縮列表包含的節點數:如果大于65535則需要遍歷計算
entryX 列表節點 不定 壓縮列表包含的各個節點,節點長度由節點保存的內容決定
zlend uint8_t 1字節 特殊值0xFF,用于標記壓縮列表的末端

壓縮列表節點由previous_entry_length、encoding、content三部分組成:

  • previous_entry_length:以字節為單位,記錄了壓縮列表前一個節點的長度,可以是1字節和5字節;
  • encoding:記錄節點的content屬性所保存數據的類型以及長度;
  • content:屬性負責保存節點的值,可以是一個字節數組或者整數;

連鎖更新
? ? ? ?由于previous_entry_length屬性都記錄了前一個節點的長度,長度小于254字節用1字節空間記錄否則用5字節空間,那么當一個壓縮列表中有多個連續且長度介于250-253字節的節點,添加一個大于等于254字節的節點到頭節點時,導致后續節點都需要空間重分配,擴展出4個字節構成5字節來記錄前一節點的長度,進而自身超出254字節,引起了后續節點的連鎖更新;在刪除節點時也可能會導致連鎖更新。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容