java提升(9)---HashMap解析

HashMap解析(一)

        平時一直再用hashmap並無稍微深刻的去了解它,本身花點時間想往裏面在深刻一點,發現它比arraylist難理解不少。html

     數據結構中有數組和鏈表來實現對數據的存儲,但這二者基本上是兩個極端。java

    數組:數組存儲區間是連續的,佔用內存嚴重,故空間複雜的很大。但數組的二分查找時間複雜度小,爲O(1);數組的特色是:尋址容易,插入和刪除困難;算法

    鏈表:鏈表存儲區間離散,佔用內存比較寬鬆,故空間複雜度很小,但時間複雜度很大,達O(N)鏈表的特色是:尋址困難,插入和刪除容易。   數組

 

1、HashMap的數據結構                                                                            

    HashMap其實是一個「鏈表散列」的數據結構,即數組和鏈表的結合體。看下面圖;來理解:數據結構

     從上圖中能夠看出,HashMap底層就是一個數組結構,只數組中的每一項又是一個鏈表。當新建一個HashMap的時候,就會初始化一個數組。app

transient Entry[] table;
 
static class Entry<K,V> implements Map.Entry<K,V> {
    final K key;
    V value;
    Entry<K,V> next;
    final int hash;
    ……
}

   能夠看出,Entry就是數組中的元素,每一個 Map.Entry 其實就是一個key-value對,它持有一個指向下一個元素的引用,這就構成了鏈表。性能

 

2、HashMap的存取實現                                                           

存儲this

public V put(K key, V value) {
    // HashMap容許存放null鍵和null值。
    // 當key爲null時,調用putForNullKey方法,將value放置在數組第一個位置。  
    if (key == null)
        return putForNullKey(value);
    // 根據key的keyCode從新計算hash值。
    int hash = hash(key.hashCode());
    // 搜索指定hash值在對應table中的索引。
    int i = indexFor(hash, table.length);
    // 若是 i 索引處的 Entry 不爲 null,經過循環不斷遍歷 e 元素的下一個元素。
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }
    // 若是i索引處的Entry爲null,代表此處尚未Entry。
    modCount++;
    // 將key、value添加到i索引處。
    addEntry(hash, key, value, i);
    return null;
}

        從上面的源代碼中能夠看出:當咱們往HashMap中put元素的時候,先根據key的hashCode從新計算hash值,根據hash值獲得這個元素在數組中的位置(即下標), 若是數組該位置上已經存放有其餘元素了,那spa

麼在這個位置上的元素將以鏈表的形式存放,新加入的放在鏈頭,最早加入的放在鏈尾。若是數組該位置上沒有元素,就直接將該元素放到此數組中的該位置上。.net

ddEntry(hash, key, value, i)方法根據計算出的hash值,將key-value對放在數組table的i索引處。addEntry 是 HashMap 提供的一個包訪問權限的方法,代碼以下:

void addEntry(int hash, K key, V value, int bucketIndex) {
    // 獲取指定 bucketIndex 索引處的 Entry  
    Entry<K,V> e = table[bucketIndex];
    // 將新建立的 Entry 放入 bucketIndex 索引處,並讓新的 Entry 指向原來的 Entry  
    table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
    // 若是 Map 中的 key-value 對的數量超過了極限
    if (size++ >= threshold)
    // 把 table 對象的長度擴充到原來的2倍。
        resize(2 * table.length);
}

        當系統決定存儲HashMap中的key-value對時,徹底沒有考慮Entry中的value,僅僅只是根據key來計算並決定每一個Entry的存儲位置。咱們徹底能夠把 Map 集合中的 value 當成 key 的附屬,當系統決定了 key 的

存儲位置以後,value 隨之保存在那裏便可。

讀取

public V get(Object key) {
    if (key == null)
        return getForNullKey();
    int hash = hash(key.hashCode());
    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.equals(k)))  
            return e.value;
    }
    return null;
}

        有了上面存儲時的hash算法做爲基礎,理解起來這段代碼就很容易了。從上面的源代碼中能夠看出:

  從HashMap中get元素時,首先計算key的hashCode,找到數組中對應位置的某一元素,而後經過key的equals方法在對應位置的鏈表中找到須要的元素。

概括

     1)hashMap的key容許爲null,當key爲null時,調用putForNullKey方法,將value放置在數組第一個位置。

     2)判斷是否key值惟一的標準,是經過對key值的hashCode計算得出,經過經過key獲取value值時也是計算key的hashCode的值去找value值。

    3)HashMap 在底層將 key-value 當成一個總體進行處理,這個總體就是一個 Entry 對象。HashMap 底層採用一個 Entry[] 數組來保存全部的 key-value 對,當須要存儲一個 Entry 對象時,會根據hash算法來決定

其在數組中的存儲位置,在根據equals方法決定其在該數組位置上的鏈表中的存儲位置;當須要取出一個Entry時,也會根據hash算法找到其在數組中的存儲位置,再根據equals方法從該位置上的鏈表中取出該Entry。

 

3、hashmap源碼解讀                                                                                      

       HashMap有兩個參數影響其性能:初始容量加載因子。默認初始容量是16,加載因子是0.75。容量是哈希表中桶(Entry數組)的數量,初始容量只是哈希表在建立時的容量。

加載因子是哈希表在其容量自動增長以前能夠達到多滿的一種尺度。當哈希表中的條目數超出了加載因子與當前容量的乘積時,經過調用 rehash 方法將容量翻倍。

  HashMap中定義的成員變量以下:

 
static final int DEFAULT_INITIAL_CAPACITY = 16;// 默認初始容量爲16,必須爲2的冪  
  
static final int MAXIMUM_CAPACITY = 1 << 30;// 最大容量爲2的30次方  
  
static final float DEFAULT_LOAD_FACTOR = 0.75f;// 默認加載因子0.75  
    
transient Entry<K,V>[] table;// Entry數組,哈希表,長度必須爲2的冪  
  
transient int size;// 已存元素的個數  
  
int threshold;// 下次擴容的臨界值,size>=threshold就會擴容  
  
final float loadFactor;// 加載因子  

HashMap一共重載了4個構造方法,分別爲:

HashMap()
          構造一個具備默認初始容量 (16) 和默認加載因子 (0.75) 的空 HashMap

HashMap(int initialCapacity)
          構造一個帶指定初始容量和默認加載因子 (0.75) 的空 HashMap

HashMap(int initialCapacity, float loadFactor)
          構造一個帶指定初始容量和加載因子的空 HashMap

HashMap(Map<? extendsK,? extendsV> m)
          構造一個映射關係與指定 Map 相同的 HashMap

看一下第三個構造方法源碼,其它構造方法最終調用的都是它。

public HashMap(int initialCapacity, float loadFactor) {  
    // 參數判斷,不合法拋出運行時異常  
    if (initialCapacity < 0)  
        throw new IllegalArgumentException("Illegal initial capacity: " +  
                                           initialCapacity);  
    if (initialCapacity > MAXIMUM_CAPACITY)  
        initialCapacity = MAXIMUM_CAPACITY;  
    if (loadFactor <= 0 || Float.isNaN(loadFactor))  
        throw new IllegalArgumentException("Illegal load factor: " +  
                                           loadFactor);  
  
    // Find a power of 2 >= initialCapacity  
    // 這裏須要注意一下  
    int capacity = 1;  
    while (capacity < initialCapacity)  
        capacity <<= 1;  
  
    // 設置加載因子  
    this.loadFactor = loadFactor;  
    // 設置下次擴容臨界值  
    threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);  
    // 初始化哈希表  
    table = new Entry[capacity];  
    useAltHashing = sun.misc.VM.isBooted() &&  
            (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);  
    init();  
}  

 

參考

                 一、HashMap深度解析(一)

                 二、  HashMap深度解析(二)

                三、  Java容器(四):HashMap(Java 7)的實現原理               

                四、   圖解集合 4 :HashMap

 

水滴石穿,成功的速度必定要超過父母老去的速度! 少尉【5】

相關文章
相關標籤/搜索