1. 程式人生 > >原始碼分析之-容器類-HashMap

原始碼分析之-容器類-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 實