java8中的HashMap原理详解

 更新时间:2023年09月11日 11:59:07   作者:feiyingHiei  
这篇文章主要介绍了java8中的HashMap原理详解,HashMap是日常开发中非常常用的容器,HashMap实现了Map接口,底层的实现原理是哈希表,HashMap不是一个线程安全的容器,需要的朋友可以参考下

java8 HashMap实现原理

HashMap是日常开发中非常常用的容器,HashMap实现了Map接口,底层的实现原理是哈希表,HashMap不是一个线程安全的容器,jdk8对HashMap做了一些改进,作为开发人员需要对HashMap的原理有所了解,现在就通过源码来了解HashMap的实现原理。

首先看HashMap中的属性

    //Node数组
    transient Node<K,V>[] table;
     //当前哈希表中k-v对个数,实际就是node的个数
    transient int size;
    //修改次数
    transient int modCount;
    //元素阈值
    int threshold;
    //负载因子
    final float loadFactor;

这里的threshold = loadFactor * table.length,hash表如果想要保持比较好的性能,数组的长度通常要大于元素个数,默认的负载因子是0.75,用户可以自行修改,不过最好使用默认的负载因子。

Node是用来存储KV的节点,每次put(k,v)的时候就会包装成一个新的Node, Node定义

    static class Node<K,V> implements Map.Entry<K,V> {
        //hash值
        final int hash;
        final K key;
        V value;
        //hash & (capacity - 1) 相同的Node会形成一个链表
        Node<K,V> next;
        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }
    }

put操作

写入操作是map中最常用的方法,这里看看hashmap的put方法代码

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

这里先计算key的hash值,然后调用putVal()方法,其中hash方法是内部自带的一个算法,会对key的hashcode再做一次hash操作

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

pubVal方法

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length; //如果数组为空,先初始化一下
        if ((p = tab[i = (n - 1) & hash]) == null) //如果对应的数组为空的话,那么就直接new一个node然后塞进去
            tab[i] = newNode(hash, key, value, null);
        else { //如果有值,说明发生了冲突,那么就先用拉链法来处理冲突
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p; //如果头结点的key和要插入的key相同,那么就说明找到了之前插入的节点
            else if (p instanceof TreeNode) //如果链表转成了红黑树
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) { //如果之前没有put过这个节点,那么就new一个新的节点
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) 
                        //另外要检查一下当前链表的长度,如果超过8那么就将链表转化成红黑树
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        //如果找到了之前的节点,那么就跳出
                        break;
                    p = e;
                }
            }
            if (e != null) {
                V oldValue = e.value; 
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e); //在当前类中NOOP
                return oldValue;
            }
        }
        ++modCount;
        //如果当前元素数量大于门限值,就要resize整个hash表,实际上就是把数组扩大一倍,然后将所有元素重新塞到新的hash表中
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict); //在该类中NOOP
        return null;
    }

在hashtable中默认的出现冲突的时候就会将冲突的元素形成一个链表,当链表长度大于8的时候就会将链表变成一个二叉树,这是java8中做出的改进,因为在使用hash表的时候在key特殊的情况下最坏的时候hash表会退化成一个链表,那么原有的O(1)的时间复杂度就变成了O(n),性能就会大打折扣,但是引用了红黑树之后那么在最好的情况下时间复杂度就变成了O(log(n))。

resize方法

final Node<K, V> [] resize() {
......
//去掉了一些代码,只关注最核心的node迁移
//resize会新建一个数组,数组的长度是原来数组长度的两倍
    for (int j = 0; j < oldCap; ++j) {//遍历原来的数组
        Node<K,V> e;
        if ((e = oldTab[j]) != null) {
            oldTab[j] = null;
            if (e.next == null)
                newTab[e.hash & (newCap - 1)] = e; //如果没有形成链表的话,就直接塞到新的hash表中
            else if (e instanceof TreeNode)
                ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); //红黑树操作??
            else { // preserve order
                Node<K,V> loHead = null, loTail = null;
                Node<K,V> hiHead = null, hiTail = null;
                Node<K,V> next;
                do {
                    next = e.next;
                    if ((e.hash & oldCap) == 0) { //如果hash值小于oldCap的时候,那么就还在原来那个数组的位置,就把这个节点放到low链表中
                        if (loTail == null)
                            loHead = e;
                        else
                            loTail.next = e;
                        loTail = e;
                    }
                    else { //否则的话就是因为扩展数组长度,就把原来的节点放到high链表中
                        if (hiTail == null)
                            hiHead = e;
                        else
                            hiTail.next = e;
                        hiTail = e;
                    }
                } while ((e = next) != null);
                if (loTail != null) {
                    loTail.next = null;
                    newTab[j] = loHead; //low链表还放在原来的位置
                }
                if (hiTail != null) {
                    hiTail.next = null;
                    newTab[j + oldCap] = hiHead; //high链表放到j+oldCap位置上
                }
            }
        }
    }
}

resize操作就是创建一个先的数组,然后把老的数组中的元素塞到新的数组中,注意java8中的hashMap中数组长度都是2的n次幂,2、4、、8、16….. 这样的好处就是可以通过与操作来替代求余操作。当数组扩大之后,那么每个元素所在的位置是可以预期的,就是要不就待在原来的位置,要不就是到j+oldCap位置上,举个栗子,如果原来数组长度为4,那么hash为3和7 的元素都会放在index为3的位置上,当数组长度变成8的时候,hash为3的元素还待在index为3的位置,hash为7的元素此时就要放到index为7的位置上。

resize操作是一个很重要的操作,resize会很消耗性能,因此在创建hashMap的时候最好先预估容量,防止重复创建拷贝。

另外hashmap也是非线程安全的,在多线程操作的时候可能会产生cpu100%的情况,主要的原因也是因为在多个线程resize的时候导致链表产生了环,这样下次get操作的时候就会容易进入死循环。

get方法()

get的实现比较简单

final Node<K,V> getNode(int hash, Object key) {
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) {
        if (first.hash == hash && ((k = first.key) == key || (key != null && key.equals(k)))) //如果节点不为空而且头结点与查找的key相同就返回
            return first;
        if ((e = first.next) != null) {
            if (first instanceof TreeNode)
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);//从红黑树中查找
            do {
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null); //遍历链表查找key相同的node
        }
    }
    return null;
}

到此这篇关于java8中的HashMap原理详解的文章就介绍到这了,更多相关HashMap原理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详解spring cloud ouath2中的资源服务器

    详解spring cloud ouath2中的资源服务器

    这篇文章主要介绍了spring cloud ouath2中的资源服务器的相关知识,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-02-02
  • JDK15正式发布(新增功能预览)

    JDK15正式发布(新增功能预览)

    这篇文章主要介绍了JDK15正式发布,新增功能预览,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2020-09-09
  • springboot如何读取配置文件(application.yml)中的属性值

    springboot如何读取配置文件(application.yml)中的属性值

    本篇文章主要介绍了springboot如何读取配置文件(application.yml)中的属性值,具有一定的参考价值,有兴趣的小伙伴可以了解一下
    2017-04-04
  • Netty分布式ByteBuf的分类方式源码解析

    Netty分布式ByteBuf的分类方式源码解析

    这篇文章主要为大家介绍了Netty分布式ByteBuf的分类方式源码解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-03-03
  • Java实现顺序表和链表结构

    Java实现顺序表和链表结构

    大家好,本篇文章主要讲的是Java实现顺序表和链表结构,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下
    2022-02-02
  • SpringBoot定时任务详解与案例代码

    SpringBoot定时任务详解与案例代码

    SpringBoot是一个流行的Java开发框架,它提供了许多便捷的特性来简化开发过程,其中之一就是定时任务的支持,让开发人员可以轻松地在应用程序中执行定时任务,本文将详细介绍如何在Spring Boot中使用定时任务,并提供相关的代码示例
    2023-06-06
  • java中注解机制及其原理的详解

    java中注解机制及其原理的详解

    这篇文章主要介绍了java中注解机制及其原理的详解的相关资料,希望通过本文能帮助到大家,让大家理解掌握这部分内容,需要的朋友可以参考下
    2017-10-10
  • Spring AOP 与代理的概念与使用

    Spring AOP 与代理的概念与使用

    大家知道我现在还是一个 CRUD 崽,平时用 AOP 也是 CV 大法。最近痛定思痛,决定研究一下 Spring AOP 的原理。 这里写一篇文章总结一下。主要介绍 Java 中 AOP 的实现原理,最后以两个简单的示例来收尾。
    2020-10-10
  • Java自定义标签用法实例分析

    Java自定义标签用法实例分析

    这篇文章主要介绍了Java自定义标签用法,结合实例形式分析了java自定义标签的定义、使用方法与相关注意事项,需要的朋友可以参考下
    2017-11-11
  • SpringBoot整合MybatisSQL过滤@Intercepts的实现

    SpringBoot整合MybatisSQL过滤@Intercepts的实现

    这篇文章主要介绍了SpringBoot整合MybatisSQL过滤@Intercepts的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-03-03

最新评论