Android基礎-Android中的HashMap淺析

以下源碼基於Android中改造過後的HashMap

0.HashMap中的關鍵變量

  • MINIMUN_CAPACITY = 4 (最小容量)
  • MAXIMUN_CAPACITY = 1 << 30 ; (最大容量)
  • private static final Entry[] EMPTY_TABLE= new HashMapEntry[MINIMUM_CAPACITY >>> 1]; 這裏的這個就是hash表,是一種數組鏈表結構(和字典一樣),默認的容量大小爲4>>1,也就是2
  • DEFAULT_LOAD_FACTOR 負載因子,默認是0.75F
  • modCount 修改次數
  • threshold 閥值
  • 其他

1、HashMapEntry

看HashMapEntry的構造函數。

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

從中可以看出,這是一個單鏈表的數據結構,存有key、value、hash值以及下一個節點。

2、HashMap的的初始化

  • HashMap()
  • HashMap(int capacity)
  • HashMap(int capacity,float loadFactor)

第三個構造方法,直接調用的是第一個構造方法,並對loadFactor進行判斷(然而,這並沒有什麼吊用)
那麼。我們就來看HashMap的代碼吧。

    public HashMap() {
        table = (HashMapEntry<K, V>[]) EMPTY_TABLE;
        threshold = -1; // Forces first put invocation to replace EMPTY_TABLE
    }
  • 這裏的這個table是什麼呢?因爲這是個數組,而數組中每個元素都是單鏈表,所有,就構成table的樣式了。
  • threshold = -1,看註釋是說,首次調用替換掉EMPTY_TABLE.

3、添加數據

  • put(K key, V value)
  • putAll(Map

3.1、put(K key,V value)

    @Override public V put(K key, V value) {
        if (key == null) {
            return putValueForNullKey(value);
        }

        int hash = Collections.secondaryHash(key);
        HashMapEntry<K, V>[] tab = table;
        int index = hash & (tab.length - 1);
        for (HashMapEntry<K, V> e = tab[index]; e != null; e = e.next) {
            if (e.hash == hash && key.equals(e.key)) {
                preModify(e);
                V oldValue = e.value;
                e.value = value;
                return oldValue;
            }
        }

        // No entry for (non-null) key is present; create one
        modCount++;
        if (size++ > threshold) {
            tab = doubleCapacity();
            index = hash & (tab.length - 1);
        }
        addNewEntry(key, value, hash, index);
        return null;
    }
  • 若key爲null,則將值存放在entryForNullKey(當然會做一些處理)
  • 算出key對應的hash值
  • 根據hash值計算出index值取到對應的鏈表,如果存在hash值相等並且key值相等的的Entry,就修改value值,並返回舊的value值
  • 如果size++大於了閥值,對進行擴容並從新計算index值
  • 插入一個新的Entry,並返回null

下面來對上面的1,4,5進行說明

3.1.1 putValueForNullKey操作

相對應的源碼如下。

    private V putValueForNullKey(V value) {
        HashMapEntry<K, V> entry = entryForNullKey;
        if (entry == null) {
            addNewEntryForNullKey(value);
            size++;
            modCount++;
            return null;
        } else {
            preModify(entry);
            V oldValue = entry.value;
            entry.value = value;
            return oldValue;
        }
    }

這裏對應的操作也很簡單,如果當前entryForNullKey爲null的話,就添加一個,不爲null,就修改值

3.1.2 doubleCapacity() 擴容

擴容部分源代碼較長,咱們分段來看。

        HashMapEntry<K, V>[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            return oldTable;
        }
        int newCapacity = oldCapacity * 2;
        HashMapEntry<K, V>[] newTable = makeTable(newCapacity);
        if (size == 0) {
            return newTable;
        }
  • 如果到最大容量了,直接返回
  • 將容量設置爲原來的2倍
  • 製造一個table,(ps:製造的時候會將閥值設置爲3/4,(容量>>1) + (容量>>2),>>1 相當於/2,>>2 相當於/4)
  • 如果size(原先存儲的數目)爲0,直接返回
        for (int j = 0; j < oldCapacity; j++) {
            /*
             * Rehash the bucket using the minimum number of field writes.
             * This is the most subtle and delicate code in the class.
             */
            HashMapEntry<K, V> e = oldTable[j];
            if (e == null) {
                continue;
            }
            int highBit = e.hash & oldCapacity;
            HashMapEntry<K, V> broken = null;
            newTable[j | highBit] = e;
            for (HashMapEntry<K, V> n = e.next; n != null; e = n, n = n.next) {
                int nextHighBit = n.hash & oldCapacity;
                if (nextHighBit != highBit) {
                    if (broken == null)
                        newTable[j | nextHighBit] = n;
                    else
                        broken.next = n;
                    broken = e;
                    highBit = nextHighBit;
                }
            }
            if (broken != null)
                broken.next = null;
        }
        return newTable;
  • 上面代碼的就是將原table中每一處對應的鏈表取出來,並且從新散列
3.1 addNewEntry添加新的Entry
table[index] = new HashMapEntry<K, V>(key, value, hash, table[index]);

其中table[index]就是一個單鏈表,這裏就是生成一個HashMapEntry並將其插入到index處的,當然,我們還需要看一下生成的構造方法。

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

結合邏輯可以知道,我們可以看的出,每次是在鏈表的頭部進行數據插入的。

3.2、putAll

    @Override public void putAll(Map<? extends K, ? extends V> map) {
        ensureCapacity(map.size());
        super.putAll(map);
    }
  • ensureCapacity,確保容量(這裏就是進行容量檢查,不夠擴容,具體的細節就不說了)
  • 調用父類去put數據

在這裏我們就需要明白父類的實現了。

    public void putAll(Map<? extends K, ? extends V> map) {
        for (Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
            put(entry.getKey(), entry.getValue());
        }
    }

從中可以看出,如果穿進來的是另一個HashMap的話,就會將這個HashMap中的Entry挨個加入到運來的HashMap中。

4、get取值

取值的過程因爲在散列與散列碼中,有提到過,所以這裏就不多說了。

5、總結

HashMap查詢快速的原因就在於hashtable的思想。就像字典一樣。

本博文中只是簡單的介紹了下HashMap。當然HaspMap中還有許多值得我們去思考的問題,諸如:

  • 負載因子 爲什麼是0.75?
  • 初始容量爲什麼在Java8中改成2了
  • 散列時index的算法
  • 爲什麼從新散列是那樣求index的
  • 其他

等等,這些問題,每一個都值得我們去好好地研究。

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