HashMap的工作原理

hashmap本質數據加鏈表。根據key取得hash值,然後計算出數組下標,如果多個key對應到同一個下標,就用鏈表串起來,新插入的在前面。

看3段重要代碼摘要:

1. 

    public HashMap(int initialCapacity, float loadFactor) {

        int capacity = 1;

        while (capacity < initialCapacity)

            capacity <<= 1;


        this.loadFactor = loadFactor;

        threshold = (int)(capacity * loadFactor);

        table = new Entry[capacity];

        init();

    }

3個關鍵參數:
capacity:容量,就是數組大小
loadFactor:比例,用於擴容
threshold:=capacity*loadFactor   最多容納的Entry數,如果當前元素個數多於這個就要擴容(capacity擴大爲原來的2倍)

2.

    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;

    }

 

根據key算hash值,再根據hash值取得數組下標,通過數組下標取出鏈表,遍歷鏈表用equals取出對應key的value。


3.

public V put(K key, V value) {

        if (key == null)

            return putForNullKey(value);

        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;

            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {

                V oldValue = e.value;

                e.value = value;

                e.recordAccess(this);

                return oldValue;

            }

        }


        modCount++;

        addEntry(hash, key, value, i);

        return null;

    }

從數組(通過hash值)取得鏈表頭,然後通過equals比較key,如果相同,就覆蓋老的值,並返回老的值。(該key在hashmap中已存在)

否則新增一個entry,返回null。新增的元素爲鏈表頭,以前相同數組位置的掛在後面。

另外:modCount是爲了避免讀取一批數據時,在循環讀取的過程中發生了修改,就拋異常

  if (modCount != expectedModCount)
                throw new ConcurrentModificationException();

下面看添加一個map元素

    void addEntry(int hash, K key, V value, int bucketIndex) {

        Entry<K,V> e = table[bucketIndex];

        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);

        if (size++ >= threshold)

            resize(2 * table.length);

    }

新增後,如果發現size大於threshold了,就resize到原來的2倍

    void resize(int newCapacity) {


        Entry[] newTable = new Entry[newCapacity];

        transfer(newTable);

        table = newTable;

        threshold = (int)(newCapacity * loadFactor);

    }

新建一個數組,並將原來數據轉移過去

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);

            }

        }

    }

將原來數組中的鏈表一個個取出,然後遍歷鏈表中每個元素,重新計算index並放入新數組。每個處理的也放鏈表頭。

在取出原來數組鏈表後,將原來數組置空(爲了大數據量複製時更快的被垃圾回收?)

還有兩點注意:

static class Entry<K,V> implements Map.Entry<K,V>是hashmap的靜態內部類,iterator之類的是內部類,因爲不是每個元素都需要持有map的this指針。

HashMap把  transient Entry[] table;等變量置爲transient,然後override了readObject和writeObject,自己實現序列化。


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