HashMap詳解( JDK8 之前與之後對比)

HashMap簡介

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

此外,HashMap中的映射不是有序的

HashMap 的實例有兩個參數影響其性能:“初始容量” 和 “加載因子”。容量 是哈希表中桶的數量,初始容量 只是哈希表在創建時的容量。加載因子 是哈希表在其容量自動增加之前可以達到多滿的一種尺度。當哈希表中的條目數超出了加載因子與當前容量的乘積時,則要對該哈希表進行 rehash 操作(即重建內部數據結構),從而哈希表將具有大約兩倍的桶數。
通常,默認加載因子是 0.75, 這是在時間和空間成本上尋求一種折衷。加載因子過高雖然減少了空間開銷,但同時也增加了查詢成本(在大多數 HashMap 類的操作中,包括 get 和 put 操作,都反映了這一點)。在設置初始容量時應該考慮到映射中所需的條目數及其加載因子,以便最大限度地減少 rehash 操作次數。如果初始容量大於最大條目數除以加載因子,則不會發生 rehash 操作。

關於hashcodeHashMap的hash算法參考博客:

https://blog.csdn.net/q5706503/article/details/85114159

HashMap的構造函數

// 默認構造函數。
HashMap()

// 指定“容量大小”的構造函數
HashMap(int capacity)

// 指定“容量大小”和“加載因子”的構造函數
HashMap(int capacity, float loadFactor)

// 包含“子Map”的構造函數
HashMap(Map<? extends K, ? extends V> map)

HashMap的API ( JDK8 )

修飾符和類型 方法和描述
void clear()

從此映射中刪除所有映射。

Object clone()

返回此HashMap實例的淺克隆:未克隆鍵和值本身。

V compute(K key, BiFunction<? super K,? super V,? extends V> remappingFunction)

嘗試計算指定鍵及其當前映射值的映射(或者null如果沒有當前映射)。

V computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction)

如果指定的鍵尚未與值關聯(或映射到null),則嘗試使用給定的映射函數計算其值,並將其輸入此映射,除非null

V computeIfPresent(K key, BiFunction<? super K,? super V,? extends V> remappingFunction)

如果指定鍵的值存在且爲非null,則嘗試在給定鍵及其當前映射值的情況下計算新映射。

boolean containsKey(Object key)

如果此映射包含指定鍵的映射,則返回true。

boolean containsValue(Object value)

如果此映射將一個或多個鍵映射到指定值,則返回true。

Set<Map.Entry<K,V>> entrySet()

返回Set此映射中包含的映射的視圖。

void forEach(BiConsumer<? super K,? super V> action)

對此映射中的每個條目執行給定操作,直到處理完所有條目或操作引發異常。

V get(Object key)

返回指定鍵映射到的值,或者null此映射是否不包含鍵的映射。

V getOrDefault(Object key, V defaultValue)

返回指定鍵映射到的值,或者 defaultValue此映射是否不包含鍵的映射。

boolean isEmpty()

如果此映射不包含鍵 - 值映射,則返回true。

Set<K> keySet()

返回Set此映射中包含的鍵的視圖。

V merge(K key, V value, BiFunction<? super V,? super V,? extends V> remappingFunction)

如果指定的鍵尚未與值關聯或與null關聯,則將其與給定的非空值關聯。

V put(K key, V value)

將指定的值與此映射中的指定鍵相關聯。

void putAll(Map<? extends K,? extends V> m)

將指定映射中的所有映射覆制到此映射。

V putIfAbsent(K key, V value)

如果指定的鍵尚未與值關聯(或映射到null),則將其與給定值關聯並返回 null,否則返回當前值。

V remove(Object key)

從此映射中刪除指定鍵的映射(如果存在)。

boolean remove(Object key, Object value)

僅當指定鍵當前映射到指定值時才刪除該條目的條目。

V replace(K key, V value)

僅當指定鍵當前映射到某個值時,才替換該條目的條目。

boolean replace(K key, V oldValue, V newValue)

僅當前映射到指定值時,才替換指定鍵的條目。

void replaceAll(BiFunction<? super K,? super V,? extends V> function)

將每個條目的值替換爲在該條目上調用給定函數的結果,直到所有條目都已處理或函數拋出異常。

int size()

返回此映射中鍵 - 值映射的數量。

Collection<V> values()

返回Collection此映射中包含的值的視圖。

關於entry, entrySet()的說明和map的遍歷可以參考我的另一篇博客:

https://blog.csdn.net/q5706503/article/details/85122343

JDK8中Entry的名字變成了Node( Node類是HashMap的一個靜態內部類,實現了 Map.Entry<K,V>接口),原因是和紅黑樹的實現TreeNode相關聯。

 HashMap的繼承關係

java.util.map類圖

說明:

HashMap繼承於AbstractMap類,實現了Map接口。

Map是"key-value鍵值對"接口,AbstractMap實現了"鍵值對"的通用函數接口。 

影響HashMap性能的有兩個參數初始容量(initialCapacity) 和加載因子(loadFactor)。容量 是哈希表中桶的數量,初始容量只是哈希表在創建時的容量。加載因子 是哈希表在其容量自動增加之前可以達到多滿的一種尺度。當哈希表中的條目數超出了加載因子與當前容量的乘積時,則要對該哈希表進行 rehash 操作(即重建內部數據結構),從而哈希表將具有大約兩倍的桶數。

關於版本說明:

HashMap就是一個散列表, 在JAVA8之前它是通過“拉鍊法”解決哈希衝突的

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

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

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

JDK8中,當同一個hash值的節點數不小於8時,將不再以單鏈表的形式存儲了,會被調整成一顆紅黑樹(上圖中null節點沒畫)。這就是JDK7與JDK8中HashMap實現的最大區別。

JDK8中Entry的名字變成了Node( Node類是HashMap的一個靜態內部類,實現了 Map.Entry<K,V>接口),原因是和紅黑樹的實現TreeNode相關聯。

HashMap使用示例:

以下示例基於jdk1.7, 取自文尾引用的博客

import java.util.Map;
import java.util.Random;
import java.util.Iterator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map.Entry;
import java.util.Collection;

/*
 * @desc HashMap測試程序
 *        
 * @author skywang
 */
public class HashMapTest {

    public static void main(String[] args) {
        testHashMapAPIs();
    }
    
    private static void testHashMapAPIs() {
        // 初始化隨機種子
        Random r = new Random();
        // 新建HashMap
        HashMap map = new HashMap();
        // 添加操作
        map.put("one", r.nextInt(10));
        map.put("two", r.nextInt(10));
        map.put("three", r.nextInt(10));

        // 打印出map
        System.out.println("map:"+map );

        // 通過Iterator遍歷key-value
        Iterator iter = map.entrySet().iterator();
        while(iter.hasNext()) {
            Map.Entry entry = (Map.Entry)iter.next();
            System.out.println("next : "+ entry.getKey() +" - "+entry.getValue());
        }

        // HashMap的鍵值對個數        
        System.out.println("size:"+map.size());

        // containsKey(Object key) :是否包含鍵key
        System.out.println("contains key two : "+map.containsKey("two"));
        System.out.println("contains key five : "+map.containsKey("five"));

        // containsValue(Object value) :是否包含值value
        System.out.println("contains value 0 : "+map.containsValue(new Integer(0)));

        // remove(Object key) : 刪除鍵key對應的鍵值對
        map.remove("three");

        System.out.println("map:"+map );

        // clear() : 清空HashMap
        map.clear();

        // isEmpty() : HashMap是否爲空
        System.out.println((map.isEmpty()?"map is empty":"map is not empty") );
    }
}

 (某一次)運行結果: 

map:{two=7, one=9, three=6}
next : two - 7
next : one - 9
next : three - 6
size:3
contains key two : true
contains key five : false
contains value 0 : false
map:{two=7, one=9}
map is empty

我們來看下JDK8中HashMap的源碼實現

Entry的名字變成了Node,原因是和紅黑樹的實現TreeNode相關聯。

transient Node<K,V>[] table;

當衝突節點數不小於8-1時,轉換成紅黑樹。

static final int TREEIFY_THRESHOLD = 8;

以put方法在JDK8中有了很大的改變

public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
 }
 
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
    Node<K,V>[] tab;
    Node<K,V> p; 
    int n, i;
    //如果當前map中無數據,執行resize方法。並且返回n
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
     //如果要插入的鍵值對要存放的這個位置剛好沒有元素,那麼把他封裝成Node對象,放在這個位置上就完事了
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
    //否則的話,說明這上面有元素
        else {
            Node<K,V> e; K k;
        //如果這個元素的key與要插入的一樣,那麼就替換一下,也完事。
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
        //1.如果當前節點是TreeNode類型的數據,執行putTreeVal方法
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
        //還是遍歷這條鏈子上的數據,跟jdk7沒什麼區別
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
            //2.完成了操作後多做了一件事情,判斷,並且可能執行treeifyBin方法
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null) //true || --
                    e.value = value;
           //3.
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
    //判斷閾值,決定是否擴容
        if (++size > threshold)
            resize();
        //4.
        afterNodeInsertion(evict);
        return null;
    }

其中treeifyBin()就是將鏈表轉換成紅黑樹。

之前的indefFor()方法消失 了,直接用(tab.length-1)&hash,所以看到這個,代表的就是數組的下角標。

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

覺得不夠詳細可以看看以下大佬的:

http://www.importnew.com/20386.html

https://www.cnblogs.com/yangming1996/p/7997468.html

下面的有JDK8的部分HashMap源碼分析

https://blog.csdn.net/fighterandknight/article/details/61624150

https://blog.csdn.net/zxt0601/article/details/77413921

 

參考以下博客:

http://www.cnblogs.com/skywang12345/p/3310835.html

http://www.importnew.com/23164.html

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