基本原理
HashMap在JDK1.8中采用数组+链表+红黑树进行存储,通过计算key的hashCode,利用hashCode通过hash方法计算出桶下标,再判断HashMap中是否存在该键值对,存在:判断新键值对与该桶第一个节点是否相同,相同,判断 onlyIfAbsent 表示是否仅在 oldValue 为 null 的情况下更新键值对的值 不存在:判断是否是红黑树节点,是:按照红黑树插入节点方式进行插入,不是:插入到链表最后一个位置,并且判断是否大于或者等于链表转换红黑树的阈值,若是则进行红黑树转化,最后判断键值对总数是否到达阈值,是,进行扩容
构造方法
共有4种构造方法
无参构造方法,最常用
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
第二种、第三种,设置了一些变量
/** 构造方法 2 */
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
/** 构造方法 3 */
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
第四种拷贝一份Map,不常用
/** 构造方法 4 */
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
参数说明
| 参数 | 说明 |
|---|---|
| initialCapacity | HashMap初始容量 |
| loadFactor | 加载因子 |
| thresold | 阈值 |
此处提问的问题
-
数组长度为什么是2的幂次方
加快计算以及减少哈希冲突 确定数组下标,位运算比取模运算快;可以让高位、底位都参加到数组下标计算中,增大hash的复杂度
-
如何扩容
确定数组容量大小合适:HashMap之tableSizeFor方法图解 - 掘金 (juejin.cn)
resize:
resize分为3种情况: 1.数组不为空 2.数组为空,但设置了数组初始值 3.数组为空,调用无参构造方法 1.数组大小、阈值直接扩容为原来的2倍 2.newCap取初始容量,若初始容量不合理,使用tableSizeFor设为2的幂次方 3.设置为默认大小 再对情况1做数值迁移 旧元素桶下标变为:下标值不变||下标值变为原来的2倍 其实很容易理解,初始化和 (16 - 1)做与运算时,只有低 4 位的 hash 是有意义的, 是扩容的时候,和 (32 -1 )做与运算时,低 5 位也参与了运算,所以低 5 位的值决定了 rehash 后新的 index 的值。如果低 5 位为 0 ,index 值不变,如果低 5 位为1,则 index 改变,并且在原先基础上加了 2 的 4 次方,即 16 。 -
头插法、尾插法
-
如何插入
-
系类问题:hashmap头插法和尾插法区别_一个跟面试官扯皮半个小时的HashMap(看这一篇就足够了) - 掘金 (juejin.cn)