HashMap的底层是通过
数组
+单向链表/红黑树
实现的。
数组特点:
链表特点:
哈希表特点:
以上数组和链表,大家都知道各自优缺点。那么我们能不能把以上两种结合在一起使用,从而实现查询效率高和删除插入效率也高的数据结构呢?答案是可以滴,那就是哈希表
可以满足,接下来我们一起复习下 HashMap 中的 put() 和 get() 方法实现原理。
第1步,首先将 k, v 封装到 Node 对象当中(节点)。
第2步,它的底层会调用 K 的 hashCode() 方法得出 hash 值。
第3步,通过哈希表函数/哈希算法,将 hash 值转换成数组的下标:
增删是在链表上完成的,而查询主要是通过数组定位,然后扫描部分链表,所以效率高。
HashMap 集合的 key,会先后调用两个方法:hashCode() 和 equals() 方法,所以当对象充当 key 时,这两个方法都需要重写。
因为 equals 默认比较的是两个对象的内存地址,如果想根据对象的属性来判断,则需要重写。
因为不一定挂到哪一个单向链表上,因此加入顺序和取出也不一样。
使用 equals 方法来保证 HashMap 的 key 不可重复。如果 key 重复的话,value 就会覆盖。存放在 HashMap 集合中的 key,其实就是存放在 HashSet 集合中,所以 HashSet 集合也需要重写 equals() 和 hashCode() 方法。
HashMap 集合的默认初始化容量为16,默认加载因子为 0.75,也就是说当 HashMap 集合底层数组的容量达到 75% 时,数组就开始扩容。HashMap 集合初始化容量是 2 的倍数,是为了达到散列均匀,提高 HashMap 集合的存取效率。
new HashMap<>()
,底层不会再创建一个长度为 16 的数组,改为首次调用 put()
方法时创建;
jdk8 底层的数组是 Node[]
,而非 Entry[]
;
jdk7 底层结构只有:数组+链表
,jdk8 中底层结构:数组+链表+红黑树
。
3.1 形成链表时,七上八下
jdk7:头插法,新元素指向旧元素(多线程修改会有死锁问题);
jdk8:尾插法,旧元素指向新元素;
3.2 为什么要用红黑树:
泊松分布
,在负载因子 0.75 的情况下,长度超过 8 的链表出现概率时 0.00000006,选择 8 就是为了尽量降低树化的几率。3.3 树化的两个条件:(必须都满足)
3.4 退化链表的条件:(任何一个满足)
如果 key1 和 key2 的哈希值相同,就会存放到同一个单向链表上。
如果 key1 和 key2 的哈希值不同,但由于哈希算法执行结束之后转换的数组下标可能相同,此时会发生哈希碰撞
。
允许
JDK8 中 HashMap 的 put() 方法:
public V put(K key, V value) {// 采用 hash(key) 来计算 key 的 hashCode 值return putVal(hash(key), key, value, false, true);
}static final int hash(Object key) {int h;// 当 key 为 null 的时候,不走 hashCode() 方法return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
HashMap 中使用 hash() 方法来计算 key 的哈希值,当 key 为 null 时,直接令 key 的哈希值为0,不走 key.hashCode() 方法,所以即使为 null 也不会抛出空指针异常。
整理完毕,完结撒花~ 🌻
参考地址:
1.来复习一波,HashMap底层实现原理解析,https://baijiahao.baidu.com/s?id=1665667572592680093&wfr=spider&for=pc
2.java中的hashMap允许key为null的原因,https://blog.csdn.net/weixin_46984636/article/details/120606095
3.HashMap,https://blog.csdn.net/qq_39181839/article/details/109478003
4.HashMap,https://blog.csdn.net/weixin_42470732/article/details/124027786