hashmap源碼解析(Java7、Java8)

集合工具類使用線程

1. hashmap源碼解析(Java7)

01.重要變量

static final int DEFAULT_INITIAL_CAPACITY = 16;
    //默認初始容量,必須是2的n次方
static final int MAXIMUM_CAPACITY = 1 << 30;
    //最大容量,當通過構造方法傳入的容量比它還大時,就用這個最大容量,必須是2的n次方
static final float DEFAULT_LOAD_FACTOR = 0.75f;
    //默認負載因子
transient Entry<K,V>[] table;
    //用來存儲鍵值對,可以看到鍵值對都是存儲在Entry中的
transient int size;
    //存放元素的個數
  
int threshold; 
    //臨界值   當實際大小超過臨界值時,會進行擴容threshold = 加載因子*容量
 
final float loadFactor; 
    //加載因子
  
transient int modCount;
    //被修改的次數

02.四個構造函數

    public HashMap(int initialCapacity, float loadFactor) {
	//1. 對傳入的 容量 和 加載因子進行判斷處理 
  	//2. 設置HashMap的容量極限 
  	//3. 計算出大於初始容量的最小 2的n次方作爲哈希表table的長度,然後用該長度創建Entry數組(table),這個是最核心的
        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)(capacity * loadFactor);
        table = new Entry[capacity];
        init();
    }

    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
        table = new Entry[DEFAULT_INITIAL_CAPACITY];
        init();
    }

    public HashMap(Map<? extends K, ? extends V> m) {
        this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
                      DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
        putAllForCreate(m);
    }

03.數據結構

Entry是一個鏈表結構,不僅包含key和value,還有可以指向下一個的next

圖片5.png

    static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        int hash;
        /**
         * Creates new entry.
         */
        Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }
    }

04.put方法

public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value);//儲存空鍵
        int hash = hash(key);//計算hash值
        int i = indexFor(hash, table.length);//計算存儲位置
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {//遍歷hashmap的內部數據,看是否與插入的數據是否已經存在相同鍵值,如果存在則直接替換
            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;
            }
        }
        //這個for循環,當發生併發,兩個線程衝突的時候,這個鏈表的結構會發生變化:可能兩個key互爲對方的next元素。此時通過next遍歷,會形成死循環。在jdb8中已經不存在了。最好的解決辦法是使用concurrenthashmap
        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

//當需要插入的key爲null時,調用putForNullKey方法處理:
//putForNullKey方法只從table[0]這個位置開始遍歷,因爲key爲null只放在table中的第一個位置,下標爲0,在遍歷中如果發現已經有key爲null了,則替換新value,返回舊value,結束;如果還沒有key爲null,調用addEntry方法增加一個Entry:
 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;
    }
   
//首先通過hash方法對hashcode進行處理:
//可以看到只是在key的hashcode值上做了一些處理,通過hash計算出來的值將會使用indexFor方法找到它應該所在的table下標:
final int hash(Object k) {
        int h = 0;
        h ^= k.hashCode();
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }
    
//這個方法其實相當於對table.length取模。
static int indexFor(int h, int length) {
        return h & (length-1);
    }

//添加元素,得到所在下標的第一個元素,也就是當前下標位置的鏈表的頭節點,然後插入數據就是新建一個鏈表,然後新鏈表指向原來的鏈表
void addEntry(int hash, K key, V value, int bucketIndex) {
        Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        if (size++ >= threshold)
            resize(2 * table.length);
    }
Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }
    
//只有當 size>=threshold並且 table中的那個槽中已經有Entry時,纔會發生resize。即有可能雖然size>=threshold,但是必須等到每個槽都至少有一個Entry時,纔會擴容。還有注意每次resize都會擴大一倍容量
 void resize(int newCapacity) { //如果已經是最大容量不進行擴容
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }

        Entry[] newTable = new Entry[newCapacity];
        transfer(newTable);
        table = newTable;
        threshold = (int)(newCapacity * loadFactor);
    }
/**
     * Transfers all entries from current table to newTable.
     */
    void transfer(Entry[] newTable) {
        Entry[] src = table;
        int newCapacity = newTable.length;
        for (int j = 0; j < src.length; j++) {
            Entry<K,V> e = src[j];
            if (e != null) {
                src[j] = null;
                do {
                    Entry<K,V> next = e.next;
                    int i = indexFor(e.hash, newCapacity);
                    e.next = newTable[i];
                    newTable[i] = e;
                    e = next;
                } while (e != null);
            }
        }
    }
    
//最後看createEntry,它先保存這個桶中的第一個Entry,創建新的Entry放入第一個位置,將原來的Entry接在後面。這裏採用的是頭插法插入元素。
void createEntry(int hash, K key, V value, int bucketIndex) {
        Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        size++;
    }

hash的原則

A、等冪性。不管執行多少次獲取Hash值的操作,只要對象不變,那麼Hash值是固定的。如果第一次取跟第N次取不一樣,那就用起來很麻煩.

B、對等性。若兩個對象equal方法返回爲true,則其hash值也應該是一樣的。舉例說明:若你將objA作爲key存入HashMap中,然後new了一個objB。在你看來objB和objA是一個東西(因爲他們equal),但是使用objB到hashMap中卻取不出來東西。

C、互異性。若兩個對象equal方法返回爲false,hash值有可能相同,但最好是不同的,這個不是必須的,只是這樣做會提高hash類操作的性能(碰撞機率低)。

解決hash碰撞的方法:開放地址法、鏈地址法

hashmap採用的就是鏈地址法,這種方法好處是無堆積現象,但是next指針會佔用額外空間

擴展:爲何數組的長度是 2 的 n 次方呢?

1.這個方法非常巧妙,它通過 h & (table.length -1) 來得到該對象的保存位,而HashMap 底層數組的長度總是 2 的 n 次方,2n-1 得到的二進制數的每個位上的值都爲 1,那麼與全部爲 1 的一個數進行與操作速度會大大提升。

2.當 length 總是 2 的 n 次方時,h& (length-1)運算等價於對 length 取模,也就是h%length,但是&比%具有更高的效率。

3.當數組長度爲 2 的 n 次冪的時候,不同的 key 算得的 index 相同的機率較小,那麼數據在數組上分佈就比較均勻,也就是說碰撞的機率小,相對的,查詢的時候就不用遍歷某個位置上的鏈表,這樣查詢效率也就較高了。

HashMap 的擴容機制:

當 HashMap 中的結點個數超過數組大小*loadFactor(加載因子)時,就會進行數組擴容,loadFactor 的默認值爲 0.75。也就是說,默認情況下,數組大小爲 16,那麼當 HashMap中結點個數超過 16*0.75=12 的時候,就把數組的大小擴展爲 2*16=32,即擴大一倍,然後重新計算每個元素在數組中的位置,並放進去,而這是一個非常消耗性能的操作。


05.get方法

//其實get方法和put方法如出一轍,怎麼放的怎麼拿
public V get(Object key) {
        if (key == null)
            return getForNullKey();
        Entry<K,V> entry = getEntry(key);
        return null == entry ? null : entry.getValue();
    }
//key爲null時,還是去table[0]去取:
private V getForNullKey() {
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null)
                return e.value;
        }
        return null;
    }
//否則調用getEntry方法:
final Entry<K,V> getEntry(Object key) {
        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;
    }
//這個方法也是通過key的hashcode計算出它應該所在的下標,再遍歷這個下標的Entry鏈,如果key的內存地址相等(即同一個引用)或者equals相等,則說明找到了


2. hashmap源碼解析(Java8)

    在jdk8中,仍然會根據key.hashCode()計算出hash值,再通過這個hash值去定位這個key,但是不同的是,當發生衝突時,會採用鏈表和紅黑樹兩種方法去處理,當結點個數較少時用鏈表(用Node存儲)個數較多時用紅黑樹(用TreeNode存儲),同時結點也不叫Entry了,而是分成了Node和TreeNode。再最壞的情況下,鏈表查找的時間複雜度爲O(n),而紅黑樹一直是O(logn),這樣會提高HashMap的效率。
jdk8中的HashMap中定義了一個變量TREEIFY_THRESHOLD,當節點個數>= TREEIFY_THRESHOLD - 1時,HashMap將採用紅黑樹存儲

基於Map接口實現、允許null鍵/值、非同步、不保證有序(比如插入的順序)、也不保證順序不隨時間變化

01.數據結構

數組 + 鏈表 + 紅黑樹

image.png

Node

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

        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

        public final K getKey()        { return key; }
        public final V getValue()      { return value; }
        public final String toString() { return key + "=" + value; }

        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }

        public final boolean equals(Object o) {
            if (o == this)
                return true;
            if (o instanceof Map.Entry) {
                Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                if (Objects.equals(key, e.getKey()) &&
                    Objects.equals(value, e.getValue()))
                    return true;
            }
            return false;
        }
    }

TreeNode

static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
        TreeNode<K,V> parent;  // red-black tree links
        TreeNode<K,V> left;
        TreeNode<K,V> right;
        TreeNode<K,V> prev;    // needed to unlink next upon deletion
        boolean red;
        TreeNode(int hash, K key, V val, Node<K,V> next) {
            super(hash, key, val, next);
        }

        /**
         * Returns root of tree containing this node.
         */
        final TreeNode<K,V> root() {
            for (TreeNode<K,V> r = this, p;;) {
                if ((p = r.parent) == null)
                    return r;
                r = p;
            }
        }
        
        。。。。。。
 }


02.常用屬性

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
    // 序列號
    private static final long serialVersionUID = 362498820763181265L;    
    // 默認的初始容量是16
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;   
    // 最大容量
    static final int MAXIMUM_CAPACITY = 1 << 30; 
    // 默認的填充因子
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    // 當桶(bucket)上的結點數大於這個值時會轉成紅黑樹
    static final int TREEIFY_THRESHOLD = 8; 
    // 當桶(bucket)上的結點數小於這個值時樹轉鏈表
    static final int UNTREEIFY_THRESHOLD = 6;
    // 桶中結構轉化爲紅黑樹對應的數組的最小大小,如果當前容量小於它,就不會將鏈表轉化爲紅黑樹,而是用resize()代替
    static final int MIN_TREEIFY_CAPACITY = 64;
    // 存儲元素的數組,總是2的冪
    transient Node<k,v>[] table; 
    // 存放具體元素的集
    transient Set<map.entry<k,v>> entrySet;
    // 存放元素的個數,注意這個不等於數組的長度。
    transient int size;
    // 每次擴容和更改map結構的計數器
    transient int modCount;   
    // 臨界值 當實際節點個數超過臨界值(容量*填充因子)時,會進行擴容
    int threshold;
    // 填充因子
    final float loadFactor;
}

03.類的構造函數

//制定初始容量和填充因子
public HashMap(int initialCapacity, float loadFactor) {
    // 初始容量不能小於0,否則報錯
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                            initialCapacity);
    // 初始容量不能大於最大值,否則爲最大值
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    // 填充因子不能小於或等於0,不能爲非數字
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                            loadFactor);
    // 初始化填充因子                                        
    this.loadFactor = loadFactor;
    // 通過tableSizeFor(cap)計算出不小於initialCapacity的最近的2的冪作爲初始容量,將其先保存在threshold裏,當put時判斷數組爲空會調用resize分配內存,並重新計算正確的threshold
    this.threshold = tableSizeFor(initialCapacity);   
} 

//指定初始容量
public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

//默認構造函數
public HashMap() {
    // 初始化填充因子
    this.loadFactor = DEFAULT_LOAD_FACTOR; 
}

//HashMap(Map<? extends K>)型構造函數
public HashMap(Map<? extends K, ? extends V> m) {
    // 初始化填充因子
    this.loadFactor = DEFAULT_LOAD_FACTOR;
    // 將m中的所有元素添加至HashMap中
    putMapEntries(m, false);
}

04.hash函數

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

image.png


05.resize函數

final Node<K,V>[] resize() {
    // 當前table保存
    Node<K,V>[] oldTab = table;
    // 保存table大小
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    // 保存當前閾值 
    int oldThr = threshold;
    int newCap, newThr = 0;
    // 之前table大小大於0,即已初始化
    if (oldCap > 0) {
        // 超過最大值就不再擴充了,只設置閾值
        if (oldCap >= MAXIMUM_CAPACITY) {
            // 閾值爲最大整形
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        // 容量翻倍
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
            oldCap >= DEFAULT_INITIAL_CAPACITY)
            // 閾值翻倍
            newThr = oldThr << 1; // double threshold
    }
    // 初始容量已存在threshold中
    else if (oldThr > 0)            // initial capacity was placed in threshold
        newCap = oldThr;
    // 使用缺省值(使用默認構造函數初始化)
    else {                                  // zero initial threshold signifies using defaults
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    // 計算新閾值
    if (newThr == 0) {
        float ft = (float)newCap * loadFactor;
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    threshold = newThr;
    @SuppressWarnings({"rawtypes","unchecked"})
    // 初始化table
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
    // 之前的table已經初始化過
    if (oldTab != null) {
        // 複製元素,重新進行hash
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
            if ((e = oldTab[j]) != null) {
                oldTab[j] = null;
                if (e.next == null)         //桶中只有一個結點
                    newTab[e.hash & (newCap - 1)] = e;
                else if (e instanceof TreeNode)         //紅黑樹
                    //根據(e.hash & oldCap)分爲兩個,如果哪個數目不大於UNTREEIFY_THRESHOLD,就轉爲鏈表
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                else { // preserve order
                    Node<K,V> loHead = null, loTail = null;
                    Node<K,V> hiHead = null, hiTail = null;
                    Node<K,V> next;
                    // 將同一桶中的元素根據(e.hash & oldCap)是否爲0進行分割成兩個不同的鏈表,完成rehash
                    do {
                        next = e.next;//保存下一個節點
                        if ((e.hash & oldCap) == 0) {       //保留在低部分即原索引
                            if (loTail == null)//第一個結點讓loTail和loHead都指向它
                                loHead = e;
                            else
                                loTail.next = e;
                            loTail = e;
                        }
                        else {                                      //hash到高部分即原索引+oldCap
                            if (hiTail == null)
                                hiHead = e;
                            else
                                hiTail.next = e;
                            hiTail = e;
                        }
                    } while ((e = next) != null);
                    if (loTail != null) {
                        loTail.next = null;
                        newTab[j] = loHead;
                    }
                    if (hiTail != null) {
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    return newTab;
}

06.put函數


put設計思路:

    對key的hashCode()做hash,然後再計算桶的index;

    如果沒碰撞直接放到桶bucket裏;

    如果碰撞了,以鏈表的形式存在buckets後;

    如果碰撞導致鏈表過長(大於等於TREEIFY_THRESHOLD),就把鏈表轉換成紅黑樹(若數組容量小於MIN_TREEIFY_CAPACITY,不進行轉換而是進行resize操作)

    如果節點已經存在就替換old value(保證key的唯一性)

    如果表中實際元素個數超過閾值(超過load factor*current capacity),就要resize

public V put(K key, V value) {
    // 對key的hashCode()做hash
    return putVal(hash(key), key, value, false, true);
}

/**
* 用於實現put()方法和其他相關的方法
*
* @param hash hash for key
* @param key the key
* @param value the value to put
* @param onlyIfAbsent if true, don't change existing value
* @param evict if false, the table is in creation mode.
* @return previous value, or null if none
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    // table未初始化或者長度爲0,進行擴容,n爲桶的個數
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    // (n - 1) & hash 確定元素存放在哪個桶中,桶爲空,新生成結點放入桶中(此時,這個結點是放在數組中)
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    // 桶中已經存在元素
    else {
        Node<K,V> e; K k;
        // 比較桶中第一個元素的hash值相等,key相等
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
                // 將第一個元素賦值給e,用e來記錄
                e = p;
        // hash值不相等或key不相等
        else if (p instanceof TreeNode)  //紅黑樹
            // 放入樹中
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        // 爲鏈表結點
        else {
            for (int binCount = 0; ; ++binCount) {
                // 到達鏈表的尾部
                if ((e = p.next) == null) {
                    // 在尾部插入新結點
                    p.next = newNode(hash, key, value, null);
                    // 結點數量達到閾值,調用treeifyBin()做進一步判斷是否轉爲紅黑樹
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    // 跳出循環
                    break;
                }
                // 判斷鏈表中結點的key值與插入的元素的key值是否相等
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    // 相等,跳出循環
                    break;
                // 用於遍歷桶中的鏈表,與前面的e = p.next組合,可以遍歷鏈表
                p = e;
            }
        }
        // 表示在桶中找到key值、hash值與插入元素相等的結點
        if (e != null) { 
            // 記錄e的value
            V oldValue = e.value;
            // onlyIfAbsent爲false或者舊值爲null
            if (!onlyIfAbsent || oldValue == null)
                //用新值替換舊值
                e.value = value;
            // 訪問後回調
            afterNodeAccess(e);
            // 返回舊值
            return oldValue;
        }
    }
    // 結構性修改
    ++modCount;
    // 實際大小大於閾值則擴容
    if (++size > threshold)
        resize();
    // 插入後回調
    afterNodeInsertion(evict);
    return null;
}

//將指定映射的所有映射關係複製到此映射中
public void putAll(Map<? extends K, ? extends V> m) {
    putMapEntries(m, true);
}

//將m的所有元素存入本HashMap實例中,evict爲false時表示構造初始HashMap
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
    int s = m.size();
    if (s > 0) {
        // table未初始化
        if (table == null) { // pre-size
            //計算初始容量
            float ft = ((float)s / loadFactor) + 1.0F;
            int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                    (int)ft : MAXIMUM_CAPACITY);
            if (t > threshold)
                threshold = tableSizeFor(t);//同樣先保存容量到threshold
        }
        // 已初始化,並且m元素個數大於閾值,進行擴容處理
        else if (s > threshold)
            resize();
        // 將m中的所有元素添加至HashMap中
        for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
            K key = e.getKey();
            V value = e.getValue();
            putVal(hash(key), key, value, false, evict);
        }
    }
}

//將鏈表轉換爲紅黑樹
final void treeifyBin(Node<K,V>[] tab, int hash) {
    int n, index; Node<K,V> e;
    //若數組容量小於MIN_TREEIFY_CAPACITY,不進行轉換而是進行resize操作
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
        resize();
    else if ((e = tab[index = (n - 1) & hash]) != null) {
        TreeNode<K,V> hd = null, tl = null;
        do {
            TreeNode<K,V> p = replacementTreeNode(e, null);//將Node轉換爲TreeNode
            if (tl == null)
                hd = p;
            else {
                p.prev = tl;
                tl.next = p;
            }
            tl = p;
        } while ((e = e.next) != null);
        if ((tab[index] = hd) != null)
            hd.treeify(tab);//重新排序形成紅黑樹
    }
}

final void treeify(Node<K,V>[] tab) {
            TreeNode<K,V> root = null;
            for (TreeNode<K,V> x = this, next; x != null; x = next) {
                next = (TreeNode<K,V>)x.next;
                x.left = x.right = null;
                if (root == null) {
                    x.parent = null;
                    x.red = false;
                    root = x;
                }
                else {
                    K k = x.key;
                    int h = x.hash;
                    Class<?> kc = null;
                    for (TreeNode<K,V> p = root;;) {
                        int dir, ph;
                        K pk = p.key;
                        if ((ph = p.hash) > h)
                            dir = -1;
                        else if (ph < h)
                            dir = 1;
                        else if ((kc == null &&
                                  (kc = comparableClassFor(k)) == null) ||
                                 (dir = compareComparables(kc, k, pk)) == 0)
                            dir = tieBreakOrder(k, pk);

                        TreeNode<K,V> xp = p;
                        if ((p = (dir <= 0) ? p.left : p.right) == null) {
                            x.parent = xp;
                            if (dir <= 0)
                                xp.left = x;
                            else
                                xp.right = x;
                            root = balanceInsertion(root, x);
                            break;
                        }
                    }
                }
            }
            moveRootToFront(tab, root);
        }

07.get函數

get函數大致思路如下: 

    1. bucket裏的第一個節點,直接命中; 

    2. 如果有衝突,則通過key.equals(k)去查找對應的entry,若爲樹,複雜度O(logn), 若爲鏈表,O(n)

public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}

final Node<K,V> getNode(int hash, Object key) {
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
    // table已經初始化,長度大於0,且根據hash尋找table中的項也不爲空
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) {
        // 比較桶中第一個節點
        if (first.hash == hash && // always check first node
            ((k = first.key) == key || (key != null && key.equals(k))))
            return first;
        // 桶中不止一個結點
        if ((e = first.next) != null) {
            // 爲紅黑樹結點
            if (first instanceof TreeNode)
                // 在紅黑樹中查找
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);
            // 否則,在鏈表中查找
            do {
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    return null;
}

public boolean containsKey(Object key) {
    return getNode(hash(key), key) != null;
}

public boolean containsValue(Object value) {
    Node<K,V>[] tab; V v;
    if ((tab = table) != null && size > 0) {
        //外層循環搜索數組
        for (int i = 0; i < tab.length; ++i) {
            //內層循環搜索鏈表
            for (Node<K,V> e = tab[i]; e != null; e = e.next) {
                if ((v = e.value) == value ||
                    (value != null && value.equals(v)))
                    return true;
            }
        }
    }
    return false;
}


Map集合常用方法

Map集合:該集合存儲鍵值對。一對一對往裏存。而且要保證鍵的唯一性。

1,添加。

put(K key, V value)

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

 

2,刪除。

clear()

remove(Object key)

 

3,判斷。

containsValue(Object value)

containsKey(Object key)

isEmpty()

 

4,獲取。

get(Object key)

size()

values()

entrySet()

keySet()

Map實現類

|--Hashtable:底層是哈希表數據結構,不可以存入nullnull值。該集合是線程同步的。jdk1.0.效率低。

|--HashMap:底層是哈希表數據結構,允許使用 null 值和 null 鍵,該集合是不同步的。將hashtable替代,jdk1.2.效率高。

|--TreeMap:底層是二叉樹數據結構。線程不同步。可以用於給map集合中的鍵進行排序。

 

和Set很像。

其實大家,Set底層就是使用了Map集合。

Hashmap和HashTable的異同:

01.兩者的默認容量與負載因子有變化

02.hashtable的 容量可以是任意值,而hashmap必須是2的次冪

03.hashtable中在put方法裏面不允許值與鍵爲空

04.計算索引的方式不同(indexof函數不同)

05.hashtable大部分方法都加上了sychronied關鍵字

06.hashtable每次擴容,容量爲原來的兩倍加2.

線程非安全:個人覺得HashMap在併發時可能出現的問題主要是兩方面,首先如果多個線程同時使用put方法添加元素,而且假設正好存在兩個put的key發生了碰撞(hash值一樣),那麼根據HashMap的實現,這兩個key會添加到數組的同一個位置,這樣最終就會發生其中一個線程的put的數據被覆蓋。第二就是如果多個線程同時檢測到元素個數超過數組大小*loadFactor,這樣就會發生多個線程同時對Node數組進行擴容,都在重新計算元素位置以及複製數據,但是最終只有一個線程擴容後的數組會賦給table,也就是說其他線程的都會丟失,並且各自線程put的數據也丟失。

map集合的兩種取出方式:

1,Set<k> keySet:將map中所有的鍵存入到Set集合。因爲set具備迭代器。

所有可以迭代方式取出所有的鍵,在根據get方法。獲取每一個鍵對應的值。

Map集合的取出原理:將map集合轉成set集合。在通過迭代器取出。

2,Set<Map.Entry<k,v>> entrySet:將map集合中的映射關係存入到了set集合中,

而這個關係的數據類型就是:Map.Entry

Entry其實就是Map中的一個static內部接口。

爲什麼要定義在內部呢?

因爲只有有了Map集合,有了鍵值對,纔會有鍵值的映射關係。

關係屬於Map集合中的一個內部事物。

而且該事物在直接訪問Map集合中的元素。

import java.util.*;
class MapDemo2 
{
	public static void main(String[] args) 
	{
		Map<String,String> map = new HashMap<String,String>();

		map.put("02","zhangsan2");
		map.put("03","zhangsan3");
		map.put("01","zhangsan1");
		map.put("04","zhangsan4");

		//將Map集合中的映射關係取出。存入到Set集合中。
		Set<Map.Entry<String,String>> entrySet = map.entrySet();

		Iterator<Map.Entry<String,String>> it = entrySet.iterator();

		while(it.hasNext())
		{
			Map.Entry<String,String> me = it.next();
			String key = me.getKey();
			String value = me.getValue();
			System.out.println(key+":"+value);
		}
		/*
		//先獲取map集合的所有鍵的Set集合,keySet();
		Set<String> keySet = map.keySet();
		//有了Set集合。就可以獲取其迭代器。
		Iterator<String> it = keySet.iterator();
		while(it.hasNext())
		{
			String key = it.next();
			//有了鍵可以通過map集合的get方法獲取其對應的值。
			String value  = map.get(key);
			System.out.println("key:"+key+",value:"+value);
		}
		*/
	}
}


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