淺談HashMap

HashMap結(jié)構(gòu)圖
image.png
HashMap主要方法
  • final int hash(Object k)
  • static int indexFor(int h, int length)
  • void resize(int newCapacity)
  • public V put(K key, V value)
  • public V get(Object key)
  • public V remove(Object key)
HashMap方法解讀

final int hash(Object k)源碼:

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

這段代碼的含義我確實搞不清楚,簡單理解的話就是做一次hash算法

static int indexFor(int h, int length)源碼:

//做一次&運算,定位到在數(shù)組的位置
 static int indexFor(int h, int length) {
        return h & (length-1);
    }

public V get(Object key)的源碼:

public V get(Object key) {
        //如果這個key為null就會放入數(shù)組的第0個位置
        if (key == null)
            return getForNullKey();
        //獲取key為非0的元素
        Entry<K,V> entry = getEntry(key);
        return null == entry ? null : entry.getValue();
    }
//獲取key為0的元素,因為數(shù)組第0個位置是一個鏈表組成,遍歷這個鏈表就可以找到對應(yīng)的值
 private V getForNullKey() {
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null)
                return e.value;
        }
        return null;
    }
//當(dāng)key不為0的時候
final Entry<K,V> getEntry(Object key) {
        //這就是之前的hash算法,算出對應(yīng)的hash值
        int hash = (key == null) ? 0 : hash(key);
        //indexFor這也就是前面講的找到數(shù)組對應(yīng)的位置
        for (Entry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;
            //判斷是否是這個key對應(yīng)的數(shù)據(jù)條件是:hash值相等&對應(yīng)的key值相等,或者key equals
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
    }

總結(jié)起來就是:
如果這個key為null就走null的獲取方式:其實就是對數(shù)據(jù)第0個位置的鏈表進行遍歷。
如果這個key不為null就走不為null的獲取方式:首先,計算出hash值,然后,定位到在數(shù)組的位置,最后遍歷這個鏈表,條件就是hash值相等并且key相等或者equals

public V remove(Object key) 源碼為:

public V remove(Object key) {
        Entry<K,V> e = removeEntryForKey(key);
        return (e == null ? null : e.value);
    }
//真正的刪除元素
final Entry<K,V> removeEntryForKey(Object key) {
        //算hash值
        int hash = (key == null) ? 0 : hash(key);
       //定位位置
        int i = indexFor(hash, table.length);
        // 將table[i]賦值給prev
        Entry<K,V> prev = table[i];
        Entry<K,V> e = prev;
        //開始遍歷刪除
        while (e != null) {
            Entry<K,V> next = e.next;
            Object k;
            //判斷相等條件
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k)))) {
                modCount++;
                size--;
              //如果刪除的元素為第一個,那么下一個元素作為第一個
                if (prev == e)
                    table[i] = next;
                else
                     //刪除當(dāng)前元素,把當(dāng)前元素的前一個節(jié)點指向當(dāng)前元素的后一個節(jié)點
                    prev.next = next;
                e.recordRemoval(this);
                return e;
            }
            //依次循環(huán)查找
            prev = e;
            e = next;
        }
        return e;
    }

總結(jié)起來就是:
第一步:算出hash;第二步:定位位置;第三步:遍歷定位位置的鏈表,找到對應(yīng)元素刪除。

public V put(K key, V value)源碼為:

 public V put(K key, V value) {
        // key為null時的put
        if (key == null)
            return putForNullKey(value);
        //hash
        int hash = hash(key);
        //index
        int i = indexFor(hash, table.length);
        //如果是index位置對應(yīng)的鏈表里面有想要put的元素,用新的元素覆蓋老的元素,**說明hashmap如果key相同的話,value會相互覆蓋**
        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++;
        //如果這個key沒有的話,新增一個entry
        addEntry(hash, key, value, i);
        return null;
    }
//新增操作
void addEntry(int hash, K key, V value, int bucketIndex) {
        //計算需要不需要擴容
        if ((size >= threshold) && (null != table[bucketIndex])) {
             //擴容操作
             resize(2 * table.length);
            //重新hash
            hash = (null != key) ? hash(key) : 0;
            //重新index
            bucketIndex = indexFor(hash, table.length);
        }
        //新增entry
        createEntry(hash, key, value, bucketIndex);
    }
//新增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++;
    }
//這個一個頭插法,新來的元素插入頭部
 Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }
//擴容操作
void resize(int newCapacity) {
        //擴容操作之前的一些判斷處理,還有一些我也不足道什么意思,不過不是很重要,我們主要看一下transfer
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }
        Entry[] newTable = new Entry[newCapacity];
        boolean oldAltHashing = useAltHashing;
        useAltHashing |= sun.misc.VM.isBooted() &&
                (newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
        boolean rehash = oldAltHashing ^ useAltHashing;
        transfer(newTable, rehash);
        table = newTable;
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }
//把原來的元素復(fù)制到新的數(shù)組里
void transfer(Entry[] newTable, boolean rehash) {
        //創(chuàng)建一個新的數(shù)組
        int newCapacity = newTable.length;
      //循環(huán)老的數(shù)組
        for (Entry<K,V> e : table) {
            //遍歷每一個數(shù)組對應(yīng)的鏈表
            while(null != e) {
                Entry<K,V> next = e.next;
                //hash
                if (rehash) {
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                // index
                int i = indexFor(e.hash, newCapacity);
                //這一點比較bug,不是很好理解,我會貼一篇比較清楚的文章
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            }
        }
    }

put操作總結(jié)起來就是:
第一:hash,index之后找原來的鏈表查看是否存在,存在的話覆蓋掉
第二:不存在就新建一個entry,新建的前提需要考慮容量是否超過閾值,沒有超過直接新建
第三:超過閾值,就需要擴容2*size,還需要把原來的table里面的元素復(fù)制到新的table里,這里需要注意如果是多線程環(huán)境操作的話可能形成環(huán)狀結(jié)構(gòu)導(dǎo)致CPU飆升
第四:新增entry是才有頭插法,好處是不用遍歷。
這里我必須貼出一遍寫的很精彩的文章,還有他對resize的詳細介紹:
hashmap擴容機制

HashMap遍歷方式

目前我常用的就是這個了,還有其他的歡迎補充。

for (Map.Entry<String, String> entry : map.entrySet()) {
            System.out.println(entry.getKey() + ":" + entry.getValue());
        }
HashMap其他特性介紹
  • hashmap的組成結(jié)構(gòu)為數(shù)組+鏈表。
  • hashmap是非線程安全的,在多線程的環(huán)境下是會出問題的。
  • hashmap 的key可以為空,value也可以為空。
  • hashmap的默認數(shù)組長度為16,默認加載因子為0.75f,當(dāng)閾值的時候是會擴容的,在擴容的時候需要注意在多線程的環(huán)境里有可能出現(xiàn)環(huán)形。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容