【重头再学Java-HashMap】

首先HashMap是我在编程中最常用的Map之一,之前只知道使用,但是从来没有去深挖过其的原理,近期准备面试却什么也不知道,所以准备从头再学一次。

原理

底层实现

- JDK1.7 table数组 + Entry链表
- JDK1.8 Table数组 + Entry链表/红黑树

其中的一些重要变量

 - DEFAULT_INITIAL_CAPACITYtable数组的初始化长度: 1 << 4 = 16
 - MAXIMUM_CAPACITY table数组最大长度:1 << 30 = 1073741824
 - DEFAULT_LOAD_FACTOR 负载因子:0.75。当元素的总个数  > 当前数组的长度 * 负载因子时,数组会扩容到原来的两倍
 - TREEIFY_THRESHOLD 链表转换为红黑树的参数,默认:8。表示在同一个Node(也就是同一个下标的位置,如果链表上元素超过8时,链表转换为红黑树)
 - UNTREEIFY_THRESHOLD 红黑树转换为链表阈值,默认:6。表示当前红黑树的元素个数小于6时,红黑树退化为链表
 - MIN_TREEIFY_CAPACITY 最小树化阈值,64.表示只有Table数组长度超过64时才会进行树化。(为了防止前期阶段频繁扩容和树化过程冲突)

底层原理

- HashMap采用Entry数组来存储key-value键值对,每个键值对组成了一个Entry实体,Entry类实际上是哥单向的链表结构,它具有next指针,可以连接下一个Entry实体。

关于原理的一些问题

1、为什么使用链表+数组
	要知道为什么使用链表首先需要知道hash冲突是怎么来的。由于我们的数组的值是限制死的,我们在对key值进行散列值取到下标后,放入到数组中时,难免出现两个key值不同,但是下标相同的格子中,此时我们就可以使用链表来对其进行链式存放
2、LinkedList可以代替数组结构吗
	可以
3、其他的几个List都可以代替数组结构,为什么要使用数组
	因为效率最高的结构是数组。在HashMap中,定位节点的位置是利用元素key的hash值对数组长度取模得到的,此时,我们已经得到了节点的位置,显然数组的查找效率也是比较重要的
	ArrayList底层是数组,虽然查找快,但是在扩容时,HashMap中数组扩容刚好是2的次幂。ArrayList是1.5倍扩容

一些方法

put

1、对key的hashCode进行hash运算,计算出index
2、如果没有发生碰撞则生成新node放入数组
3、如果碰撞了,以链表的形式存放在数组上
4、如果碰撞了导致链表过长则转换为红黑树
5、如果节点已经存在就替换旧值
6、如果满了就扩容
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        // 如果数组为空则初始化为默认长度16
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        // 将key的hashCode进行二次hash后,与数组长度进行取模运行,也就是数组长度减一的二进制与二次hash值进行与运算得到数组下标,判断在数组中该下标位置是否有值,没有则创建一个新node放到该位置
        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 = p;
            // 如果当前Node类型为TreeNode,调用 PutTreeVal方法
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
            //如果不是TreeNode,则就是链表,遍历并与输入key做命中碰撞。
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                    //如果当前链表中不存在当前value,则添加。
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        //超过了``TREEIFY_THRESHOLD``则转化为红黑树。
                            treeifyBin(tab, hash);
                        break;
                    }
                    //做命中碰撞,使用hash、内存和equals同时判断(不同的元素hash可能会一致)。
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                //如果命中不为空,更新操作。
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
        //扩容检测!
            resize();
        afterNodeInsertion(evict);
        return null;
    }

get

1、对key的hashCode做hash运算,计算index
2、在数组中的第index节点直接命中则返回
3、如果有冲突,则通过key.equals(index)去查找对应的Entry
4、若为数,则在树中查找

containsKey

根据get方法的结果,判断是否为空,判断是否包含该key
public boolean containsKey(Object key) {
        return getNode(hash(key), key) != null;
    }

为什么hashMap中链表元素超过8时转换为红黑树

1、 在1.8中改动了什么
	a.由数组+链表的结构改为数组+链表+红黑树
	b.优化了位运算的hash算法: h^(h>>16)
	c.扩容后,元素要么在原来的位置,要么是在原来位置再移动2次幂的位置,且链表顺序不变
2、hashMap的线程不安全性
	[具体描述,非常仔细](https://mp.weixin.qq.com/s/VJf5zu6wJzkUN_N8rI22kQ)
3、为什么不一开始就使用红黑树
	因为红黑树需要左旋、右旋这些操作来保持;平衡,链表不需要,当个数小于8时,链表结构已经能满足查询了,当元素大于8时,需要红黑树来加快查询,但是新增节点变慢了
4、什么时候退化红黑树
	当元素个数小于6时,退化红黑树,中间有个7避免频繁转换

后记:高并发下建议使用currentHashMap,虽然我目前没有使用过

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值