如上图为HashMap的结构
1、HashMap是数组+链表的结构,数组,查找效率是O(1),删除和插入是O(n),链表插入、删除是O(1),插入是O(n);使用数组+链表结合的方式,提高效率
2、关键属性
(1)、初始容量(capacity)
(2)、填充因子(loadFactor)
(3)、阈值(threshold)
(4)、UNTREEIFY_THRESHOLD(table[i]位置的所有bin转为链表的条件,值为6)
THREEIFY_THRESHOLD(table[i]位置所有bin转为红黑树的条件,值为8)
(5)、MIN_TREEIFY_CAPACITY(在将链表转为红黑树的时候,判断table.length如果小于MIN_TREEIFY_CAPACITY,需要resize,MIN_TREEIFY_CAPACITY值为64)
(6)、MAXIMUM_CAPACITY( table的最大长度)
其中,threshold = capacity*loadFactor,当map.size>threshold时,需要resize进行扩容;
table的长度是大于等于capacity的最近的2的幂,这样做的好处是,为了更方便更快的计算hashIndex,即,找寻的key对应的table数组的下标(具体方式在下面讲)
3、resize扩容原理
(1)、如果是第一次的话,并且没有设定初始容量initialCapacity即创建HashMap的时候,没有传参,则table的长度为默认值16,threshold为初始长度16*初始填充因子0.75;
(2)、否则,是第一次,并且传参(传参的时候,根据传入的参数,重新计算threshold为大于等于capacity的最近的2的幂,具体方式为
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) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}复制代码
从而得到的值为2的幂,详解:
比如n=cap-1为 01xx.......xx
n>>>1为:001xx..xx,再与n或操作得n=011xx..xx;
n>>>2为:00011xx..xx,再与n或操作得n=01111xx.xx;
n>>>4为:000001111xx..xx,再与n或操作得n=011111111xx..xx;
.......
以此类推,右移16位,则最多得到32个连续的1,保证从最高位的1到末尾全部为1,然后再加1,得到了最近的大于cap的2的幂
),table的长度为threshold;
(3)、不是第一次,则判断table.length是否已经达到最大限度MAXIMUM_CAPACITY,如果是,threshold设置为Integer.MAX_VALUE,并返回;否则,threshold在原来的基础上*2,即扩容2倍。然后将oldTable的内容复制到newTable上(
由于在扩容的时候,直接是在原来的基础上*2,即左移一位,因此可以理解为,扩容后的最高位要么为1(扩容2倍,数组长度是原来的2倍),要么为0(没有扩容,即已经达到最大);即高位为0的话,索引就是原索引,是1的话,索引变为原索引+oldCap。具体重新分配逻辑为:
)。
4、hashIndex的计算方式
(1)、计算hash
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}复制代码
使key的hashCode,右移16位,使key的高16位保持不变,然后再异或,充分利用高位,从而避免浪费,并且降低碰撞的频率(若只利用低16位,会容易碰撞)
(2)、(n - 1) & hash
由于上面说过,数组的长度是2的幂,因此这里只用逻辑与而不是取余,系统开销小
使用上面的方式计算hashIndex,充分利用高位与逻辑运算,降低了碰撞率与执行的效率;同时,对value进行插入的时候,使用链表以及红黑树,也降低了碰撞率复制代码
5、put原理
(1)、利用hashCode找到hashIndex对应的table[hashIndex]位置的值e
(2)、如果e为空,则直接插入
(3)、如果e.hash==hash&&((k=e.key)==key)||(key!=null&&key.equals(k)),则key相同,根据参数onlyIfAbsent,或者e.value为空,判断是否需要覆盖
(4)、否则,如果是一个红黑树,则插入红黑树里面,否则jdk8插入链表尾部,jdk7是头插法(插入链表的时候,会判断table[i]位置上的bin个数是否满足要转为红黑树的条件,如果是,则转为红黑树)
上述4步执行完之后,判断数组中以及插入的bin的个数即map.size是否大于threshold,如果是,需要resize
6、get原理
计算hashIndex,获取table[hashIndex]位置的值,返回
7、remove原理
计算hashIndex,获取table[hashIndex]位置的值,如果为空,就是没找到,否则在链表或者红黑树里面删除