基于现有的一些资料,加入一些方法和常量的补充解释
方法
1. resize()
我们讲解下JDK1.8做了哪些优化。经过观测可以发现,我们使用的是2次幂的扩展(指长度扩为原来2倍),
所以,元素的位置要么是在原位置,要么是在原位置再移动2次幂的位置。
看下图可以明白这句话的意思,n为table的长度,图(a)表示扩容前的key1和key2两种key确定索引位置的示例,图(b)表示扩容后key1和key2两种key确定索引位置的示例,其中hash1是key1对应的哈希与高位运算结果。
元素在重新计算hash之后,因为n变为2倍,那么n-1的mask范围在高位多1bit(红色),因此新的index就会发生这样的变化:
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
// 超过最大值就不再扩充了,就只好随你碰撞去吧
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
// 没超过最大值,就扩充为原来的2倍
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
// 计算新的resize上限
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) {
// 把每个bucket都移动到新的buckets中
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // 链表优化重hash的代码块
Node<K,V> loHead = null, loTail = null; // 原索引存放的引用
Node<K,V> hiHead = null, hiTail = null; // 原索引+oldCap存放的引用
Node<K,V> next;
do {
next = e.next;
/*
取余(%)操作中如果除数是2的幂次则等价于与其除数减一的与(&)操作
(也就是说 hash%length==hash&(length-1)的前提是 length 是2的 n 次方;)。
*/
// 原索引
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e; // 头插法
loTail = e; // 头插法
} else { // 原索引+oldCap
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
// 原索引放到bucket里
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
// 原索引+oldCap放到bucket里
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
2. values() 方法
values() 是获取集合中的所有的值----没有键,没有对应关系.
3. keySet() 方法
将Map中所有的键存入到set集合中。因为set具备迭代器。所有可以迭代方式取出所有的键,再根据get方法。获取每一个键对应的值。 keySet():迭代后只能通过get()取key
4. entrySet() 方法
Set<Map.Entry<K,V>> entrySet() //返回此映射中包含的映射关系的 Set 视图。 Map.Entry表示映射关系。entrySet():迭代后可以e.getKey(),e.getValue()取key和value。返回的是Entry接口 。
虽然使用keyset及entryset来进行遍历能取得相同的结果,但两者的遍历速度是有差别的
keySet():迭代后只能通过get()取key
entrySet():迭代后可以e.getKey(),e.getValue()取key和value。返回的是Entry接口
说明:keySet()的速度比entrySet()慢了很多,也就是keySet方式遍历Map的性能不如entrySet性能好, 因而通常为了提高性能,以后多考虑用entrySet()方式来进行遍历。
优化
1. HashMap 初始大小为何是 16
每当插入一个元素时,我们都需要计算该值在数组中的位置,即p = tab[i = (n - 1) & hash]。
当 n = 16 时,n - 1 = 15,二进制为 1111,这时和 hash 作与运算时,元素的位置完全取决与 hash 的大小
倘若不是 16,如 n = 10,n - 1 = 9,二进制为 1001,这时作与运算,很容易出现重复值,如 1101 & 1001,1011 & 1001,1111 & 1001,结果都是一样的,所以选择 16 以及 每次扩容都乘以二的原因也可想而知了
2. 懒加载
我们在 HashMap 的构造函数中可以发现,哈希表 Node[] table 并没有在一开始就完成初始化;观察 put 方法可以发现:
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
复制代码
当发现哈希表为空或者长度为 0 时,会使用 resize 方法进行初始化,这里很显然运用了 lazy-load 原则,当哈希表被首次使用时,才进行初始化
3. 树化
Java8 中,HashMap 最大的变动就是增加了树化处理,当链表中元素大于等于 8,这时有可能将链表改造为红黑树的数据结构,为什么我这里说可能呢?
final void treeifyBin(HashMap.Node<K,V>[] tab, int hash) {
int n, index; HashMap.Node<K,V> e;
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
else if ((e = tab[index = (n - 1) & hash]) != null) {
//......
}
复制代码
我们可以观察树化处理的方法 treeifyBin,发现当tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY为 true 时,只会进行扩容处理,而没有进行树化;MIN_TREEIFY_CAPACITY 规定了 HashMap 可以树化的最小表容量为 64,这是因为当一开始哈希表容量较小是,哈希碰撞的几率会比较大,而这个时候出现长链表的可能性会稍微大一些,这种原因下产生的长链表,我们应该优先选择扩容而避免这类不必要的树化。
那么,HashMap 为什么要进行树化呢?我们都知道,链表的查询效率大大低于数组,而当过多的元素连成链表,会大大降低查询存取的性能;同时,这也涉及到了一个安全问题,一些代码可以利用能够造成哈希冲突的数据对系统进行攻击,这会导致服务端 CPU 被大量占用。
作者:Howie_Y
链接:juejin.cn/post/684490…
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
作者:luke_
链接:www.jianshu.com/p/bdfe7ddd8…
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。