原始碼分析之-容器類-HashMap
一、HashMap基本原理
HashMap採用Entry陣列來儲存key-value對,每一個鍵值對組成了一個Entry實體,Entry類實際上是一個單向的連結串列結構,它具有Next指標,可以連線下一個Entry實體,依次來解決Hash衝突的問題,因為HashMap是按照Key的hash值來計算Entry在HashMap中儲存的位置的,如果hash值相同,而key內容不相等,就用連結串列來解決這種hash衝突。
二、HashMap原始碼分析
本文以JDK7原始碼分析一下HashMap的關鍵程式碼。
private static int roundUpToPowerOf2 (int number) {
// assert number >= 0 : "number must be non-negative";
return number >= MAXIMUM_CAPACITY
? MAXIMUM_CAPACITY
: (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
}
Integer.highestOneBit方法的作用是二進位制位除最左面為1的位之外全部置為0的值。 roundUpToPowerOf2方法的作用是取比給定值稍大的2的次冪。 至於這裡為什麼要取2的次冪,原因如下: (1)浪費空間 (2)減低查詢效率 舉例: 當陣列長度為15時,新增陣列時h & (length-1)計算成為hash&14(0x1110),那麼最後一位永遠是0,從而造成table陣列中 1(0x0001),3(0x0011),5(0x0101),7(0x0111),9(0x1001),11(0x1011)等位置永遠不可以存放資料,從而造成空間浪費;更糟的是這種情況中,陣列可以使用的位置比陣列長度小了很多,這意味著進一步增加了碰撞的機率,單個桶裡儲存的資料多,從而減慢了查詢的效率。
/**
* Initialize the hashing mask value. We defer initialization until we
* really need it.
*/
final boolean initHashSeedAsNeeded(int capacity) {
boolean currentAltHashing = hashSeed != 0;
boolean useAltHashing = sun.misc.VM.isBooted() &&
(capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
boolean switching = currentAltHashing ^ useAltHashing;
if (switching) {
hashSeed = useAltHashing
? sun.misc.Hashing.randomHashSeed(this)
: 0;
}
return switching;
}
用指定初始容量和指定載入因子構造一個新的空雜湊表。其中initHashSeedAsNeeded方法用於初始化hashSeed引數,其中hashSeed用於計算key的hash值,它與key的hashCode進行按位異或運算。這個hashSeed是一個與例項相關的隨機值,主要用於解決hash衝突。
final int hash(Object k) {
int h = hashSeed;
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h ^= k.hashCode();
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
因為length為2的指數倍,所以length-1所對應的二進位制位都為1,然後在與hashCode(key)做與運算,即可得到[0,length)內的索引,但是這裡有個問題,如果hashCode(key)的大於length的值,而且hashCode(key)的二進位制位的低位變化不大,那麼衝突就會很多。造成衝突的原因關鍵在於16限制了只能用低位來計算,高位直接捨棄了,所以我們需要額外的雜湊函式而不只是簡單的物件的hashCode方法了。具體來說,就是HashMap中hash函式乾的事了。
/**
* 返回hashcode=h的索引位置.
*/
static int indexFor(int h, int length) {
// assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
return h & (length-1);
}
/**
* 根據key查詢entry,從程式碼可以看出key=null是合法的
*/
final Entry<K,V> getEntry(Object key) {
if (size == 0) {
return null;
}
int hash = (key == null) ? 0 : hash(key);
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}
/**
*
*/
public V put(K key, V value) {
//判斷陣列table是否為空
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
//如果key為空呼叫putForNullKey方法新增鍵為null的entry
if (key == null)
return putForNullKey(value);
//得到key的hash值
int hash = hash(key);
//根據上面得到的hash值得到應該放到table中的位置
int i = indexFor(hash, table.length);
//遍歷i位置的連結串列
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
//判斷i位置是否存在相同的key的entry,存在則用新值替換老的值
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
//如果走到這裡,說明map中不存在鍵為key的entry
addEntry(hash, key, value, i);
return null;
}
void addEntry(int hash, K key, V value, int bucketIndex) {
// 如果當前table的大小大於閾值且當前位置的桶有資料,則對map進行擴容到原來的2倍
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
//擴容後重新計算key的hash值和應放置的位置
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
//根據傳入的引數建立一個新的entry放置在位置bucketIndex處
createEntry(hash, key, value, bucketIndex);
}
void createEntry(int hash, K key, V value, int bucketIndex) {
//先取出位置bucketIndex處的entry,這裡是位置bucketIndex處連結串列的頭結點
Entry<K,V> e = table[bucketIndex];
//把新建的entry放置到位置bucketIndex處作為該處連結串列的頭結點,並把此處原來的entry設定為
//新entry的後繼節點,也就是說每插入一個新值,都會放到連結串列的最前端。
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}
/**
* 插null值,從程式碼可以看出,null值全部被插到第一個桶裡面,而且只能插入一個key為null的資料
* 後面key為null的資料不會被插入
*/
private V putForNullKey(V value) {
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(0, null, value, 0);
return null;
}
相關推薦
原始碼分析之-容器類-HashMap
一、HashMap基本原理 HashMap採用Entry陣列來儲存key-value對,每一個鍵值對組成了一個Entry實體,Entry類實際上是一個單向的連結串列結構,它具有Next指
【kubernetes/k8s原始碼分析】kubelet原始碼分析之容器網路初始化原始碼分析
一. 網路基礎 1.1 網路名稱空間的操作 建立網路名稱空間: ip netns add 名稱空間內執行命令: ip netns exec 進入名稱空間: ip netns exec bash 1.2 bridge-nf-c
symfony原始碼分析之容器的生成與使用
symfony 的容器是有一個編譯過程的,框架初始化的時候會執行Symfony\Component\HttpKernel\Kernel::initializationContainer ,這個方法會對程式碼進行檢查,看是否需要生成新的容器程式碼。如果需要 Symfony 會將各個類的依賴關係通過
docker原始碼分析之容器日誌處理與log-driver實現
子程序:由一個程序(父程序)建立的程序,整合父程序大部分屬性,同時可以被父程序守護和管理。 (2) 你需要知道關於程序產生日誌的形式: 程序產生
java原始碼分析之集合框架HashMap 10
HashMap HashMap 是一個散列表,它儲存的內容是鍵值對(key-value)對映。 HashMap 繼承於AbstractMap,實現了Map、Cloneable、java.io.Serializable介面。 HashMap 的實現不是同步的,
JAVA原始碼分析之---Object類(一)---registerNatives,getClass方法的使用
本人java碼農一名,在工作中,萌生了分析java原始碼的想法,從今天開始,一步一步開始分析java原始碼吧。 本人閱讀jdk原始碼版本為jdk1.7。 既然是Java,那麼肯定要從最頂層的Object類開始分析。 Object全稱:java.lang.Objec
Spring原始碼分析之容器初始化
AnnotationConfigApplicationContext 核心類,BeanDefinition註冊器,所有的BeanD
Alink漫談(二十二) :原始碼分析之聚類評估
# Alink漫談(二十二) :原始碼分析之聚類評估 [ToC] ## 0x00 摘要 Alink 是阿里巴巴基於實時計算引擎 Flink 研發的新一代機器學習演算法平臺,是業界首個同時支援批式演算法、流式演算法的機器學習平臺。本文和上文將帶領大家來分析Alink中 聚類評估 的實現。 ## 0x01
【kubernetes/k8s原始碼分析】kubelet原始碼分析之啟動容器
主要是呼叫runtime,這裡預設為docker 0. 資料流 NewMainKubelet(cmd/kubelet/app/server.go) -> NewKubeGenericRuntimeManager(pkg/kubelet/kuberuntime/kuberuntime
JAVA原始碼分析之HashMap
前言 從事了好長時間的開發工作,平時只注重業務程式碼的開發,而忽略了java本身一些基礎,所以從現在開始閱讀以下jdk的原始碼,首先從集合開始吧!這一篇先看下HashMap的原始碼。 java集合架構 &nbs
STL原始碼分析之hashtable關聯容器 上
前言 前面我們分析過RB-tree關聯容器, RB-tree在插入(可重複和不可重複), 刪除等操作時間複雜度都是O(nlngn), 以及滿足5個規則, 以及以他為底層的配接器; 本節就來分析hashtable另個關聯容器, 他在插入, 刪除等操作都可以做到O(1)的時間複雜度.
STL原始碼分析之RB-tree關聯容器 上
前言 本節將分析STL中難度很高的RB-tree, 如果對紅黑樹有所認識的那麼分析起來的難度也就不是很大, 對紅黑樹沒有太多瞭解的直接來分析的難度就非常的大了, 可以對紅黑樹有個瞭解紅黑樹之原理和演算法詳細介紹. 紅黑樹是很類似與AVL-tree的, 但是因為AVL-tree在插入,
STL原始碼分析之slist有序容器 下
前言 上節我們對slist的基本構成, 構造析構做了分析, 本節 我們就來分析關於slist的基本元素操作. slist分析 基本屬性資訊 slist是隻有正向迭代, 所以只能直接獲取頭部的資料. template <class T, class Alloc =
STL原始碼分析之slist有序容器 上
前言 不同於list是Forward Iterator型別的雙向連結串列, slist是單向連結串列, 也是Bidirectional Iterator型別. slist主要耗費的空間小, 操作一些特定的操作更加的快, 同樣類似與slist, 在執行插入和刪除操作迭代器都不會像vec
STL原始碼分析之deque有序容器 中
前言 前一節我們分析了deque的基本使用, 本節我們來分析一下deque的對map的操作, 即插入, 刪除等. 但是本節只分析push, pop和刪除操作, 而insert操作有點複雜還是放到下節來分析. push, pop 因為deque的是能夠雙向操作, 所以其push
STL原始碼分析之deque有序容器 上
前言 deque的功能很強大, 其複雜度也比list, vector複雜很多. deque是一個random_access_iterator_tag型別. 前面分析過vector是儲存在連續的線性空間, 頭插入和刪除其代價都很大, 當陣列滿了還要重新尋找更大的空間; deque也是一
STL原始碼分析之list有序容器 下
前言 前兩節對list的push, pop, insert等操作做了分析, 本節準備探討list怎麼實現sort功能. list是一個迴圈雙向連結串列, 不是一個連續地址空間, 所以sort功能需要特殊的演算法單獨實現, 而不能用演算法中的sort. 當然還可以將list的元素插
JDK10原始碼分析之HashMap
HashMap在工作中大量使用,但是具體原理和實現是如何的呢?技術細節是什麼?帶著很多疑問,我們來看下JDK10原始碼吧。 1、資料結構 採用Node<K,V>[]陣列,其中,Node<K,V>這個類實現Map.Entry<K,V>,是一個連結串列結構的物件,並且在一定
cocos2d-x3.2原始碼分析之 ---- 類FileUtils實現把資源放在Resources檔案目錄下達到多平臺的引用
我以TMXTiledMap::Create函式為講解物件。 首先轉到TMXTiledMap::Create的定義中,其定義是在CCFastTMXTiledMap.cpp檔案中,程式碼1如下。其目錄是E:\mycoscos2d\test2\cocos2d\cocos\2d中,這就說明這是與具體平臺無關
雲客Drupal8原始碼分析之實體Entity(二)配置實體基類
配置實體基類是系統定義的一個用於配置實體的抽象基類,繼承自實體基類,完成了配置實體的大部分通用功能,具體的配置實體往往會繼承它,比如使用者角色實體,這樣寫少量程式碼即可,類定義如下: Drupal\Core\Config\Entity\ConfigEntityBase 實