2.HashMap在Java1.7與1.8中的區別
同系列文章:(1)美團面試題:Hashmap的結構,1.7和1.8有哪些區別,史上最深入的分析
1.Java源碼分析:HashMap 1.8 相對於1.7 到底更新了什么?(轉載)
=======
2.HashMap在Java1.7與1.8中的區別
基於JDK1.7.0_80與JDK1.8.0_66做的分析
JDK1.7中
使用一個Entry數組來存儲數據,用key的hashcode取模來決定key會被放到數組里的位置,如果hashcode相同,或者hashcode取模后的結果相同(hash collision),那么這些key會被定位到Entry數組的同一個格子里,這些key會形成一個鏈表。
在hashcode特別差的情況下,比方說所有key的hashcode都相同,這個鏈表可能會很長,那么put/get操作都可能需要遍歷這個鏈表
也就是說時間復雜度在最差情況下會退化到O(n)
JDK1.8中
使用一個Node數組來存儲數據,但這個Node可能是鏈表結構,也可能是紅黑樹結構
如果插入的key的hashcode相同,那么這些key也會被定位到Node數組的同一個格子里。
如果同一個格子里的key不超過8個,使用鏈表結構存儲。
如果超過了8個,那么會調用treeifyBin函數,將鏈表轉換為紅黑樹。
那么即使hashcode完全相同,由於紅黑樹的特點,查找某個特定元素,也只需要O(log n)的開銷
也就是說put/get的操作的時間復雜度最差只有O(log n)
聽起來挺不錯,但是真正想要利用JDK1.8的好處,有一個限制:
key的對象,必須正確的實現了Compare接口
如果沒有實現Compare接口,或者實現得不正確(比方說所有Compare方法都返回0)
那JDK1.8的HashMap其實還是慢於JDK1.7的
簡單的測試數據如下:
向HashMap中put/get 1w條hashcode相同的對象
JDK1.7: put 0.26s,get 0.55s
JDK1.8(未實現Compare接口):put 0.92s,get 2.1s
但是如果正確的實現了Compare接口,那么JDK1.8中的HashMap的性能有巨大提升,這次put/get 100W條hashcode相同的對象
JDK1.8(正確實現Compare接口,):put/get大概開銷都在320ms左右
為什么要這么操作呢?
我認為應該是為了避免Hash Collision DoS攻擊
Java中String的hashcode函數的強度很弱,有心人可以很容易的構造出大量hashcode相同的String對象。
如果向服務器一次提交數萬個hashcode相同的字符串參數,那么可以很容易的卡死JDK1.7版本的服務器。
但是String正確的實現了Compare接口,因此在JDK1.8版本的服務器上,Hash Collision DoS不會造成不可承受的開銷。
參考資料:
jdk1.7.0_80的HashMap源碼
jdk1.8.0_66的HashMap源碼
1.Java源碼分析:HashMap 1.8 相對於1.7 到底更新了什么?(轉載)
前言
HashMap
在Java
和Android
開發中非常常見- 而
HashMap 1.8
相對於HashMap 1.7
更新多 - 今天,我將通過源碼分析
HashMap 1.8
,從而講解HashMap 1.8
相對於HashMap 1.7
的更新內容,希望你們會喜歡。
- 本文基於版本
JDK 1.8
,即Java 8
- 關於版本
JDK 1.7
,即Java 7
,具體請看文章Java:手把手帶你源碼分析 HashMap 1.7
目錄

1. 簡介
- 類定義
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
- 主要簡介

HashMap
的實現在JDK 1.7
和JDK 1.8
差別較大- 今天,我將對照
JDK 1.7
的源碼,在此基礎上講解JDK 1.8
中HashMap
的源碼解析
請務必打開
JDK 1.7
對照看:Java:手把手帶你源碼分析 HashMap 1.7
2. 數據結構:引入了 紅黑樹
2.1 主要介紹

關於 紅黑樹 的簡介

更加具體的了解,請:點擊閱讀文章
2.2 存儲流程
注:為了讓大家有個感性的認識,只是簡單的畫出存儲流程,更加詳細 & 具體的存儲流程會在下面源碼分析中給出

2.3 數組元素 & 鏈表節點的 實現類
HashMap
中的數組元素 & 鏈表節點 采用Node
類 實現
與
JDK 1.7
的對比(Entry
類),僅僅只是換了名字
- 該類的源碼分析如下
具體分析請看注釋
/** * Node = HashMap的內部類,實現了Map.Entry接口,本質是 = 一個映射(鍵值對) * 實現了getKey()、getValue()、equals(Object o)和hashCode()等方法 **/ static class Node<K,V> implements Map.Entry<K,V> { final int hash; // 哈希值,HashMap根據該值確定記錄的位置 final K key; // key V value; // value Node<K,V> next;// 鏈表下一個節點 // 構造方法 Node(int hash, K key, V value, Node<K,V> next) { this.hash = hash; this.key = key; this.value = value; this.next = next; } public final K getKey() { return key; } // 返回 與 此項 對應的鍵 public final V getValue() { return value; } // 返回 與 此項 對應的值 public final String toString() { return key + "=" + value; } public final V setValue(V newValue) { V oldValue = value; value = newValue; return oldValue; } /** * hashCode() */ public final int hashCode() { return Objects.hashCode(key) ^ Objects.hashCode(value); } /** * equals() * 作用:判斷2個Entry是否相等,必須key和value都相等,才返回true */ public final boolean equals(Object o) { if (o == this) return true; if (o instanceof Map.Entry) { Map.Entry<?,?> e = (Map.Entry<?,?>)o; if (Objects.equals(key, e.getKey()) && Objects.equals(value, e.getValue())) return true; } return false; } }
2.4 紅黑樹節點 實現類
HashMap
中的紅黑樹節點 采用TreeNode
類 實現
/** * 紅黑樹節點 實現類:繼承自LinkedHashMap.Entry<K,V>類 */ static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> { // 屬性 = 父節點、左子樹、右子樹、刪除輔助節點 + 顏色 TreeNode<K,V> parent; TreeNode<K,V> left; TreeNode<K,V> right; TreeNode<K,V> prev; boolean red; // 構造函數 TreeNode(int hash, K key, V val, Node<K,V> next) { super(hash, key, val, next); } // 返回當前節點的根節點 final TreeNode<K,V> root() { for (TreeNode<K,V> r = this, p;;) { if ((p = r.parent) == null) return r; r = p; } }
3. 具體使用
3.1 主要使用API(方法、函數)
與
JDK 1.7
基本相同
V get(Object key); // 獲得指定鍵的值 V put(K key, V value); // 添加鍵值對 void putAll(Map<? extends K, ? extends V> m); // 將指定Map中的鍵值對 復制到 此Map中 V remove(Object key); // 刪除該鍵值對 boolean containsKey(Object key); // 判斷是否存在該鍵的鍵值對;是 則返回true boolean containsValue(Object value); // 判斷是否存在該值的鍵值對;是 則返回true Set<K> keySet(); // 單獨抽取key序列,將所有key生成一個Set Collection<V> values(); // 單獨value序列,將所有value生成一個Collection void clear(); // 清除哈希表中的所有鍵值對 int size(); // 返回哈希表中所有 鍵值對的數量 = 數組中的鍵值對 + 鏈表中的鍵值對 boolean isEmpty(); // 判斷HashMap是否為空;size == 0時 表示為 空
3.2 使用流程
與
JDK 1.7
基本相同
- 在具體使用時,主要流程是:
- 聲明1個
HashMap
的對象 - 向
HashMap
添加數據(成對 放入 鍵 - 值對) - 獲取
HashMap
的某個數據 - 獲取
HashMap
的全部數據:遍歷HashMap
- 示例代碼
import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; public class HashMapTest { public static void main(String[] args) { /** * 1. 聲明1個 HashMap的對象 */ Map<String, Integer> map = new HashMap<String, Integer>(); /** * 2. 向HashMap添加數據(成對 放入 鍵 - 值對) */ map.put("Android", 1); map.put("Java", 2); map.put("iOS", 3); map.put("數據挖掘", 4); map.put("產品經理", 5); /** * 3. 獲取 HashMap 的某個數據 */ System.out.println("key = 產品經理時的值為:" + map.get("產品經理")); /** * 4. 獲取 HashMap 的全部數據:遍歷HashMap * 核心思想: * 步驟1:獲得key-value對(Entry) 或 key 或 value的Set集合 * 步驟2:遍歷上述Set集合(使用for循環 、 迭代器(Iterator)均可) * 方法共有3種:分別針對 key-value對(Entry) 或 key 或 value */ // 方法1:獲得key-value的Set集合 再遍歷 System.out.println("方法1"); // 1. 獲得key-value對(Entry)的Set集合 Set<Map.Entry<String, Integer>> entrySet = map.entrySet(); // 2. 遍歷Set集合,從而獲取key-value // 2.1 通過for循環 for(Map.Entry<String, Integer> entry : entrySet){ System.out.print(entry.getKey()); System.out.println(entry.getValue()); } System.out.println("----------"); // 2.2 通過迭代器:先獲得key-value對(Entry)的Iterator,再循環遍歷 Iterator iter1 = entrySet.iterator(); while (iter1.hasNext()) { // 遍歷時,需先獲取entry,再分別獲取key、value Map.Entry entry = (Map.Entry) iter1.next(); System.out.print((String) entry.getKey()); System.out.println((Integer) entry.getValue()); } // 方法2:獲得key的Set集合 再遍歷 System.out.println("方法2"); // 1. 獲得key的Set集合 Set<String> keySet = map.keySet(); // 2. 遍歷Set集合,從而獲取key,再獲取value // 2.1 通過for循環 for(String key : keySet){ System.out.print(key); System.out.println(map