美文网首页
HashMap原理

HashMap原理

作者: 馒Care | 来源:发表于2021-08-09 20:48 被阅读0次
public V put(K key, V value) {
        return this.putVal(hash(key), key, value, false, true);
    }

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

2.执行putValu进行值计算

final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)

1.校验table是否为空或者length等于0,如果是则调用resize方法进行初始化

    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;

2.通过hash值计算索引位置,将该索引位置的头节点赋值给p,如果p为空则直接在该索引位置新增一个节点即可

   if ((p = tab[i = (n - 1) & hash]) == null)
     tab[i] = newNode(hash, key, value, null);

3.判断p节点的key和hash值是否跟传入的相等,如果相等, 则p节点即为要查找的目标节点,将p节点赋值给e节点

       if (p.hash == hash &&
           ((k = p.key) == key || (key != null && key.equals(k))))
           e = p;

4.判断p节点是否为TreeNode, 如果是则调用红黑树的putTreeVal方法查找目标节点

       else if (p instanceof TreeNode)
           e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);

5.走到这代表p节点为普通链表节点,则调用普通的链表方法进行查找,使用binCount统计链表的节点数

for (int binCount = 0; ; ++binCount){}
        else {
       走到这代表p节点为普通链表节点,则调用普通的链表方法进行查找,使用binCount统计链表的节点数
            for (int binCount = 0; ; ++binCount) {
                // 6.如果p的next节点为空时,则代表找不到目标节点,则新增一个节点并插入链表尾部
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    // 7.校验节点数是否超过8个,如果超过则调用treeifyBin方法将链表节点转为红黑树节点,
                    // 减一是因为循环是从p节点的下一个节点开始的
                    if (binCount >= TREEIFY_THRESHOLD - 1)
                        treeifyBin(tab, hash);
                    break;
                }
                // 8.如果e节点存在hash值和key值都与传入的相同,则e节点即为目标节点,跳出循环
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;  // 将p指向下一个节点
            }
        }
        // 9.如果e节点不为空,则代表目标节点存在,使用传入的value覆盖该节点的value,并返回oldValue
        if (e != null) {
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e); // 用于LinkedHashMap
            return oldValue;
        }
    }
    ++modCount;
    // 10.如果插入节点后节点数超过阈值,则调用resize方法进行扩容
    if (++size > threshold)
        resize();

执行get方法,如果是红黑树,则调用红黑树的查找目标节点方法getTreeNode

public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}
 
final Node<K,V> getNode(int hash, Object key) {
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
    // 1.对table进行校验:table不为空 && table长度大于0 && 
    // table索引位置(使用table.length - 1和hash值进行位与运算)的节点不为空
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) {
        // 2.检查first节点的hash值和key是否和入参的一样,如果一样则first即为目标节点,直接返回first节点
        if (first.hash == hash && // always check first node
            ((k = first.key) == key || (key != null && key.equals(k))))
            return first;
        // 3.如果first不是目标节点,并且first的next节点不为空则继续遍历
        if ((e = first.next) != null) {
            if (first instanceof TreeNode)
                // 4.如果是红黑树节点,则调用红黑树的查找目标节点方法getTreeNode
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);
            do {
                // 5.执行链表节点的查找,向下遍历链表, 直至找到节点的key和入参的key相等时,返回该节点
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    // 6.找不到符合的返回空
    return null;
}

3.DEFAULT_INITIAL_CAPACITY=16?
在线位运算
在线二进制转换

以下仅演示低4位的位运算,其实在jdk1.8的时候位把高28位的运算都加入,以此来降低hash冲突的概率


如果length默认是16,那么length-1=15,执行下面的与运算
h&length-1
 
    15 二进制:  1111
&   0  二进制:  0000
----------------------------
               1111
====================
    15 二进制:  1111
&   1  二进制:  0001
----------------------------
               1110          


如果length默认是15,那么length-1=14,执行下面的与运算
h&length-1
 
    14 二进制:  1110
&   0  二进制:  0000
----------------------------
               1110--------------------------->1110跟下面这个值一样
==================== 
    14 二进制:  1110
&   1  二进制:  0001
----------------------------
               1110--------------------------->1110跟上面这个值一样                

综上分析,如果默认值不是16,或者说不是2的幂指数,那么执行位运算之后,得出的结果可能是相同的,也就是哈希碰撞的概率变高

3.TreeNode 红黑树(高性能的平衡树)来保存冲突节点O(n)->提高到O(logn)

可以考虑下时间复杂度是怎么计算的,这里备注下

4.是否每一个节点都是红黑树?

不是,为了效率更高,并不会直接每个节点都使用红黑树,因为红黑树的初始化复杂,而且有很多的左旋右旋,本身来说,构建复杂耗时。
当节点超过8的时候,才会转成红黑树
目的就是为了让查找效率跟高效

5.HashMap线程安全问题

多线程插入、删除、扩充操作数据的时候,会有多线程安全问题,
可以参考HashTable实现策略
或者SparseArray

6.HashTable 源码分析

基本来说就是把整个HashMap加了锁,其他跟HashMap基本一致

7.ConcurrentHashMap跟HashTable、HashMap区别

锁的范围精确到对应链表,而不是整个HashMap

7.SparseArray 源码分析 跟HashMap区别

8.HashMap为什么会发生死循环
https://baijiahao.baidu.com/s?id=1675991555833901875&wfr=spider&for=pc

待总结

相关文章

网友评论

      本文标题:HashMap原理

      本文链接:https://www.haomeiwen.com/subject/iwjxeltx.html