java核心之集合框架——HashMap源碼分析

——每天的寥寥幾筆,加持下去,將會是一份沉甸甸的積累。


源碼分析第一篇先講HashMap。

首先,明白HashMap分成Hash即hash表的數據結構,Map即Key-Value結構的值,然後就是將Key-value的值存到Hash表中。

OK,進入正題,

首先得有個Hash表的數據結構,在hashMap中是以鏈表數組實現的。

transient Entry<K,V>[] table;//Hash表,每個元素是Entry類型

static final int DEFAULT_INITIAL_CAPACITY = 16;//默認Hash表大小

public HashMap(int initialCapacity, float loadFactor) {
    this.loadFactor = loadFactor;//裝填因子,如果Hash表的使用率超過裝填因子時則需要擴容。
    threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
    //當前最大可使用容量,等於裝載因子乘以Hash表大小
    table = new Entry[capacity];//new出hash表,每個表元素又是一個鏈表結點

 static class Entry<K,V> implements Map.Entry<K,V> {
    final K key;
    V value;
    Entry<K,V> next;//可以看出這是一個鏈表結點,可以指向下一個元素
    int hash;
}

然後看來看看添加一個元素的put函數

public V put(K key, V value) {
	if (key == null)
	    return putForNullKey(value);//如果可key爲null,則特殊處理,實際上就是下面當索引i=0的情況。因爲null默認對應table[0]這個鏈表
	int hash = hash(key);//計算Hash值
	int i = indexFor(hash, table.length);//將計算出的Hash值壓縮到0至table.length-1的表索引範圍。
	//根據key計算出的索引,搜索對應的鏈表,如果找到相同key的則更新value,否則新建一個Entry鏈表結點。
	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;
	    }
	}
	//for循環中return沒返回說明沒有找到相同的key,下面新建結點
	modCount++;
	addEntry(hash, key, value, i);
	return null;
}
其中putForNullKey():

    private V putForNullKey(V value) {
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {//遍歷table[0]這個鏈表,找到相同key則更新value,否則新建一個結點
            if (e.key == null) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        modCount++;
        addEntry(0, null, value, 0);
        return null;
    }

addEntry()和createEntry():

void addEntry(int hash, K key, V value, int bucketIndex) {
    if ((size >= threshold) && (null != table[bucketIndex])) {
    	//表空間不足則擴容
        resize(2 * table.length);//兩個操作,擴大表大小爲之前的兩倍,同時將原數據轉移到新表,並將原引用指向新表。(resize函數和transfer函數就不貼了)
        hash = (null != key) ? hash(key) : 0;
        bucketIndex = indexFor(hash, table.length);
    }
    createEntry(hash, key, value, bucketIndex);
}
void createEntry(int hash, K key, V value, int bucketIndex) {
    Entry<K,V> e = table[bucketIndex];//記錄下索引爲bucketIndex的鏈表首節點
    table[bucketIndex] = new Entry<>(hash, key, value, e);//創建一個新節點來作爲首節點,並指向e這個原來的首節點
    size++;
}

hash函數(瞭解即可)和索引求解函數indexFor(很重要,需要理解):

final int hash(Object k) {//高級函數,大家瞭解即可
	int h = 0;
	if (useAltHashing) {
	    if (k instanceof String) {return sun.misc.Hashing.stringHash32((String) k);}
	    h = hashSeed;
	}
	h ^= k.hashCode();
	h ^= (h >>> 20) ^ (h >>> 12);
	return h ^ (h >>> 7) ^ (h >>> 4);
}
static int indexFor(int h, int length) {
     return h & (length-1);//Hash表的長度要求是2的N次方,這樣的話可以使得length-1的二進制碼全爲1,然後和h與操作後,結果將被限定在length-1的大小範圍內
}

說完put(放),然後講get(取)。

public V get(Object key) {
    if (key == null)
        return getForNullKey();//特殊處理,同上,遍歷table[0]鏈表
    Entry<K,V> entry = getEntry(key);
    return null == entry ? null : entry.getValue();
}
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) {//根據key計算table索引,然後遍歷該鏈表,找到則返回,否則最後返回null
        Object k;
        if (e.hash == hash &&
            ((k = e.key) == key || (key != null && key.equals(k))))//比較是否相等,相等條件爲Hash值相等並且key.equals(k)
            return e;
    }
    return null;
}
-----------------------------------------------------------------------重點理解------------------------------------------------------------------

接下來的重點是迭代器(很重要),遍歷整個map(key,value,entry)的利器,不過Hashmap還是做了進一步的封裝,將迭代器封裝了相應的View視圖中,三大視圖keySet<k>,Values<v>,EnterSet<k,v>。


【View視圖】——一個非常相當重要的概念,簡單來說,原映射表通過一個方法來實例化view視圖類並獲得該對象,該對象實現了某些集合接口,並能對原映射表進行某些操作。舉例來說,HashMap中有一個keySet方法,該方法返回一個內部類KeySet的實例對象,然後我們可以通過調用該對象的一些方法如get,set,remove等來對原映射表HashMap進行相關操作,而這些操作是能夠影響原映射表HashMap的實例域的。說白了,就是原映射表提供出來了一個方法,讓我們獲得一個對象來操作原映射表。


首先爲了獲得Iterator接口類,先實現一個抽象類

private abstract class HashIterator<E> implements Iterator<E> {
        Entry<K,V> next;        // current的下一個的結點對象,即調用nextEntry方法返回的對象
        int expectedModCount;   // 期望的修改次數
        int index;              // 當前索引,指的是當前遍歷的表是table[index]
        Entry<K,V> current;     // 當前的Entry結點。調用nextEntry方法後current=next

        HashIterator() {
            expectedModCount = modCount;
            if (size > 0) { // 指向第一個Entry next=t[0],準備開始遍歷以next爲首指針的鏈表
                Entry[] t = table;
                while (index < t.length && (next = t[index++]) == null)
                    ;
            }
        }
        public final boolean hasNext() {
            return next != null;
        }
        final Entry<K,V> nextEntry() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            Entry<K,V> e = next;//調用nextEntry則把next賦給current(e = next;current = e;),返回該對象
            if (e == null)
                throw new NoSuchElementException();
            //判斷是否要切到下一條鏈表
            if ((next = e.next) == null) {//e.next==null表明遍歷完一條鏈表,接着切換到下一條鏈表,即next = t[index],index++
                Entry[] t = table;
                while (index < t.length && (next = t[index++]) == null)
                    ;
            }
            current = e;
            return e;
        }
        public void remove() {
            if (current == null)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            Object k = current.key;
            current = null;
            HashMap.this.removeEntryForKey(k);
            //本內部類本來僅僅是進行遍歷、獲取,如果要進行刪除操作,必須改變外部類HashMap中的實例域,因此要調用外部類方法。
            expectedModCount = modCount;
        }
    }

上面這個是Iterator的抽象類,下面是三個實現了抽象類的子類並給與了三個獲得子類實例對象的方法。

   private final class ValueIterator extends HashIterator<V> {
        public V next() {
            return nextEntry().value;
        }
    }
    private final class KeyIterator extends HashIterator<K> {
        public K next() {
            return nextEntry().getKey();
        }
    }
    private final class EntryIterator extends HashIterator<Map.Entry<K,V>> {
        public Map.Entry<K,V> next() {
            return nextEntry();
        }
    }
    Iterator<K> newKeyIterator()   {//可以直接調用這三個方法來獲得你想要的Iterator對象,通過Iterator對象來遍歷原映射表
        return new KeyIterator();
    }
    Iterator<V> newValueIterator()   {
        return new ValueIterator();
    }
    Iterator<Map.Entry<K,V>> newEntryIterator()   {
        return new EntryIterator();
    }

但HashMap還是將上面的三個Iterator對象封裝到了一個實現了某個集合接口的類中(這些類就是視圖),這樣就能更全面的對原映射表進行相關操作,或者限制相關操作(比如不添加add方法,那麼獲得的視圖對象也將不能使用該方法來對原映射表操作,要想對原映射表進行該操作,必須通過其他方式直接獲得原映射表的引用)
public Set<K> keySet() {
        Set<K> ks = keySet;
        return (ks != null ? ks : (keySet = new KeySet()));
    }

    private final class KeySet extends AbstractSet<K> {
        public Iterator<K> iterator() {//將Iterator對象封裝進來,作爲Set集合的一個子方法提供給用戶使用,其實是等價於用newKeyIterator()
                                        方法獲得,不過後者是包訪問性,一般也無法訪問得到,所以通過KeySet來訪問Iterator對象
            return newKeyIterator();
        }
        public int size() {//提供了Set接口下其他一個實用的方法,使我們能夠更好的操作原映射表
            return size;
        }
        public boolean contains(Object o) {
            return containsKey(o);
        }
        public boolean remove(Object o) {
            return HashMap.this.removeEntryForKey(o) != null;
        }
        public void clear() {
            HashMap.this.clear();
        }
    }

 public Collection<V> values() {
        Collection<V> vs = values;
        return (vs != null ? vs : (values = new Values()));
    }

    private final class Values extends AbstractCollection<V> {
        public Iterator<V> iterator() {
            return newValueIterator();
        }
        public int size() {
            return size;
        }
        public boolean contains(Object o) {
            return containsValue(o);
        }
        public void clear() {
            HashMap.this.clear();
        }
    }

public Set<Map.Entry<K,V>> entrySet() {
        return entrySet0();
    }

    private Set<Map.Entry<K,V>> entrySet0() {
        Set<Map.Entry<K,V>> es = entrySet;
        return es != null ? es : (entrySet = new EntrySet());
    }

    private final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
        public Iterator<Map.Entry<K,V>> iterator() {
            return newEntryIterator();
        }
        public boolean contains(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry<K,V> e = (Map.Entry<K,V>) o;
            Entry<K,V> candidate = getEntry(e.getKey());
            return candidate != null && candidate.equals(e);
        }
        public boolean remove(Object o) {
            return removeMapping(o) != null;
        }
        public int size() {
            return size;
        }
        public void clear() {
            HashMap.this.clear();
        }
    }


貼了這麼多代碼,這裏在精煉一下,HashMap主要要理解如下幾點:

1.內部數據結構是數組鏈表

2.瞭解從key到index的過程和原理(Hash,indexFor)

3.知道如何計算索引,然後簡單的遍歷鏈表(get,put)

4.視圖的概念與使用(記住這東西就是封裝出來供大家對原映射表進行相關操作的,如獲得Iterator對象)

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