hashMap源碼學習

簡介

hashmap是雙鏈表格式的存儲結構<K,V>存儲數據,沒有順序性,1.7基於hash表存儲。容許空值存在,鍵中有且只容許有一個,值中也容許有空值存在。初始容量大小爲16加載因子爲0.75。
線程不安全,在並操做時存在安全問題。

常見操做解讀

初始化new

初始化的時候無參狀況下,使用默認初始大小和加載因子進行初始化。html

//空參構造方法
public HashMap() {
    this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
//構造方法
//執行構造方法以前會加載類中的變量
//初始化一個Entry對象數組
//transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
public HashMap(int initialCapacity, float loadFactor) {
    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);
    this.loadFactor = loadFactor;
    threshold = initialCapacity;
    init();
}
添加操做
public V put(K key, V value) {
    //是否爲空Entry<K,V> table
    if (table == EMPTY_TABLE) {
        inflateTable(threshold);
    }
    //key爲null,hashMap專門空值設置一個存儲方法
    if (key == null)
        return putForNullKey(value);
    //計算hash
    int hash = hash(key);
    //計算index,根絕key的hash和table的長度
    int i = indexFor(hash, table.length);
    //是否有重複的key值,經過獲取現有的table[i]是否爲空來判斷
    //當不一樣key的hash值相同時爲hash衝突,hash衝突時,須要便利全部的Entry比較是否key的==和equal方法一致。
    //hash一致後Entry變爲爲鏈狀,同一個index下有多個Entry[]數據,並把添加放置到重複index下的Entry中的最後一個
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        //重複key值處理
        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++;
    //添加Entry
    addEntry(hash, key, value, i);
    return null;
}

addEntry方法java

void addEntry(int hash, K key, V value, int bucketIndex) {
     //當前entry的size大小大於threshold(size*0.75加載因子)而且當前表的index值已經存在
     //散列表散列值計算,一般是兩種方法:鏈表法和開放地址法
     //鏈表法就是將相同hash值的對象組織成一個鏈表放在hash值對應的槽位;開放地址法是經過一個探測算法,當某個槽位已經被佔據的狀況下繼續查找下一個可使用的槽位。
     //1.7hashMap使用的就是鏈表法
     if ((size >= threshold) && (null != table[bucketIndex])) {
         //當大小超過threshold而且出現hash衝突的時候會擴容在不大於最大值的狀況下是是舊錶的二倍
         resize(2 * table.length);
         //是否從新計算hash
         hash = (null != key) ? hash(key) : 0;
         //給衝突的hash集合新表的長度再次計算hash
         bucketIndex = indexFor(hash, table.length);
     }

     createEntry(hash, key, value, bucketIndex);
 }

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, initHashSeedAsNeeded(newCapacity));
    table = newTable;
    //從新計算 閥值
    threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}

threshold方法數組

void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    //遍歷舊錶對遷移新表
    for (Entry<K,V> e : table) {
        //在對Entry鏈表的複製過程當中可能會存在環的問題。多條線程併發操做,遍歷Enrt表的時候會致使環的存在,新鏈表插入相比較舊鏈表而言是倒敘,由於多線程快慢問題可能致使。
        //對有bucket鏈進行便利
        while(null != e) {
            //記錄舊錶的下一個entry值
            Entry<K,V> next = e.next;
            //是否從新計算hash
            if (rehash) {
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            //計算idex值
            int i = indexFor(e.hash, newCapacity);
            //新表的指針反轉舊錶指向
            e.next = newTable[i];
            //舊的enry攜帶新的指向賦值給新的槽位
            newTable[i] = e;
            //舊錶指針往下
            e = next;
        }
    }
}
獲取
public V get(Object key) {
    if (key == null)
        return getForNullKey();
    Entry<K,V> entry = getEntry(key);
    return null == entry ? null : entry.getValue();
}

getEntry方法安全

final Entry<K,V> getEntry(Object key) {
    if (size == 0) {
        return null;
    }
    int hash = (key == null) ? 0 : hash(key);
    //hash重複的狀況
    //比較key的equals和==的方法
    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;
}
刪除
public V remove(Object key) {
     Entry<K,V> e = removeEntryForKey(key);
     return (e == null ? null : e.value);
 }

removeEnryForKey方法多線程

final Entry<K,V> removeEntryForKey(Object key) {
    if (size == 0) {
        return null;
    }
    int hash = (key == null) ? 0 : hash(key);
    int i = indexFor(hash, table.length);
    //刪除
    Entry<K,V> prev = table[i];
    Entry<K,V> e = prev;
    while (e != null) {
        Entry<K,V> next = e.next;
        Object k;
        //hash相同時
        if (e.hash == hash &&(k = e.key) == key || (key != null && key.equals(k)))) {
            modCount++;
            size--;
            if (prev == e)
                table[i] = next;
            else
                prev.next = next;
            e.recordRemoval(this);
            return e;
        }
        prev = e;
        e = next;
    }
    return e;
}
相關文章
相關標籤/搜索