java8 对hashmap底层实现原理的理解

对hashmap的个人理解,分享给大家,如果有不对的地方还请大家指正。

在java8中hashmap的底层数据结构是Node数组,Node数组的数据结构是链表,当链表达到一定长度(8)将转为红黑树,这也是为什么说java8中hashmap是由数组+链表+红黑树组成。

那么接下来就大概说一下具体的实现过程
1.put
在执行put操作时,首先根据hash方法获取key.hashcode,之后根据hashcode通过取模运算(table[table.length-1 & hash])获取索引,当发现当前索引没有值时,直接将put进来的k-v存储在当前位置;那么如果当前索引位置有值(也就是说将要push数据的k.hasdcode与数组中的某一个k.hashcode相同)此时需要通过equal比较key是否也相同,如果key相同,则说明是同一个key,那么需要将原来的oldvalue替换为新的value;
还有一种情况,就是当k.hashcode相同,但是key不相同时,这种情况就叫做hash碰撞,在这种情况下解决方案是在当前hashcode所指向的node链表下继续添加k-v,如果链表的长度超过8时,自动将当前链表转换为红黑树来存储,为什么要转为红黑树呢?因为红黑树的速度比链表更快,熟悉算法的朋友应该知道链表的时间复杂度为O(n),红黑树的时间复杂度为O(logn)。
以上也说明了在使用map时,key为什么要必须覆写equals方法以及hashcode方法。

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    
    
    HashMap.Node<K,V>[] tab; HashMap.Node<K,V> p; int n, i;
    // 1.如果table为空或者长度为0,即没有元素,那么使用resize()方法扩容
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    // 2.计算插入存储的数组索引i,此处计算方法同 1.7 中的indexFor()方法
    // 如果数组为空,即不存在Hash冲突,则直接插入数组
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    // 3.插入时,如果发生Hash冲突,则依次往下判断
    else {
    
    
        HashMap.Node<K,V> e; K k;
        // a.判断table[i]的元素的key是否与需要插入的key一样,若相同则直接用新的value覆盖掉旧的value
        // 判断原则equals() - 所以需要当key的对象重写该方法
        if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        // b.继续判断:需要插入的数据结构是红黑树还是链表
        // 如果是红黑树,则直接在树中插入 or 更新键值对
        else if (p instanceof HashMap.TreeNode)
            e = ((HashMap.TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        // 如果是链表,则在链表中插入 or 更新键值对
        else {
    
    
            // i .遍历table[i],判断key是否已存在:采用equals对比当前遍历结点的key与需要插入数据的key
            //    如果存在相同的,则直接覆盖
            // ii.遍历完毕后任务发现上述情况,则直接在链表尾部插入数据
            //    插入完成后判断链表长度是否 > 8:若是,则把链表转换成红黑树
            for (int binCount = 0; ; ++binCount) {
    
    
                if ((e = p.next) == null) {
    
    
                    p.next = newNode(hash, key, value, null);
                    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;
            }
        }
        // 对于i 情况的后续操作:发现key已存在,直接用新value覆盖旧value&返回旧value
        if (e != null) {
    
     // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    // 插入成功后,判断实际存在的键值对数量size > 最大容量
    // 如果大于则进行扩容
    if (++size > threshold)
        resize();
    // 插入成功时会调用的方法(默认实现为空)
    afterNodeInsertion(evict);
    return null;
}

2.get
get就相对简单了,通过key获取hashcode,之后同样根据hashcode取模运算(table[table.length-1 & hash])获取索引,之后在node数组中根据索引取出对应的值,这里存在一种情况就是之前的hash碰撞,将值存在链表或者红黑树中了,这时需要通过遍历,依次比对key来取出对应的值,这里红黑树就发挥快的作用了。

以上是个人对hashmap的一些理解。

猜你喜欢

转载自blog.csdn.net/qq_41454044/article/details/110642514