关于JDK 1.7 HashMap 源码分析

put操作

    public V put(K key, V value) {
    	// 如果表未进行初始化,
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        // key==null 在table[0]的位置
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);
        // 计算hash方法   h & (length-1) 的结合table的长度为2的幂次,比 % 操作更加高效
        int i = indexFor(hash, table.length);
        // hash直接定位到table的位置,然后在链表上遍历查找
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            // 如果存在存在equals 或者 相等的,用新值去覆盖,并且返回老的值
            // 这也是重写hash,必须重写equals
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                // HashMap里是空方法,可以通过继承覆盖附加一些操作。
                e.recordAccess(this);
                return oldValue;
            }
        }
	// 覆盖次数统计
        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

addEntry

    void addEntry(int hash, K key, V value, int bucketIndex) {
    	// 如果出发节点数量达到了扩容阈值threshold
    	// 并且当前散列表的位置是会发生hash碰撞的
        if ((size >= threshold) && (null != table[bucketIndex])) {
        	// 进行扩容,扩容的长度为原来的两倍
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            // 重新获取hash值
            bucketIndex = indexFor(hash, table.length);
        }
        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++;
    }

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

transfer

    void transfer(Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
        // 遍历散列表
        for (Entry<K,V> e : table) {
        // 遍历散列表中的链表
            while(null != e) {
                Entry<K,V> next = e.next;
                // 是否需要重新计算hash
                if (rehash) {
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                int i = indexFor(e.hash, newCapacity);
                // 采用头插法将元素加入新的列表
                // 这边容易在多线程的场景中出现链表闭环,导致CPU 100%
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            }
        }
    }

CPU100%问题简而言之,原因就是1.7链表新节点采用的是头插法,这样在线程一扩容迁移元素时,会将元素顺序改变,导致两个线程中出现元素的相互指向而形成循环链表,1.8采用了尾插法,从根源上杜绝了这种情况的发生

get操作

get方法的查找操作比较简单。

    public V get(Object key) {
        if (key == null)
        // 在table[0]的位置获取是否存在 key相等、或者相同的位置
            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;
        }
	//  重新计算hash
        int hash = (key == null) ? 0 : hash(key);
        // 通过hash值计算到散列表的位置对链表进行遍历
        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;
    }
posted @ 2021-09-03 15:47  月落随山隐  阅读(55)  评论(0)    收藏  举报