1.為什么使用
前面介紹了ziplist壓縮列表,他主要在zset和hash這兩種數據類型中,並且當zset和hash中的數據元素較少的時候使用.它的特點是提高了內存使用率,並且可以方便的從兩邊遍歷數據,但是因為它把數據保存在一塊連續的內存空間中,所以它並不適合頻繁的刪除,插入,修改操作,這會造成頻繁的申請內存和復制數據,而且極端情況下還會引起級聯更新.為了解決級聯更新的問題,redis引入了一種新的數據類型listpack.
listpack的出現並不能解決鏈表中頻繁插入和刪除造成的重新申請空間和復制數據的問題,主要為了解決級聯更新的問題,未來redis可能會使用listpack完全替代ziplist.
2.內部結構
2.1 listpack
struct listpack<T> { int32 total_bytes; // 整個listpack,占用的總字節數 int16 size; // 整個listpack中元素個數 T[] entries; // 緊湊排列的元素列表 int8 end; // listpack的結束符,恆為 0xFF }
total_bytes:和ziplist中一樣,listpack中也記錄了整個listpack的總字節數
size:和ziplist中一樣,listpack中也記錄了整個listpack的總元素的個數
但是在listpack中並沒有記錄最后一個元素的地址,這個是和ziplist中不同的.
2.2 lpentry
struct lpentry { int<var> encoding; //entry中數據的的編碼方式,具體在下面介紹 optional byte[] content; //存放的具體的內容 int<var> length; // 當前lpEntry的長度 }
length:和ziplist中的最大不同支出在於,listpack把當前長度放到了最后,並且這里存放的也不是前一個entry的長度,而是當前entry的長度.這樣llistpack不需要保存最后一個元素的地址,只需要根據整個listpack的長度和最后一個entry的長度就可以計算出最后一個entry的地址.因為entry中不保存其他entry中的長度了,所以當對listpack進行增刪改的時候並不會出現級聯更新的情況.為了提高內存使用率,redis把length的長度設計為不固定的,它會隨着當前entry長度的改變而改變.編碼方式和utf8一樣,先讀取最后一個字節的最高位如果是1,則表示下一個字節也屬於長度的一部分,知道找到第一個位不是1的字節為止,但是length的最大長度位5個字節.
encoding:同樣是為了提高內存使用率,reids也對encoding經過了復雜的設計.
1、0xxxxxxx 表示非負小整數,可以表示 0~127。
2、10xxxxxx 表示小字符串,長度范圍是 0~63,content 字段為字符串的內容。
3、110xxxxx yyyyyyyy 表示有符號整數,范圍是-2048~2047。
4、1110xxxx yyyyyyyy 表示中等長度的字符串,長度范圍是 0~2047,content 字段為字符串的內容。
5、11110000 aaaaaaaa bbbbbbbb cccccccc dddddddd 表示大字符串,四個字節表示長度,content 字段為字符串內容。
6、11110001 aaaaaaaa bbbbbbbb 表示 2 字節有符號整數。內容保存在content中
7、11110010 aaaaaaaa bbbbbbbb cccccccc 表示 3 字節有符號整數。直接保存在encoding中
8、11110011 aaaaaaaa bbbbbbbb cccccccc dddddddd 表示 4 字節有符號整數。直接保存在encoding中
9、11110011 aaaaaaaa ... hhhhhhhh 表示 8 字節有符號整數。直接保存在encoding中
10、11111111 表示 listpack 的結束符號,也就是 0xFF
3.使用數據類型
在redis6.*中listpack已經被用在在hash類型中替代ziplist了,暫時只有hash中使用