HashMap原理详解,包括面试会问到的一些问题的总结。
Java重要知识点总结如下:
序号 | 文章 |
---|---|
1 | Java并发的CAS原理详解 |
2 | Java并发的ABA原理详解 |
3 | Java的18种Queue |
4 | 一篇文章整理Java的volatile |
5 | Java集合的线程不安全 |
6 | Java中的21种锁 |
7 | JVM进阶之思维导图 |
8 | Java的HashMap原理总结(问答式学习) |
文章目录
1. 介绍下 HashMap 的底层数据结构
我们现在用的都是 JDK 1.8,底层是由 数组+链表+红黑树 组成,如下图,而在 JDK 1.8 之前是由 数组+链表 组成。
1.1 为什么要改成 数组+链表+红黑树?
主要是为了提升在 hash 冲突严重时(链表过长)的查找性能
,使用链表的查找性能是 O(n),而使用红黑树是 O(logn)。
1.2 为什么用红黑树不用普通的AVL树?
AVL树
:一般用平衡因子判断是否平衡并通过旋转来实现平衡,左右子树树高不超过1,和红黑树相比,AVL树是高度平衡的二叉树,平衡条件必须满足(所有节点的左右子树高度差不超过1)。不管我们是执行插入还是删除操作,只要不满足上面的条件,就要通过旋转来保持平衡,而的由于旋转比较耗时,由此我们可以知道AVL树适合用于插入与删除次数比较少,但查找多的情况
。
红黑树
: 也是一种平衡二叉树,但每个节点有一个存储位表示节点的颜色,可以是红或黑。通过对任何一条从根到叶子的路径上各个节点着色的方式的限制,红黑树确保没有一条路径会比其它路径长出两倍,因此,红黑树是一种弱平衡二叉树红黑树从根到叶子的最长路径不会超过最短路径的2倍(由于是弱平衡,可以看到,在相同的节点情况下,AVL树的高度<=红黑树),相对于要求严格的AVL树来说,它的旋转次数少,所以对于搜索,插入,删除操作较多的情况下,用红黑树
。
1.3 什么时候用链表?什么时候用红黑树?
-
对于插入,默认情况下是使用链表节点。
当同一个索引位置的节点在新增后达到9个(阈值8):如果此时数组长度大于等于 64,则会触发链表节点转红黑树节点(
treeifyBin
);而如果数组长度小于64,则不会触发链表转红黑树,而是会进行扩容,因为此时的数据量还比较小。 -
对于移除,当同一个索引位置的节点在移除后达到 6 个,并且该索引位置的节点为红黑树节点,会触发红黑树节点转链表节点(
untreeify
)。
1.4 为什么链表转红黑树的阈值是8?
在进行方案设计时,必须考虑的两个很重要的因素是:时间和空间
。对于 HashMap 也是同样的道理,简单来说,阈值为8是在时间和空间上权衡的结果。
红黑树节点大小约为链表节点的2倍
,在节点太少时,红黑树的查找性能优势并不明显,付出2倍空间的代价不值得。
1.5 为什么转回链表节点是用的6而不是复用8?
如果我们设置节点多于8个转红黑树,少于8个就马上转链表,当节点个数在8徘徊时,就会频繁进行红黑树和链表的转换
,造成性能的损耗。
2. 讲一下 HashMap 的重要属性
2.1 HashMap 有哪些重要属性?分别用于做什么的?
除了用来存储我们的节点 table 数组外,HashMap 还有以下几个重要属性:
size
:HashMap 已经存储的节点个数;threshold
:扩容阈值,当 HashMap 的个数达到该值,触发扩容。loadFactor
:负载因子,扩容阈值 = 容量 * 负载因子
。
2.2 threshold 除了用于存放扩容阈值还有其他作用吗?
在我们新建 HashMap 对象时, threshold 还会被用来存初始化时的容量
。HashMap 直到我们第一次插入节点时,才会对 table 进行初始化,避免不必要的空间浪费。
2.3 HashMap 的默认初始容量是多少?HashMap 的容量有什么限制吗?
默认初始容量是16
。HashMap 的容量必须是2的N次方
,HashMap 会根据我们传入的容量计算一个大于等于该容量的最小的2的N次方,例如传 9,容量为16。
2.4 2的N次方是怎么算的?代码解释一下。
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0