HashMap详解

概述

HashMap主要有以下特点,注:由于HashMap的实现机制过于复杂,内部由哈希数组+链表+红黑树实现,并且其中涉及到Hash算法、哈希数组扩容机制、负载因子、红黑树等等一系列较为复杂的问题,具体可以去看美团网技术团队的关于HashMap的文章。在我自己实现的HashMap中,我把许多东西难度都降低了,我主要想把HashMap的实现机制说清楚,而具体怎么实现的,我自己许多东西不会,以后再来写清楚吧。

  • HashMap内部是哈希数组+链表+红黑树实现。
  • HashMap遍历无序,但其有很快的访问速度。
  • HashMap中key不能有重复元素,可以有一个null;value可以有重复元素多个null对象。
  • HashMap是线程不安全的。

详解

实现原理

HashMap的实现如下图:

HashMap是使用哈希表存放元素,使用了链地址法(数组+链表)解决哈希冲突。JDK1.8后当链表长度大于8时使用了红黑树解决哈希冲突。

首先通过key的hashCode()方法找到key的哈希值,通过Hash算法算出该key存放的位置(哈希数组的下标),在这里可能发生哈希冲突,然后插入到链表或红黑树中存放。

HashMap解决哈希冲突主要有两点:1.好的Hash算法使key-value对均匀分布在哈希数组中。2.key-value对过多时,扩充哈希数组的容量。

具体实现

一些字段

1
2
3
4
transient Node<K,V>[] table;  // 哈希数组
transient int size; // HashMap存放元素个数
int threshold; // key-value对的最大容量(哈希数组长度Capacity*负载因子loadFactor)
final float loadFactor; // 负载因子

这些字段的说明:

  • threshold表示该哈希数组存放的最大存放key-value对数,threshold = loadFactory(负载因子) * Capatity(哈希数组长度)。
  • loadFactoy默认0.75,Capatity默认16。在每次存放key-value对时检查threshold,若其不足则需要扩容(哈希数组长度)。

Node[] table是哈希桶数组,Node实现了Map.Entry接口(Map.Entry就是键值对)的链表。

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
static class Node implements Map.Entry {
final int hash; // 经过hash运算后得出的数组下标
final K key;
V value;
Node next;
Node(int hash, K key, V value, Node 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;
}
}

一些方法,注:扩容方法,我不太懂就不写了,源码见HashMap中的resize()方法。

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
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
public V put(K key, V value){
// 对key的hashCode()做hash
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;
// 步骤①:tab为空则创建
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 步骤②:查看该链表或者红黑树中是否有元素
if ((p = tab[i = (n - 1) & hash]) == null)
// 没有元素就创建头元素
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
// 步骤③:判断table[i]中首个元素是否是该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) {
// 步骤⑤:判断链表长度是否大于8,若大于转换成红黑树处理
p.next = newNode(hash, key, value, null);
// 转换成红黑树处理
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
// 步骤⑥:插入成功,判断是否超过最大容量
if (++size > threshold){
// 扩容
resize();
}
afterNodeInsertion(evict);
return null;
}

实现

自己模拟写的HashMap

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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
public class HashMap<K, V> extends AbstractMap<K, V> implements Map<K, V>{
private static final int DEFAULT_CAPACITY = 16; // 哈希数组默认大小
private int size; // HashMap存放元素个数
private Node[] table; // 哈希数组

HashMap(){
table = new Node[DEFAULT_CAPACITY];
}

@Override
public V put(K key, V value) {
// 找到该key对应的哈希数组下标
int index = hash(key);
// 该链表没有元素
if(table[index] == null){
table[index] = new Node<>(index, key, value, null);
size++;
return null;
}
@SuppressWarnings("unchecked")
Node<K, V> node = table[index];
Node<K, V> prev = node;
// 遍历该链表
while(node != null){
// 找到key,则替换value并返回原value
if(Objects.equals(node.getKey(),key)){
V oldValue = node.getValue();
node.setValue(value);
return oldValue;
}
prev = node;
node = node.next;
}
// 未找到,则加入链表
prev.next = new Node<>(index, key, value, null);
size++;
return null;
}

@Override
public V get(Object key) {
// 找到该key对应的哈希数组下标
int index = hash(key);
// 该链表没有元素
if(table[index] == null){
return null;
}
@SuppressWarnings("unchecked")
Node<K, V> node = table[index];
// 遍历该链表
do {
if(Objects.equals(node.getKey(),key)){
return node.getValue();
}
node = node.next;
} while (node != null);
return null;
}

@Override
public V remove(Object key) {
// 找到该key对应的哈希数组下标
int index = hash(key);
@SuppressWarnings("unchecked")
Node<K, V> node = table[index];
// 该链表没有元素
if(node == null){
return null;
}
// 该链表只有一个元素
if(node.next == null){
if(Objects.equals(node.getKey(),key)){
V oldValue = node.getValue();
table[index] = null;
size--;
return oldValue;
}
return null;
}
// 遍历该链表
Node<K, V> prev = node;
node = node.next;
while(node != null){
if(Objects.equals(node.getKey(),key)){
prev.next = node.next;
size--;
return node.getValue();
}
node = node.next;
prev = prev.next;
}
return null;
}

@Override
@SuppressWarnings("unchecked")
public Set<Entry<K, V>> entrySet() {
Set<Entry<K, V>> set = new HashSet<>();
for(Node<K, V> node: table ){
while(node != null){
set.add(node);
node = node.next;
}
}
return set;
}

@Override
public int size() {
return size;
}

/**
* 计算key的哈希值,然后Hash算法(取模)求出该key对应的数组下标
*/
public int hash(Object key){
return Objects.hashCode(key) % table.length;
}

static class Node<K, V> implements Map.Entry<K, V>{
final int hash; // 经过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;
}

@Override
public K getKey() {
return this.key;
}

@Override
public V getValue() {
return this.value;
}

@Override
public V setValue(V value) {
V oldValue = this.value;
this.value = value;
return oldValue;
}

@Override
public boolean equals(Object obj) {
if(obj == this){
return true;
}
if(obj instanceof Map.Entry<?, ?>){
Map.Entry<?, ?> entry = (Map.Entry<?, ?>) obj;
return Objects.equals(entry.getKey(),this.key) && Objects.equals(entry.getValue(),this.value);
}
return false;
}

@Override
public int hashCode() {
return Objects.hashCode(this.key) ^ Objects.hashCode(this.value);
}

@Override
public String toString() {
return this.key + "=" + this.value;
}
}
}

Reference

美团点评技术团队:http://tech.meituan.com/java-hashmap.html