Redis的hash結構跟Java的HashMap十分相似,同樣都是用數組加鏈表組成(還是是數組和鏈表,和上一節的quicklist組成是一樣的吧,只不過quicklist的結構是由數組組成的鏈表,而hash則是由鏈表組成的數組)。Set內部結構也是hash,只不過hashEntry中所有的 value 都是一個值NULL(又抄我java)。hash表就不多說了,直接上圖,哦,這里要注意,hash結構的值只能是string類型(redis中整數什么的都可以視為string)。
探索hash字典內部原理
struct hashEntry {
void* key;
void* val;
hashEntry* next; // 下一個 entry的地址值
}
struct hashTable{
hashEntry** table; // 二維數組,相當于java中的 new Entry[][]
long size; // 第一維數組的長度,hash表中數組部分的長度
long used; // hash 表中的所有元素個數
...
}
這個二維數組的行代表著hash表中數組的部分的長度,列代表著hash表中的每一個鏈表的長度。
兩個hashTable
實際上,每個hash類型的數據結構里面都會有兩個hashTable,就是因為在擴容的時候采取了一種漸進式的擴容方式,這種方式會提前創建一個新的hashtable,容量為當前used*2,觸發擴容條件時,將老hashTable的數據分批次遷移到新的hashtable當中,這種方式最大的好處就是不會因為一次性遷移的數據量太大而導致讀寫的阻塞。(CopyOnWriteArrayList是嘛)
在讀請求進來時,如果發現擴容還未結束,會先從老hashTable查,查不到會再從新的hashTable查。而有寫請求時,會順便將這個元素rehash一次,把值直接存到新hashTable中。
擴容時機
1.當hash中元素總個數=hash中數組的長度時,且沒有正在進行bgsave或bgrewriteaof操作(持久化內存快照)。
2.當hash中元素總個數=hash中數組的長度的5倍時,即使正在進行bgsave或bgrewriteaof操作,也會強制擴容。(我覺得紅黑樹還能再挺一下)
縮容時機
當hash中元素總個數=hash中數組的長度的1/10時,便會縮容。
Set中的intset
struct intset {
uint32_t encoding;//編碼方式,可選16/32/64位
uint32_t length;//數組長度
int8_t contents[];//元素
}
當set內元素個數較少且都為整型時,set內部結構會優化為一個intset,結構和壓縮列表類似,不過intset的元素都為整型,在intset中定位一個元素時,會使用二分法查找,時間復雜度為O(logn)。
intset 只能升級,不能降級。將一個大于當前encoding位數的元素插入到intset ,會導致intset升級,或者直接變成hash結構。