当前位置:   article > 正文

【Java 面试合集】HashMap 在1.8做了哪些优化_hashmap1.8

hashmap1.8

HashMap 在1.8做了哪些优化

1. 概述

嗨,大家好,我又来了。今天分享的面试合集是HashMap 在1.8做了哪些优化。HashMap是面试高频题,我也会从源码的角度逐一解析。有什么不对的,希望大家及时指正。

2. 数据结构

2.1 jdk1.7版本体现

使用数据结构是数组 + 链表

第一步:执行put方法

// --------------- 此方法为 put方法 ------------------
// 通过hash值以及数组的长度计算出该值,在数组中对应的位置
int i = indexFor(hash, table.length);
// 如果是第一个添加的话 table[i] 一定是null。 所以这个for不会进去
for (Entry<K,V> e = table[i]; e != null; e = e.next) 
// 开始添加节点
addEntry(hash, key, value, i);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

第二步:执行addEntry方法

// --------------- 此方法为 addEntry方法 ------------------
// 如果是第一次执行的话 下面这个if一定不会满足
if ((size >= threshold) && (null != table[bucketIndex]))

// 创建节点
createEntry(hash, key, value, bucketIndex);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

第三步:执行createEntry方法

// --------------- 此方法为 createEntry方法 ------------------
// 如果第一个执行的话,得到的e 一定是null。
Entry<K,V> e = table[bucketIndex];
// 所以在对应数组table的位置【bucketIndex】 插入一个节点
// 如果下次执行的话,会将该节点插入到链表头部(因为此处代码将链表作为将添加节点的next节点)
table[bucketIndex] = new Entry<>(hash, key, value, e);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

第四步:查看class【Entry】结构

// 可以发现Entry 本身就是一个链表结构
static class Entry<K,V> implements Map.Entry<K,V> {
    Entry(int h, K k, V v, Entry<K,V> n) {
        value = v;
        next = n;
        key = k;
        hash = h;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

2.2 jdk1.8 版本体现

使用数据结构是数组 + 链表 + 红黑树

第一步:putVal 实现逻辑

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
// 如果说数组下标位置为null的话。说明该索引位置没有链表
if ((p = tab[i = (n - 1) & hash]) == null)
	// 创建一个新的链表,直接赋值到索引位置
	tab[i] = newNode(hash, key, value, null);

	// 执行到此处的时候,挨个遍历节点
   for (int binCount = 0; ; ++binCount) {
   		// 直到遍历到最后一个节点
       if ((e = p.next) == null) {
       		// 直接插入到尾部
           p.next = newNode(hash, key, value, null);
           // 如果count >= 7的话 将整个链表 转换为二叉树
           if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
               treeifyBin(tab, hash);
           break;
       }
       if (e.hash == hash &&
           ((k = e.key) == key || (key != null && key.equals(k))))
           break;
       p = e;
   }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

第二步:核心点

// 如果count >= 7的话 将整个链表 转换为二叉树
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
    treeifyBin(tab, hash);
  • 1
  • 2
  • 3

上述的条件其实是链表转换二叉树的核心点。

3. 插入方式

3.1 jdk1.7版本体现

在这里插入图片描述

链表插入方式链表 头插法

第一步:方法【createEntry】

详细的执行流程可以参照2.1 的过程。

 void createEntry(int hash, K key, V value, int bucketIndex) {
 	// 此时将数组的中指定下标的链表取出。所以此时e 其实就是一个链表
     Entry<K,V> e = table[bucketIndex];
     // 添加新的节点。将链表e作为next节点。 
     table[bucketIndex] = new Entry<>(hash, key, value, e);
     size++;
 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

综上源码所述,使用的是头插法

3.2 jdk1.8 版本体现

链表插入方式链表 尾插法 其实就是添加到链表的尾部

 for (int binCount = 0; ; ++binCount) {
     if ((e = p.next) == null) {
     	// 此代码是尾插法的核心部分
         p.next = newNode(hash, key, value, null);
     }
     p = e;
 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

4. 获取索引方式

4.1 jdk1.7 版本体现

在1.7版本中 只是将hash值 以及数组长度进行&运算,从而计算出索引位置。

 static int indexFor(int h, int length) {
     // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
     return h & (length-1);
 }
  • 1
  • 2
  • 3
  • 4

4.2 jdk1.8 版本体现

Java1.8的hash()中,将hash值高位(前16位)参与到取模的运算中,使得计算结果的不确定性增强,降低发生哈希碰撞的概率

 static final int hash(Object key) {
     int h;
     return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
 }
  • 1
  • 2
  • 3
  • 4

5. 扩容优化

5.1 jdk1.7 版本体现

jdk1.7中 使用2倍扩容 以及rehash算法来将旧数组上的元素 添加到新数组中

第一步:方法addEntry 扩容机制

// --------------- 此方法为 createEntry方法 ------------------
void addEntry(int hash, K key, V value, int bucketIndex) {
	if ((size >= threshold) && (null != table[bucketIndex])) {
		resize(2 * table.length);
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

第二步:方法resize核心功能

// --------------- 此方法为 resize核心功能 ------------------
    void resize(int newCapacity) {
        Entry[] newTable = new Entry[newCapacity];
        transfer(newTable, initHashSeedAsNeeded(newCapacity));
        table = newTable;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

第三步:方法transfer 开始移动元素

// --------------- 此方法为 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;
            if (rehash) {
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            // 根据新的hash值 以及数组长度 计算位置
            int i = indexFor(e.hash, newCapacity);
            e.next = newTable[i];
            newTable[i] = e;
            e = next;
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

5.2 jdk1.8 版本体现

扩容也是2倍的扩容方式。
JDK 1.8 在扩容时并没有像 JDK 1.7 那样,重新计算每个元素的哈希值,而是通过高位运算(e.hash & oldCap)来确定元素是否需要移动

 do {
     next = e.next;
     // 如果结果不是1的时候 代表需要移动了
     if ((e.hash & oldCap) == 0) {
         if (loTail == null)
             loHead = e;
         else
             loTail.next = e;
         loTail = e;
     }
     else {
         if (hiTail == null)
             hiHead = e;
         else
             hiTail.next = e;
         hiTail = e;
     }
 } while ((e = next) != null);

if (hiTail != null) {
    hiTail.next = null;
    // 当前下标的长度 + 旧数组长度
    newTab[j + oldCap] = hiHead;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/564540
推荐阅读
相关标签
  

闽ICP备14008679号