看完这篇 HashMap ,和面试官扯皮就没问题了(2)

简介: 看完这篇 HashMap ,和面试官扯皮就没问题了(2)

HashMap 1.7 的底层结构


JDK1.7 中,HashMap 采用位桶 + 链表的实现,即使用链表来处理冲突,同一 hash 值的链表都存储在一个数组中。但是当位于一个桶中的元素较多,即 hash 值相等的元素较多时,通过 key 值依次查找的效率较低。它的数据结构如下


image.png


HashMap 大致结构


HashMap 底层数据结构就是一个 Entry 数组,Entry 是 HashMap 的基本组成单元,每个 Entry 中包含一个 key-value 键值对。


transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;


而每个 Entry 中包含 「hash, key ,value」 属性,它是 HashMap 的一个内部类


static class Entry<K,V> implements Map.Entry<K,V> {

final K key;
V value;
Entry<K,V> next;
int hash;

Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
...
}


所以,HashMap 的整体结构就像下面这样


image.png


HashMap 1.8 的底层结构


与 JDK 1.7 相比,1.8 在底层结构方面做了一些改变,当每个桶中元素大于 8 的时候,会转变为红黑树,目的就是优化查询效率,JDK 1.8 重写了 resize() 方法。


image.png


HashMap 重要属性


「初始容量」


HashMap 的默认初始容量是由 DEFAULT_INITIAL_CAPACITY 属性管理的。


static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;


HashMap 的默认初始容量是 1 << 4 = 16, << 是一个左移操作,它相当于是


image.png


「最大容量」


HashMap 的最大容量是


static final int MAXIMUM_CAPACITY = 1 << 30;


这里是不是有个疑问?int 占用四个字节,按说最大容量应该是左移 31 位,为什么 HashMap 最大容量是左移 30 位呢?因为在数值计算中,最高位也就是最左位的 是代表着符号为,0 -> 正数,1 -> 负数,容量不可能是负数,所以 HashMap 最高位只能移位到 2 ^ 30 次幂。


「默认负载因子」


HashMap 的默认负载因子是


static final float DEFAULT_LOAD_FACTOR = 0.75f;


float 类型所以用 .f 为单位,负载因子是和扩容机制有关,这里大致提一下,后面会细说。扩容机制的原则是当 HashMap 中存储的数量 > HashMap 容量 负载因子时,就会把 HashMap 的容量扩大为原来的二倍。


HashMap 的第一次扩容就在 DEFAULT_INITIAL_CAPACITY DEFAULT_LOAD_FACTOR = 12 时进行。


「树化阈值」


HashMap 的树化阈值是


static final int TREEIFY_THRESHOLD = 8;


在进行添加元素时,当一个桶中存储元素的数量 > 8 时,会自动转换为红黑树(JDK1.8 特性)。


「链表阈值」


HashMap 的链表阈值是


static final int UNTREEIFY_THRESHOLD = 6;


在进行删除元素时,如果一个桶中存储元素数量 < 6 后,会自动转换为链表


「扩容临界值」


static final int MIN_TREEIFY_CAPACITY = 64;


这个值表示的是当桶数组容量小于该值时,优先进行扩容,而不是树化



「节点数组」


HashMap 中的节点数组就是 Entry 数组,它代表的就是 HashMap 中 「数组 + 链表」 数据结构中的数组。


transient Node<K,V>[] table;


Node 数组在第一次使用的时候进行初始化操作,在必要的时候进行 resize,resize 后数组的长度扩容为原来的二倍。


「键值对数量」


在 HashMap 中,使用 size 来表示 HashMap 中键值对的数量。


「修改次数」


在 HashMap 中,使用 modCount 来表示修改次数,主要用于做并发修改 HashMap 时的快速失败 - fail-fast 机制。


「扩容阈值」


在 HashMap 中,使用 threshold 表示扩容的阈值,也就是 初始容量 * 负载因子的值。


threshold 涉及到一个扩容的阈值问题,这个问题是由 tableSizeFor 源码解决的。我们先看一下它的源码再来解释


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;
}


代码中涉及一个运算符 |= ,它表示的是按位或,啥意思呢?你一定知道 「a+=b 的意思是 a=a+b」,那么同理:a |= b 就是 a = a | b,也就是双方都转换为二进制,来进行与操作。如下图所示


image.png


我们上面采用了一个比较大的数字进行扩容,由上图可知 2^29 次方的数组经过一系列的或操作后,会算出来结果是 2^30 次方。


所以扩容后的数组长度是原来的 2 倍。


「负载因子」


loadFactor 表示负载因子,它表示的是 HashMap 中的密集程度。



            </div>
目录
相关文章
|
Python
我这样回答多线程并发,面试官非要跟我做朋友!
我这样回答多线程并发,面试官非要跟我做朋友!
128 0
|
存储 机器学习/深度学习 缓存
为什么要用HashMap?这样回答面试官直呼内行【手撕HashMap系列】
为什么要用HashMap?这样回答面试官直呼内行【手撕HashMap系列】
321 0
为什么要用HashMap?这样回答面试官直呼内行【手撕HashMap系列】
|
存储 机器学习/深度学习 算法
算法系列(2)—— 简答一波 HashMap 常见八股面试题
算法系列(2)—— 简答一波 HashMap 常见八股面试题
182 0
算法系列(2)—— 简答一波 HashMap 常见八股面试题
|
存储 安全 Java
看完这篇 HashMap ,和面试官扯皮就没问题了(4)
看完这篇 HashMap ,和面试官扯皮就没问题了(4)
86 0
看完这篇 HashMap ,和面试官扯皮就没问题了(4)
|
存储 Java
看完这篇 HashMap ,和面试官扯皮就没问题了(2)
看完这篇 HashMap ,和面试官扯皮就没问题了(2)
113 0
看完这篇 HashMap ,和面试官扯皮就没问题了(2)
|
存储 安全 程序员
看完这篇 HashMap ,和面试官扯皮就没问题了(1)
看完这篇 HashMap ,和面试官扯皮就没问题了(1)
133 0
看完这篇 HashMap ,和面试官扯皮就没问题了(1)
|
存储 Java Serverless
看完这篇 HashMap ,和面试官扯皮就没问题了(3)
看完这篇 HashMap ,和面试官扯皮就没问题了(3)
99 0
看完这篇 HashMap ,和面试官扯皮就没问题了(3)
|
SQL Web App开发 缓存
吊打面试官系列之:我这样回答 “如何更高效的进行接口测试“,面试官果然跪了。
吊打面试官系列之:我这样回答 “如何更高效的进行接口测试“,面试官果然跪了。
30922 0
|
存储 算法 安全
看完这篇垃圾回收,和面试官扯皮没问题了
看完这篇垃圾回收,和面试官扯皮没问题了
|
存储 缓存 算法
学完了这篇JVM,面试官真拿我没办法了!
在我们面试中经常会遇到面试官问一些有关JVM的问题,下面我大概从运行时数据域、类加载机制、类加载器、垃圾收集器、垃圾收集算法、JVM堆内存模型、JVM内存结构、JVM调优等几个方面来讲一下JVM。
162 0
学完了这篇JVM,面试官真拿我没办法了!