集合面试题--HashMap

目录

HashMap实现原理

HashMap的jdk1.7和jdk1.8有什么区别

 总结

 HashMap的put方法的具体流程

常见属性

添加数据的流程图

​编辑

具体实现源码

总结

HashMap的扩容机制

总结

hashMap的寻址算法

 总结

 hashmap在1.7情况下的多线程死循环问题


HashMap实现原理

 

HashMap的jdk1.7和jdk1.8有什么区别

 总结

 HashMap的put方法的具体流程

常见属性

重点看第三个元素,Node是一个内部类,其中第一个属性Hash是用来计算在数组中的索引的位置。第二个是你存储的key,value还有next。

那为什么一用next呢?因为数组中不仅仅只存一个数据,也有可能是链表或者是红黑树,当然有下一个了。这就是table,hashmap中真正存储数据的数组

那他的构造函数是什么样子的呢?

添加数据的流程图

首先会去判断table是否为空,就是指的是当前数组是否为空,第一次添加数据肯定是空的,于是回去调用resize方法,来初始化长度16的数组,然后会根据key来计算索引值,

然后再判断,当前下标的位置随便null,第一次添加肯定为null,所以直接插入,同时会和threshold比较,这个就是阈值,如果超过阈值,就会再一次调用resize进行扩容

那如果不是第一次添加的话,首先会判断,数组当前位置是否为null,如果不为null,则判断传入的key和下标位置存储的key是否相同,如图所示

具体实现源码

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

    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)
            //如果未初始化,调用resize方法 进行初始化
            n = (tab = resize()).length;
        //通过 & 运算求出该数据(key)的数组下标并判断该下标位置是否有数据
        if ((p = tab[i = (n - 1) & hash]) == null)
            //如果没有,直接将数据放在该下标位置
            tab[i] = newNode(hash, key, value, null);
        //该数组下标有数据的情况
        else {
            Node<K, V> e;
            K k;
             //判断该位置数据的key和新来的数据是否一样
            if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                //如果一样,证明为修改操作,该节点的数据赋值给e,后边会用到
                e = p;
             //判断是不是红黑树
            else if (p instanceof TreeNode)
                 //如果是红黑树的话,进行红黑树的操作
                e = ((TreeNode<K, V>) p).putTreeVal(this, tab, hash, key,
                        value);
            //新数据和当前数组既不相同,也不是红黑树节点,证明是链表
            else {
                //遍历链表
                for (int binCount = 0; ; ++binCount) {
                    //判断next节点,如果为空的话,证明遍历到链表尾部了
                    if ((e = p.next) == null) {
                        //把新值放入链表尾部
                        p.next = newNode(hash, key, value, null);
                         //因为新插入了一条数据,所以判断链表长度是不是大于等于8
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1 st
                         //如果是,进行转换红黑树操作
                        treeifyBin(tab, hash);
                        break;
                    }
                    //判断链表当中有数据相同的值,如果一样,证明为修改操作
                    if (e.hash == hash &&
                            ((k = e.key) == key || (key != null &&
                                    key.equals(k))))
                        break;
                    //把下一个节点赋值为当前节点
                    p = e;
                }
            }
            //判断e是否为空(e值为修改操作存放原数据的变量)
            if (e != null) { // existing mapping for key
                //不为空的话证明是修改操作,取出老值
                V oldValue = e.value;
                //一定会执行 onlyIfAbsent传进来的是false
                if (!onlyIfAbsent || oldValue == null)
                    //将新值赋值当前节点
                    e.value = value;
                afterNodeAccess(e);
                //返回老值
                return oldValue;
            }
        }
        //计数器,计算当前节点的修改次数
        ++modCount;
        //当前数组中的数据数量如果大于扩容阈值
        if (++size > threshold)
            //进行扩容操作
            resize();
        //空方法
        afterNodeInsertion(evict);
        //添加操作时 返回空值
        return null;
    }

总结

HashMap的扩容机制

当第一次扩容的时候,oldCap是0,肯定是不成立的,如第一行

当不是第一次扩容时,就会让旧容量*2

可以参考这个

HashMap扩容时的rehash方法中(e.hash & oldCap) == 0详解_上进的小镇青年的博客-CSDN博客

总结

 

hashMap的寻址算法

 

 总结

 hashmap在1.7情况下的多线程死循环问题

   

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值