原理
unordered_map 內部實現是散列表,是一個無序的容器。內部實現的散列表采用了鏈地址法,意思是使用鏈表來解決散列沖突。當往容器中加入一個元素的時候,會計算散列值,然后取余之后放到一個桶 (bucket) 里。如果不斷往容器加元素,那么所有的桶都會變成一個很長的鏈表,這樣效率就很低了,這種情況應該如何避免呢?unordered_map 采用了擴容的機制,當負載因子 (load factor) 超過了閾值,就會進行一次擴容。負載因子的計算方法是總鍵值對數除以桶數。擴容的時候,會重新計算散列,重新放入到桶里。
cplusplus 文檔:https://www.cplusplus.com/reference/unordered_map/unordered_map/
代碼聲明
從以下的代碼中,我們可以看到 unordered_map 內部的存儲使用 allocator 來管理的,存儲的是 std::pair 類型的數據。
template<class _Key, class _Tp,
class _Hash = hash<_Key>,
class _Pred = std::equal_to<_Key>,
class _Alloc = std::allocator<std::pair<const _Key, _Tp> > >
class unordered_map;
自定義類型
一個自定義的類型,如果要作為 Key,應該如何寫代碼呢?假如我們定義了一個 Apple 類型,里面只有構造器(構造器內部只有輸出)。直接將 Apple 作為 key 會出現報以下錯誤。std::hash<Apple>
does not provide a call provider. 這意味着需要我們自定義散列的規則。如果細細思考 unordered_map 的模板參數,你會發現你不僅需要定義散列的規則,還需要定義相等判定的規則。
散列
可以采用的實現方式:
- 覆寫 operator() 的類或者結構體,需要在 unordered_map 實例化的地方指定模板參數
struct apple_hash {
std::size_t operator()(const Apple& apple) const {
return std::hash<int>()(apple.GetData());
}
};
std::unordered_map<Apple, int, apple_hash> apple_to_int;
- 特化 std::hash 來實現,經過特化后,在使用 unordered_map 的時候就不用指定模板參數了
特化 std::hash 的方法如下,覆寫 operator() 需要注意幾個細節,返回類型是 size_t,輸入的參數是 const 引用,函數體是 const。
template<>
struct std::hash<Apple> {
std::size_t operator()(const Apple& apple) const {
return std::hash<int>()(apple.GetData());
}
};
判等
- 覆寫 operator() 的類或者結構體作為模板參數
struct apple_equal {
bool operator()(const Apple& x, const Apple& y) const {
std::cout << "apple_equal" << std::endl;
return x.GetData() == y.GetData();
}
};
std::unordered_map<Apple, int, std::hash<Apple>, apple_equal> apple_to_int; // 不知道怎么跳過第三個參數進行實例化hhh
- 特化 std::equal_to來實現
template<>
struct std::equal_to<Apple> {
bool operator()(const Apple& x, const Apple& y) const {
return x.GetData() == y.GetData();
}
};
- 覆寫類的 operator==() 方法
bool operator==(const Apple& apple) const {
return this->data_ == apple.data_;
}