Java源码分析之HashMap(JDK1.8)
一、HashMap概述
HashMap是常用的Java集合之一,是基于哈希表的Map接口的實現。與HashTable主要區別為不支持同步和允許null作為key和value。由于HashMap不是線程安全的,如果想要線程安全,可以使用ConcurrentHashMap代替。
二、HashMap數據結構
HashMap的底層是哈希數組,數組元素為Entry。HashMap通過key的hashCode來計算hash值,當hashCode相同時,通過“拉鏈法”解決沖突,如下圖所示。
相比于之前的版本,jdk1.8在解決哈希沖突時有了較大的變化,當鏈表長度大于閾值(默認為8)時,將鏈表轉化為紅黑樹,以減少搜索時間。原本Map.Entry接口的實現類Entry改名為了Node。轉化為紅黑樹時改用另一種實現TreeNode。?
Node類
static class Node<K,V> implements Map.Entry<K,V> {
? ? ? ? final int hash; // 哈希值
? ? ? ? final K key;
? ? ? ? V 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 int hashCode() {
? ? ? ? ? ? return Objects.hashCode(key) ^ Objects.hashCode(value);
? ? ? ? }
? ? ? ? public final V setValue(V newValue) {
? ? ? ? ? ? V oldValue = value;
? ? ? ? ? ? value = newValue;
? ? ? ? ? ? return oldValue;
? ? ? ? }
? ? ? ? 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;
? ? ? ? }
? ? }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
TreeNode類
? ? static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
? ? ? ? TreeNode<K,V> parent; ?// red-black tree links
? ? ? ? TreeNode<K,V> left;
? ? ? ? TreeNode<K,V> right;
? ? ? ? TreeNode<K,V> prev; ? ?// needed to unlink next upon deletion
? ? ? ? boolean red;
? ? ? ? TreeNode(int hash, K key, V val, Node<K,V> next) {
? ? ? ? ? ? super(hash, key, val, next);
? ? ? ? }
? ? }
1
2
3
4
5
6
7
8
9
10
HashMap就是這樣一個Entry(包括Node和TreeNode)數組,Node對象中包含鍵、值和hash值,next指向下一個Entry,用來處理哈希沖突。TreeNode對象包含指向父節點、子節點和前一個節點(移除對象時使用)的指針,以及表示紅黑節點的boolean標識。
三、HashMap源碼分析
1. 主要屬性
? ? transient Node<K,V>[] table; // 哈希數組
? ? transient Set<Map.Entry<K,V>> entrySet; // entry緩存Set
? ? transient int size; // 元素個數
? ? transient int modCount; // 修改次數
? ? int threshold; // 閾值,等于加載因子*容量,當實際大小超過閾值則進行擴容
? ? final float loadFactor; // 加載因子,默認值為0.75
1
2
3
4
5
6
7
8
9
10
11
2. 構造方法
以下是HashMap的幾個構造方法。
? ? /**
? ? ?* 根據初始化容量和加載因子構建一個空的HashMap.
? ? ?*/
? ? public HashMap(int initialCapacity, float loadFactor) {
? ? ? ? if (initialCapacity < 0)
? ? ? ? ? ? throw new IllegalArgumentException("Illegal initial capacity: " +
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?initialCapacity);
? ? ? ? if (initialCapacity > MAXIMUM_CAPACITY)
? ? ? ? ? ? initialCapacity = MAXIMUM_CAPACITY;
? ? ? ? if (loadFactor <= 0 || Float.isNaN(loadFactor))
? ? ? ? ? ? throw new IllegalArgumentException("Illegal load factor: " +
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?loadFactor);
? ? ? ? this.loadFactor = loadFactor;
? ? ? ? this.threshold = tableSizeFor(initialCapacity);
? ? }
? ? /**
? ? ?* 使用初始化容量和默認加載因子(0.75).
? ? ?*/
? ? public HashMap(int initialCapacity) {
? ? ? ? this(initialCapacity, DEFAULT_LOAD_FACTOR);
? ? }
? ? /**
? ? ?* 使用默認初始化大小(16)和默認加載因子(0.75).
? ? ?*/
? ? public HashMap() {
? ? ? ? this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
? ? }
? ? /**
? ? ?* 用已有的Map構造一個新的HashMap.
? ? ?*/
? ? public HashMap(Map<? extends K, ? extends V> m) {
? ? ? ? this.loadFactor = DEFAULT_LOAD_FACTOR;
? ? ? ? putMapEntries(m, false);
? ? }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
3. 數據存取
putAll方法
? ? public void putAll(Map<? extends K, ? extends V> m) {
? ? ? ? putMapEntries(m, true);
? ? }
? ? /**
? ? ?* Implements Map.putAll and Map constructor
? ? ?*
? ? ?* @param m the map
? ? ?* @param evict false when initially constructing this map, else
? ? ?* true (relayed to method afterNodeInsertion).
? ? ?*/
? ? final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
? ? ? ? int s = m.size();
? ? ? ? if (s > 0) {
? ? ? ? ? ? if (table == null) { // pre-size
? ? ? ? ? ? ? ? float ft = ((float)s / loadFactor) + 1.0F;
? ? ? ? ? ? ? ? int t = ((ft < (float)MAXIMUM_CAPACITY) ?
? ? ? ? ? ? ? ? ? ? ? ? ?(int)ft : MAXIMUM_CAPACITY);
? ? ? ? ? ? ? ? if (t > threshold)
? ? ? ? ? ? ? ? ? ? threshold = tableSizeFor(t);
? ? ? ? ? ? }
? ? ? ? ? ? else if (s > threshold)
? ? ? ? ? ? ? ? resize();
? ? ? ? ? ? for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
? ? ? ? ? ? ? ? K key = e.getKey();
? ? ? ? ? ? ? ? V value = e.getValue();
? ? ? ? ? ? ? ? putVal(hash(key), key, value, false, evict); // put核心方法
? ? ? ? ? ? }
? ? ? ? }
? ? }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
put方法
? ? public V put(K key, V value) {
? ? ? ? return putVal(hash(key), key, value, false, true);
? ? }
? ? final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
? ? ? ? ? ? ? ? ? ?boolean evict) {
? ? ? ? Node<K,V>[] tab; Node<K,V> p; int n, i;
? ? ? ? if ((tab = table) == null || (n = tab.length) == 0) // table為空或length為0
? ? ? ? ? ? n = (tab = resize()).length; // 初始化
? ? ? ? if ((p = tab[i = (n - 1) & hash]) == null) // 如果hash所在位置為null,直接put
? ? ? ? ? ? tab[i] = newNode(hash, key, value, null);
? ? ? ? else { // tab[i]有元素,遍歷節點后添加
? ? ? ? ? ? Node<K,V> e; K k;
? ? ? ? ? ? // 如果hash、key都相等,直接覆蓋
? ? ? ? ? ? if (p.hash == hash &&
? ? ? ? ? ? ? ? ((k = p.key) == key || (key != null && key.equals(k))))
? ? ? ? ? ? ? ? e = p;
? ? ? ? ? ? else if (p instanceof TreeNode) // 紅黑樹添加節點
? ? ? ? ? ? ? ? e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
? ? ? ? ? ? else { // 鏈表
? ? ? ? ? ? ? ? for (int binCount = 0; ; ++binCount) {
? ? ? ? ? ? ? ? ? ? if ((e = p.next) == null) { // 找到鏈表最后一個節點,插入新節點
? ? ? ? ? ? ? ? ? ? ? ? p.next = newNode(hash, key, value, null);
? ? ? ? ? ? ? ? ? ? ? ? // 鏈表節點大于閾值8,調用treeifyBin方法,當tab.length大于64將鏈表改為紅黑樹
? ? ? ? ? ? ? ? ? ? ? ? // 如果tab.length < 64或tab為null,則調用resize方法重構鏈表.
? ? ? ? ? ? ? ? ? ? ? ? if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
? ? ? ? ? ? ? ? ? ? ? ? ? ? treeifyBin(tab, hash);
? ? ? ? ? ? ? ? ? ? ? ? break;
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? // hash、key都相等,此時節點即要更新節點
? ? ? ? ? ? ? ? ? ? if (e.hash == hash &&
? ? ? ? ? ? ? ? ? ? ? ? ((k = e.key) == key || (key != null && key.equals(k))))
? ? ? ? ? ? ? ? ? ? ? ? break;
? ? ? ? ? ? ? ? ? ? p = e;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? ? ? // 當前節點e = p.next不為null,表示鏈表中原本存在相同的key,則返回oldValue
? ? ? ? ? ? if (e != null) { // existing mapping for key
? ? ? ? ? ? ? ? V oldValue = e.value;
? ? ? ? ? ? ? ? // onlyIfAbsent值為false,參數主要決定存在相同key時是否執行替換
? ? ? ? ? ? ? ? if (!onlyIfAbsent || oldValue == null)
? ? ? ? ? ? ? ? ? ? e.value = value;
? ? ? ? ? ? ? ? afterNodeAccess(e);
? ? ? ? ? ? ? ? return oldValue;
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? ++modCount;
? ? ? ? if (++size > threshold) // 檢查是否超過閾值
? ? ? ? ? ? resize();
? ? ? ? afterNodeInsertion(evict);
? ? ? ? return null; // 原HashMap中不存在相同的key,插入鍵值對后返回null
? ? }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
get方法
? ? public V get(Object key) {
? ? ? ? Node<K,V> e;
? ? ? ? return (e = getNode(hash(key), key)) == null ? null : e.value;
? ? }
? ? /**
? ? ?* Implements Map.get and related methods
? ? ?*
? ? ?* @param hash hash for key
? ? ?* @param key the key
? ? ?* @return the node, or null if none
? ? ?*/
? ? final Node<K,V> getNode(int hash, Object key) {
? ? ? ? Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
? ? ? ? if ((tab = table) != null && (n = tab.length) > 0 &&
? ? ? ? ? ? (first = tab[(n - 1) & hash]) != null) {
? ? ? ? ? ? if (first.hash == hash && // always check first node
? ? ? ? ? ? ? ? ((k = first.key) == key || (key != null && key.equals(k))))
? ? ? ? ? ? ? ? return first;
? ? ? ? ? ? if ((e = first.next) != null) {
? ? ? ? ? ? ? ? if (first instanceof TreeNode) // 紅黑樹
? ? ? ? ? ? ? ? ? ? return ((TreeNode<K,V>)first).getTreeNode(hash, key);
? ? ? ? ? ? ? ? // 鏈表
? ? ? ? ? ? ? ? do {
? ? ? ? ? ? ? ? ? ? if (e.hash == hash &&
? ? ? ? ? ? ? ? ? ? ? ? ((k = e.key) == key || (key != null && key.equals(k))))
? ? ? ? ? ? ? ? ? ? ? ? return e;
? ? ? ? ? ? ? ? } while ((e = e.next) != null);
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? return null;
? ? }
? ? // 遍歷紅黑樹搜索節點
? ? /**
? ? ?* Calls find for root node.
? ? ?*/
? ? final TreeNode<K,V> getTreeNode(int h, Object k) {
? ? ? ? return ((parent != null) ? root() : this).find(h, k, null);
? ? }
? ? /**
? ? ?* Returns root of tree containing this node.
? ? ?*/
? ? final TreeNode<K,V> root() {
? ? ? ? for (TreeNode<K,V> r = this, p;;) {
? ? ? ? ? ? if ((p = r.parent) == null)
? ? ? ? ? ? ? ? return r;
? ? ? ? ? ? r = p;
? ? ? ? }
? ? }
? ? /**
? ? ?* Finds the node starting at root p with the given hash and key.
? ? ?* The kc argument caches comparableClassFor(key) upon first use
? ? ?* comparing keys.
? ? ?*/
? ? final TreeNode<K,V> find(int h, Object k, Class<?> kc) {
? ? ? ? TreeNode<K,V> p = this;
? ? ? ? do {
? ? ? ? ? ? int ph, dir; K pk;
? ? ? ? ? ? TreeNode<K,V> pl = p.left, pr = p.right, q;
? ? ? ? ? ? if ((ph = p.hash) > h) // 當前節點hash大
? ? ? ? ? ? ? ? p = pl; // 查左子樹
? ? ? ? ? ? else if (ph < h) // 當前節點hash小
? ? ? ? ? ? ? ? p = pr; // 查右子樹
? ? ? ? ? ? else if ((pk = p.key) == k || (k != null && k.equals(pk)))
? ? ? ? ? ? ? ? return p; // hash、key都相等,即找到,返回當前節點
? ? ? ? ? ? else if (pl == null) // hash相等,key不等,左子樹為null,查右子樹
? ? ? ? ? ? ? ? p = pr;
? ? ? ? ? ? else if (pr == null)
? ? ? ? ? ? ? ? p = pl;
? ? ? ? ? ? else if ((kc != null ||
? ? ? ? ? ? ? ? ? ? ? (kc = comparableClassFor(k)) != null) &&
? ? ? ? ? ? ? ? ? ? ?(dir = compareComparables(kc, k, pk)) != 0)
? ? ? ? ? ? ? ? p = (dir < 0) ? pl : pr;
? ? ? ? ? ? else if ((q = pr.find(h, k, kc)) != null)
? ? ? ? ? ? ? ? return q;
? ? ? ? ? ? else
? ? ? ? ? ? ? ? p = pl;
? ? ? ? } while (p != null);
? ? ? ? return null;
? ? }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
remove方法
? ? public V remove(Object key) {
? ? ? ? Node<K,V> e;
? ? ? ? return (e = removeNode(hash(key), key, null, false, true)) == null ?
? ? ? ? ? ? null : e.value;
? ? }
? ? /**
? ? ?* Implements Map.remove and related methods
? ? ?*
? ? ?* @param hash hash for key
? ? ?* @param key the key
? ? ?* @param value the value to match if matchValue, else ignored
? ? ?* @param matchValue if true only remove if value is equal
? ? ?* @param movable if false do not move other nodes while removing
? ? ?* @return the node, or null if none
? ? ?*/
? ? final Node<K,V> removeNode(int hash, Object key, Object value,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?boolean matchValue, boolean movable) {
? ? ? ? Node<K,V>[] tab; Node<K,V> p; int n, index;
? ? ? ? if ((tab = table) != null && (n = tab.length) > 0 &&
? ? ? ? ? ? (p = tab[index = (n - 1) & hash]) != null) {
? ? ? ? ? ? Node<K,V> node = null, e; K k; V v;
? ? ? ? ? ? // 直接命中
? ? ? ? ? ? if (p.hash == hash &&
? ? ? ? ? ? ? ? ((k = p.key) == key || (key != null && key.equals(k))))
? ? ? ? ? ? ? ? node = p;
? ? ? ? ? ? else if ((e = p.next) != null) {
? ? ? ? ? ? ? ? if (p instanceof TreeNode) // 在紅黑樹中查找
? ? ? ? ? ? ? ? ? ? node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
? ? ? ? ? ? ? ? else { // 在鏈表中查找
? ? ? ? ? ? ? ? ? ? do {
? ? ? ? ? ? ? ? ? ? ? ? if (e.hash == hash &&
? ? ? ? ? ? ? ? ? ? ? ? ? ? ((k = e.key) == key ||
? ? ? ? ? ? ? ? ? ? ? ? ? ? ?(key != null && key.equals(k)))) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? node = e;
? ? ? ? ? ? ? ? ? ? ? ? ? ? break;
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? p = e;
? ? ? ? ? ? ? ? ? ? } while ((e = e.next) != null);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? ? ? // 命中后刪除
? ? ? ? ? ? if (node != null && (!matchValue || (v = node.value) == value ||
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?(value != null && value.equals(v)))) {
? ? ? ? ? ? ? ? if (node instanceof TreeNode) // 在紅黑樹中刪除節點
? ? ? ? ? ? ? ? ? ? ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
? ? ? ? ? ? ? ? else if (node == p) // 鏈表首節點刪除
? ? ? ? ? ? ? ? ? ? tab[index] = node.next;
? ? ? ? ? ? ? ? else // 多節點鏈表刪除
? ? ? ? ? ? ? ? ? ? p.next = node.next;
? ? ? ? ? ? ? ? ++modCount;
? ? ? ? ? ? ? ? --size;
? ? ? ? ? ? ? ? afterNodeRemoval(node);
? ? ? ? ? ? ? ? return node;
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? return null;
? ? }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
clear方法
? ? /**
? ? ?* Removes all of the mappings from this map.
? ? ?* The map will be empty after this call returns.
? ? ?*/
? ? public void clear() {
? ? ? ? Node<K,V>[] tab;
? ? ? ? modCount++;
? ? ? ? if ((tab = table) != null && size > 0) {
? ? ? ? ? ? size = 0;
? ? ? ? ? ? for (int i = 0; i < tab.length; ++i)
? ? ? ? ? ? ? ? tab[i] = null; // 把哈希數組中所有位置都賦為null
? ? ? ? }
? ? }
1
2
3
4
5
6
7
8
9
10
11
12
13
四、總結
本文從源碼入手,簡單地分析了HashMap底層的結構和實現。在源碼分析部分主要分析了常用的幾個方法,還有一些方法比如調整哈希表大小的resize、將鏈表轉化為紅黑樹的treeify以及逆操作untreeify等,在此不再詳細分析。紅黑樹部分的代碼只理解了大概,實現細節上還有待進一步閱讀分析。?
---------------------?
作者:逝水寒緣?
來源:CSDN?
原文:https://blog.csdn.net/u014026363/article/details/56342142?
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!
總結
以上是生活随笔為你收集整理的Java源码分析之HashMap(JDK1.8)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 面试必备:LinkedHashMap源码
- 下一篇: 面试必备:ArrayList源码解析(J