jdk容器之HashMap

1、HashMap簡介

    原博客地址:http://www.cnblogs.com/tstd/p/5055286.htmlhtml

  1. hash就是經過散列算法,將一個任意長度關鍵字轉換爲一個固定長度的散列值,因爲hash函數是一個壓縮映射,因此可能存在不一樣的關鍵字進行hash後所獲得的hash值相同的現象。java

     hash函數定義以下:算法

  2. static int hash(int h) {
       // 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);
    }
    /**
     * Returns index for hash code h.
     */
    static int indexFor(int h, int length) {
       return h & (length - 1);
    }

    解析:數組

    1. 首先看第二個函數indexFor,這個函數用於尋找hash值h在一個長度爲length的數組中的位置(即下標值),而後將該hash值所對應的元素存儲在相應的位置上。app

      因爲HashMap中定義的數組長度均爲2的冪的形式,而2^n-1表示爲2進制時的形式爲01....1(1個0後面n個1的形式),因此經過h&(length-1),能夠將h映射到0~(length-1)之間的數,達到h%length同樣的效果,卻擁有比h%length更高的效率;函數

    2. 再看hash函數,因爲h的低位相同(尤爲是等於length位數的部分)時,通過hash後衝突的機率比較大,因此在hash函數中給h中的各個位加入了一些隨機性,從而儘量下降衝突;spa

  3. HashMap的底層是用一個Entrry數組實現的,經過indexFor函數計算hash值爲h的元素在一個長度爲length的Entry數組中的位置,當出現衝突時,將hash值相同的元素以一個單向鏈表的形式存儲在Entry數組的同一位置,而且最早存儲的元素位於鏈表尾部,最後到達的元素位於鏈表頭。也就是說HashMap的底層結構是一個數組,而數組的元素是一個單向鏈表。當存儲一個key=null的元素時,HashMap默認將該元素置於Entry[0]的位置。code

  4. Entry是HashMap的內部類,它繼承了Map中的Entry接口,它定義了鍵(key),值(value),和下一個節點的引用(next),以及hash值。htm

  5. HashMap中定義了初始容量、加載因子、閾值和最大容量等參數。容量即HashMap中Entry數組所能包含的元素數;初始容量爲16,即默認最小容量爲16,使用時能夠經過含參構造函數構造一個知足用戶所需大小的HashMap;加載因子和閾值主要用於定義擴容臨界值(閾值=容量*加載因子),當已使用容量達到閾值時,將會擴容;最大容量即默認HashMap所能擴容達到的最大大小,爲2^30。blog

    注意:

    1. HashMap進行擴容時,數組長度發生了變化,本來在同一鏈表中的元素如今可能並不在同一鏈表中,因此須要對數組中的每個元素都從新計算位置,並存儲到新數組中,這是一個很是耗時的操做,因此若是事先可以預知須要使用的HashMap的長度的話,最好將長度設置爲所需大小,這樣能夠減小時間消耗,提升效率;

    2.  默認加載因子=0.75,但能夠根據須要本身設置一個恰當的加載因子。加載因子越大,數組填充得越滿,空間利用率越高,但這樣的話可能會增大沖突機率,影響查詢效率;而加載因子太小的話,則會形成空間上的浪費。因此須要經過設置一個有效地加載因子,在時空這二者間尋找一個平衡點。

2、HashMap的增刪改查

  1. 構造方法

    HashMap有4個構造方法

  2. //構造一個指定初始容量和加載因子的HashMap
    public HashMap( int initialCapacity, float loadFactor)   
    //構造一個指定初始加載容量和使用默認加載因子(0.75)的HashMap
    public HashMap( int initialCapacity)
    //構造一個使用默認初始容量(16)和默認加載因子(0.75)的HashMap
    public HashMap()
    //構造一個指定map的HashMap,所建立HashMap使用默認加載因子(0.75)和足以容納指定map的初始容量
    public HashMap(Map<? extends K, ? extends V> m)

           當使用指定初始容量的構造函數建立HashMap時,所獲得的HashMap的實際容量是大於指定容量的最小的2的冪次方,也就是說,不管使用何種構造函數,最終所獲得的HashMap的容量均爲2的冪

  3. 向HashMap中增長元素

  4. HashMap中增長元素的方法和修改元素的方法相同,均爲public V put(K key, V value),具體操做流程爲:

    1. 計算hash = hash(key.hashCode()),獲得鍵值key的hash值

    2. 計算index = indexFor(hash,table.length),獲得該元素在數組中的位置

    3. 取得index位置的鏈表,遍歷鏈表,若鏈表中包含hash和key值均相同的元素,則用新的value值替換原有的value值,返回原value值;不然在該鏈表頭新增一個Entry節點,將該元素存入新建的Entry節點中,並返回null;

        注意:在新增Entry節點時,數組大小會加1,當size>閾值時,將調用resize(2*table.length)方法擴容,也就是說若新增節點時,數組大小達到了擴容閾值,則會將原數組擴大一倍。數組擴容時須要經過遍歷對原數組中的全部元素從新計算下標值,並添加到新數組相應的位置,該算法比較耗時,因此在使用HashMap時若能提早預知數組容量,則最好預設初始容量。

  5. 從 HashMap中刪除元素

    HashMap中刪除元素的方法爲public V remove(Object key),即根據元素的key值刪除該元素,並返回所刪除元素的value值

    1. 計算hash = hash(key.hashCode()),獲得鍵值key的hash值

    2. 計算index = indexFor(hash,table.length),獲得該元素在數組中的位置

    3. 取得該位置處的第一個鏈表節點

    4. 遍歷該鏈表,找到key所在的位置,若key位於鏈表頭,則將key節點的next賦值給table[index],不然,將key的下一節點賦值給key的上一節點

  6. 從 HashMap中查找元素

  7.         從HashMap中查找元素的方法爲public V get(Object key),該方法經過遍歷HashMap,比較是否存在(e.hash == hash && ((k = e.key) == key || key.equals(k)))的元素,若存在,返回該元素的value值,不然,返回null

  8. HashMap中是否包含鍵爲key或值爲value的元素

        查詢HashMap中是否包含某一鍵key或某一值value的元素時,均須要遍歷數組進行查找。可是針對key進行查找時,能夠經過hash函數計算出key所在的位置,從而只須要遍歷該位置處的鏈表便可。而針對value進行查找時,須要遍歷整個數組。因此針對key進行查找的效率要高於針對value進行查找的效率。
相關文章
相關標籤/搜索