Java集合--07HashMap

HsahMap基礎

Java8 對 HashMap 進行了一些修改,最大的不同就是利用了紅黑樹,所以其由 數組+鏈表+紅黑樹 組成。

根據 Java7 HashMap 的介紹,我們知道,查找的時候,根據 hash 值我們能夠快速定位到數組的具體下標,但是之後的話,需要順着鏈表一個個比較下去才能找到我們需要的,時間複雜度取決於鏈表的長度,爲 O(n)。

爲了降低這部分的開銷,在 Java8 中,當鏈表中的元素超過了 8 個以後,會將鏈表轉換爲紅黑樹,在這些位置進行查找的時候可以降低時間複雜度爲 O(logN)。

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable { }

(01)是一個散列表,它存儲的內容是鍵值對(key-value)映射。
(02)繼承於AbstractMap,實現了Map、Cloneable、java.io.Serializable接口。
(03)實現不是同步的,這意味着它不是線程安全的。它的key、value都可以爲null。此外,HashMap中的映射不是有序的

    HashMap 的實例有兩個參數影響其性能:“初始容量” 和 “加載因子”。

    容量是哈希表中桶的數量,初始容量只是哈希表在創建時的容量,默認初始容量是16,必須是2的冪。加載因子是哈希表判斷是否需要自增容量的因子,也是哈希表達到多大容量的衡量尺度。當哈希表中的當前數量超出了加載因子與初始容量的乘積時,則要對該哈希表進行 rehash 操作(即重建內部數據結構),從而哈希表將具有大約兩倍的桶數。
    通常,默認加載因子是 0.75, 這是在時間和空間成本上尋求一種折衷。加載因子過高雖然減少了空間開銷,但同時也增加了查詢成本(在大多數 HashMap 類的操作中,包括 get 和 put 操作,都反映了這一點)。

備註:默認的初始容量是16,必須是2的冪的原因。容量主要是用於計算key所在的索引,下面是源碼中返回索引值的方法。

// 返回索引值
    // h & (length-1)保證返回值的小於length
    static int indexFor(int h, int length) {
        return h & (length-1);
    }

    h是通過K的hashCode最終計算出來的哈希值,並不是hashCode本身,而是在hashCode之上又經過一層運算的hash值,length是目前容量。當容量一定是2^n時,h & (length - 1) == h % length,它倆是等價不等效的,位運算效率非常高,實際開發中,很多的數值運算以及邏輯判斷都可以轉換成位運算。這個等式實際上可以推理出來,2^n轉換成二進制就是1+n個0,減1之後就是0+n個1,如16 -> 10000,15 -> 01111,那根據&位運算的規則,都爲1(真)時,才爲1,那0≤運算後的結果≤15,假設h <= 15,那麼運算後的結果就是h本身,h >15,運算後的結果就是最後三位二進制做&運算後的值,最終,就是%運算後的餘數。總之就是當是2的冪的情況下,位運算效率高

HashMap數據結構

執行put方法後,最終HashMap的存儲結構會有三種情況:

(01)key的索引位置沒有元素,直接進行插入。

(02)key的索引位置存在元素,且hashCode相同,則替換原來的值。

(03)key的索引位置存在元素,但hashCode不相同,則在索引處建立單鏈表,進行插入。

HashMap就是一個散列表,它是通過“拉鍊法解決哈希衝突每一個Entry本質上是一個單向鏈表

    // 將“key-value”添加到HashMap中
    public V put(K key, V value) {
        // 若“key爲null”,則將該鍵值對添加到table[0]中。
        if (key == null)
            return putForNullKey(value);
        // 若“key不爲null”,則計算該key的哈希值,然後將其添加到該哈希值對應的鏈表中。
        int hash = hash(key.hashCode());
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            // 若“該key”對應的鍵值對已經存在,則用新的value取代舊的value。然後退出!
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
  • HashMap通過鍵的hashCode來快速的存取元素。
  • HashMap中的key-value都是存儲在Entry數組中的。

 

備註:createEntry和addEntry的區別是:
    (01) addEntry()一般用在 新增Entry可能導致“HashMap的實際容量”超過“閾值”的情況下。例如,我們新建一個HashMap,然後不斷通過put()向HashMap中添加元素;put()是通過addEntry()新增Entry的。在這種情況下,我們不知道何時“HashMap的實際容量”會超過“閾值”;因此,需要調用addEntry()
   (02) createEntry() 一般用在 新增Entry不會導致“HashMap的實際容量”超過“閾值”的情況下。例如,我們調用HashMap“帶有Map”的構造函數,它繪將Map的全部元素添加到HashMap中;但在添加之前,我們已經計算好“HashMap的容量和閾值”。也就是,可以確定“即使將Map中的全部元素添加到HashMap中,都不會超過HashMap的閾值”。 此時,調用createEntry()即可。

HashMap遍歷方式

(01)根據entrySet()獲取HashMap的“鍵值對”的Set集合。通過Iterator迭代器遍歷“第一步”得到的集合。

// 假設map是HashMap對象
// map中的key是String類型,value是Integer類型
Integer integ = null;
Iterator iter = map.entrySet().iterator();
while(iter.hasNext()) {
    Map.Entry entry = (Map.Entry)iter.next();
    // 獲取key
    key = (String)entry.getKey();
        // 獲取value
    integ = (Integer)entry.getValue();
}

(02)根據keySet()獲取HashMap的“鍵”的Set集合。通過Iterator迭代器遍歷“第一步”得到的集合。

// 假設map是HashMap對象
// map中的key是String類型,value是Integer類型
String key = null;
Integer integ = null;
Iterator iter = map.keySet().iterator();
while (iter.hasNext()) {
        // 獲取key
    key = (String)iter.next();
        // 根據key,獲取value
    integ = (Integer)map.get(key);
}

(03)根據value()獲取HashMap的“值”的集合。通過Iterator迭代器遍歷“第一步”得到的集合。

// 假設map是HashMap對象
// map中的key是String類型,value是Integer類型
Integer value = null;
Collection c = map.values();
Iterator iter= c.iterator();
while (iter.hasNext()) {
    value = (Integer)iter.next();
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章