44
5520年10月18日写
66
7- 本文基于 JDK 8 的 HashMap 缩写 。
7+ 本文基于 JDK 8 的 HashMap 所写 。
88
99# 概述
1010
11- HashMap 是基于 Map 接口实现的哈希表,==它允许拥有为 null 的 key 和 value==。<font color = red >相比 HashTable 二者最大的不同在于 HashTable 不接受 null 值(HashMap 只允许一个 key 为 null, 但 value 可以多个为 null)</font >,而且 HashTable 是线程安全的但 HashMap 并不是,其他方面二者大致相同。要注意 HashMap 并不能保证映射的顺序性,而且随着事件的推移映射的顺序也可能发生改变 (这是因为 hash 算法的随机性且在扩容时重新hash)。但是使用链表实现的 LinkedHashMap 可以保证顺序性。
11+ HashMap 是基于 Map 接口实现的哈希表,==它允许拥有为 null 的 key 和 value==。<font color = red >相比 HashTable 二者的明显不同在于 HashTable 不接受 null 值(HashMap 只允许一个 key 为 null, 但 value 可以多个为 null)</font >,而且 HashTable 是线程安全的但 HashMap 并不是,其他方面二者大致相同。要注意 HashMap 并不能保证映射的顺序性,而且随着时间的推移映射的顺序也可能发生改变 (这是因为 hash 算法的随机性且在扩容时重新hash)。但是使用链表实现的 LinkedHashMap 可以保证顺序性。
1212
1313
1414
15- 在正常情况下 HashMap 提供的 get 和 put 方法的时间复杂度恒定为 O(1), 当如果存在大量 hash 碰撞的情况 get 方法会退化为 O(logn) ~ O(n) 之间。在遍历的时候 HashMap 的性能和容量、负载因子相关性非常大:容量越大、负载因子越小 HashMap 的性能就越差。如果迭代的性能很重要,则不要设置过高的容量和过低的负载因子。这一点非常重要。
15+ 在正常情况下 HashMap 提供的 get 和 put 方法的时间复杂度恒定为 O(1), 当如果存在大量 hash 碰撞的情况 get 方法会退化为 O(logn) ~ O(n) 之间。在遍历的时候 HashMap 的性能和容量、负载因子相关性非常大:容量越大、负载因子越小 HashMap 的性能就越差(原因是 hash 冲突会变多) 。如果迭代的性能很重要,则不要设置过高的容量和过低的负载因子。这一点非常重要。
1616
17- 初始容量(initialCapacity)和负载因子(loadFactor)是影响 HashMap 的重要指标,initialCapacity 指的是 HashMap 底层数组初始化时的容量,而 loadFactor 是指自动扩容的阈值。此阈值 = initialCapacity * loadFactor,当哈希表的使用条目大于了阈值。则将容量扩充为 initialCapacity 的 2 倍。
17+ 初始容量(initialCapacity)和负载因子(loadFactor)是影响 HashMap 的重要指标,initialCapacity 指的是 HashMap 底层数组初始化时的容量,而 loadFactor 是指自动扩容的阈值。此阈值 = initialCapacity * loadFactor,当哈希表的使用条目大于了阈值。则将容量扩充为 initialCapacity 的 == 2 倍。==
1818
1919默认情况下 HashMap 的负载因子为 0.75, 这是一个在空间和时间成本上做了很好这中的经验值。如果负载因子较大表面上是可以较少扩容次数节约空间,但是 get 和 push 的执行时间成本将会增加。如果设置的过低,则会多次触发扩容和重新 hash 时间性能会大幅下降。所以权衡 initialCapacity 和 loadFactor 是很关键的。在设置他们的时候应该结合使用场景考虑映射中的预期条目数及其负载因子,要最大程度上减少重新哈希操作的次数。
2020
@@ -179,9 +179,10 @@ HashMap 的数据存储过程主要体现在 putVal 函数当中,但此部分
179179 int hash = hash(key);
180180 // 检测 table 有没有初始化
181181 if (table == null || length == 0 ) {
182+ // ! 这种一行干了几件事的代码不可学习,增加阅读障碍!
182183 length = (table = resize()). length;
183184 }
184- // 计算索引 - 计算索引是个重点!!!!
185+ // 计算索引 : 计算索引是个重点!!!!
185186 int index = indexFor(hash, length);
186187 // 如果当前位置没有使用过,直接创建 Node 存储
187188 if (table[index] == null ) {
@@ -210,17 +211,18 @@ HashMap 的数据存储过程主要体现在 putVal 函数当中,但此部分
210211 private V putOnHashCollision(Node<K , V > [] table, Node<K , V > curNode,
211212 int hashOfKey, K key, V value) {
212213 Node<K , V > linkedNode;
214+ // 比较内存地址
213215 if (isEqualsWith(curNode, key, hashOfKey)) {
214216 // key 相同则直接覆盖
215217 linkedNode = curNode;
216218 } else if (curNode instanceof TreeNode ) {
217- // todo 红黑数的操作,暂时忽略
219+ // todo: 红黑数的操作,暂时忽略……
218220 linkedNode = new TreeNode<> (hashOfKey, key, value, null );
219221 } else {
220222 // key 不同 在链表中寻找目标
221223 for (int binCount = 0 ; ; binCount++ ) {
222224 linkedNode = curNode. next;
223- // 遍历到了尾节点,直接插入 [尾插法]
225+ // 遍历到了尾节点,直接插入 [尾插法](JDK8在这里做了改动,JDK7还是头插法:造成并发死循环的根本原因)
224226 if (linkedNode == null ) {
225227 curNode. next = newNode(hashOfKey, key, value, null );
226228 break ;
@@ -246,7 +248,7 @@ HashMap 的数据存储过程主要体现在 putVal 函数当中,但此部分
246248## 小结:
247249
2482501 . JDK 8 中的 HashMap 是在第一次 put 元素的时候才初始化的,初始化的具体逻辑在 resize 函数中
249- 2 . JDK 8 发生冲突的时候使用尾插法插入新数据,而 JDK 7 使用的是头插入法。使用头插法的原因是不想遍历链表,但是头插法会改变节点原始顺序,在多线程中会造成链表有环的问题。
251+ 2 . == JDK 8 发生冲突的时候使用尾插法插入新数据,而 JDK 7 使用的是头插入法。使用头插法的原因是不想遍历链表,但是头插法会改变节点原始顺序,在多线程中会造成链表有环的问题。==
2502523 . 更新数据的时候 modeCount 并不会累加
2512534 . 在 hash 冲突的时候会使用 key 的 equls 和 hashCode 方法,==hasCode 方法用于确定数组中的索引位置,而 equls 用于比较 key 是否是同一个==。可见这两个方法很重要,所以如果我们复写了 equls 和 hashCode 其中的任何一个,都要复写另外一个。这样才能确定对象的唯一性,保证使用 Hash 算法集合的正确性。
2522545 . 计算数组索引的函数极其重要,只有好的函数才能将元素均匀的分配在数组中,充分利用存储空间,较少 hash 碰撞。
@@ -271,9 +273,9 @@ static int indexFor(int h, int length) { //jdk1.7的源码,jdk1.8没有这个
271273
272274
273275
274- 方法一的目的是保证 HashMap 容量很小的时候 hashCode 的高位也能参与运算,较少 hash 冲突。
276+ == 方法一的目的是保证 HashMap 容量很小的时候 hashCode 的高位也能参与运算,较少 hash 冲突。==
275277
276- 方法二的目的是加快取模运算,但是 % 运算的效率很低,所以可以使用等价的位运算:
278+ == 方法二的目的是加快取模运算,但是 % 运算的效率很低,所以可以使用等价的位运算:==
277279
278280> 当且仅当 $$ length = 2^n $$ 此公式成立:$$ hashCode \% length = h \& (length - 1) $$ ==这也就是为啥 HashMap 的容量必须为$ 2^n $==
279281
0 commit comments