前言:

现在面试时,面试官经常会问到HashMap,简单点就会问下HashMap的一些关键知识点,困难些的可能会当场让你手写一个HashMap,考察下你对HashMap底层原理的了解深度;所以,今天特别手写了一个简单的HashMap,只实现了 put、get、containsKey、keySet 方法的 HashMap,来帮助我们理解HashMap的底层设计原理。
本文参考:手写实现一个HashMap

手撕HashMap:

1. 首先定义接口:

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
import java.util.Set;

/**
*@Title: MyMap
* @Description: 自定义map接口
* @date: 2019年7月13日 下午3:56:57
*/
public interface MyMap<K,V> {

/**
* @Description: 插入键值对方法
* @param k
* @param v
* @return
*@date: 2019年7月13日 下午3:59:16
*/
public V put(K k,V v);
/**
* @Description:根据key获取value
* @param k
* @return
*@date: 2019年7月13日 下午3:59:40
*/
public V get(K k);

/**
* @Description: 判断key键是否存在
* @param k key键
* @return
*@date: 2019年7月23日 下午4:07:22
*/
public boolean containsKey(K k);


/**
* @Description: 获取map集合中所有的key,并放入set集合中
* @return
*@date: 2019年7月23日 下午4:24:19
*/
public Set<K> keySet();





//------------------------------内部接口 Entry(存放key-value)---------------------

/**
* @Title: Enter
* @Description: 定义内部接口 Entry,存放键值对的Entery接口
* @date: 2019年7月13日 下午4:00:33
*/
interface Entry<K,V>{
/**
* @Description: 获取key方法
* @return
*@date: 2019年7月13日 下午4:02:06
*/
public K getKey();
/**
* @Description:获取value方法
* @return
*@date: 2019年7月13日 下午4:02:10
*/
public V getValue();
}

}

2. 接口实现类:

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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;


/**
*@Title: MyHashMap
* @Description: MyMap接口的实现类
* @date: 2019年7月13日 下午4:04:56
*/
@SuppressWarnings(value={"unchecked","rawtypes","hiding"})
public class MyHashMap<K, V> implements MyMap<K, V>{

/**
* Entry数组的默认初始化长度为16;通过位移运算向左移动四位,得到二进制码 "00010000",转换为十进制是16
*/
private static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
/**
* 负载因子默认为0.75f;负载因子是用来标志当使用容量占总容量的75%时,就需要扩充容量了,
* 扩充Entry数组的长度为原来的两倍,并且重新对所存储的key-value键值对进行散列。
*/
private static final float DEFAULT_LOAD_FACTOR = 0.75f;

/**
* 可设置的初始容量
*/
private int defaultInitSize;
/**
* 可设置的负载因子
*/
private float defaultLoadFactor;

/**
* 当前已存入的元素的数量
*/
private int entryUseSize;

/**
* 存放key-value键值对对象的数组
*/
private Entry<K, V>[] table = null;


/**
* 无参构造,数组初始大小为16,负载因子大小为0.75f
*/
public MyHashMap() {
this(DEFAULT_INITIAL_CAPACITY,DEFAULT_LOAD_FACTOR);
}

/**
* 有参构造,自己设置数组初始大小和负载因子大小
* @param defaultInitialCapacity 数组初始大小
* @param defaultLoadFactor2 负载因子
*/
public MyHashMap(int defaultInitialCapacity, float defaultLoadFactor2) {
//判断初始容量参数是否合法
if (defaultInitialCapacity < 0) {
//抛出非法参数异常
throw new IllegalArgumentException("输入的初始容量参数是非法的 :"+defaultInitialCapacity);
}
//判断负载因子参数是否合法,Float.isNaN()方法是判断数据是否符合 0.0f/0.0f
if (defaultLoadFactor2 < 0 || Float.isNaN(defaultLoadFactor2)) {
throw new IllegalArgumentException("输入的负载因子参数是非法的 :"+defaultLoadFactor2);
}
this.defaultInitSize = defaultInitialCapacity;
this.defaultLoadFactor = defaultLoadFactor2;
//初始化数组
table = new Entry[this.defaultInitSize];
}

/**
* @Description: 集合中的put方法
* @param k
* @param v
* @return 如是更新则返回key的旧value值,如是插入新的key-value则返回null
*@date: 2019年7月13日 下午6:29:47
*/
@Override
public V put(K k, V v) {
V oldValue = null;
//是否需要扩容?
//扩容完毕后一定会需要重新进行散列
if (entryUseSize >= defaultInitSize * defaultLoadFactor) {
//扩容并重新散列,扩容为原来的两倍
resize(2 * defaultInitSize);
}
//根据key获取的HASH值、数组长度减1,两者做'与'运算,计算出数组中的位置
int index = hash(k) & (defaultInitSize -1);
//如果数组中此下标位置没有元素的话,就直接放到此位置上
if (table[index] == null) {
table[index] = new Entry(k, v, null);
//总存入元素数量+1
++entryUseSize;
}
else {
//遍历数组下边的链表
Entry<K,V> entry = table[index];
Entry<K,V> e = entry;
while(e != null){
if (k == e.getKey() || k.equals(e.getKey())) {
oldValue = e.getValue();
//key已存在,直接更新value
e.value = v;
return oldValue;
}
//获取数组此下标位置上链表的下个元素
e = e.next;
}
//JDK1.7中的链表头插法,直接占据数组下标位置
table[index] = new Entry<K,V>(k, v, entry);
//总存入元素数量+1
++entryUseSize;
}
return oldValue;
}


/**
* @Description: 根据key获取value值
* @param k
* @return
*@date: 2019年7月13日 下午6:34:49
*/
@Override
public V get(K k) {
//通过hash函数和数组元素容量做 【与】运算得到数组下标
int index = hash(k) & (defaultInitSize -1);
if (table[index] == null) {
return null;
}
else {
//获取到数组下标位置元素
Entry<K, V> entry = table[index];
Entry<K, V> e = entry;
do {
if (k.equals(e.getKey())) {
return e.getValue();
}
//获取数组下标位置对应链表中的下一个元素
e = e.next;
} while (entry != null);
}
return null;
}


/**
* @Description:扩容并重新将元素进行散列
* @param i 扩容后的大小
*@date: 2019年7月13日 下午5:06:06
*/
public void resize(int size){
Entry<K,V>[] newTable = new Entry[size];
//改变数组的初始大小
defaultInitSize = size ;
//将已存放键值对数量置为0
entryUseSize = 0 ;
//将已存的元算根据最新的数组的大小进行散列
rehash(newTable);
}


/**
* @Description: 重新进行散列
* @param newTable
*@date: 2019年7月13日 下午5:10:07
*/
public void rehash(Entry<K, V>[] newTable){
List<Entry<K, V>> entryList = new ArrayList<>();
for(Entry<K, V> entry : table){
if (entry != null) {
do {
//将原来数组中的元素放到list集合中
entryList.add(entry);
//如果此数组下标的位置存在链表的话,需要遍历下列表,将列表中的键值对数据取出来放到集合中
entry = entry.next;
} while (entry != null);
}
}

//将旧的数组引用覆盖,让引用指向堆中新开辟的数组
if (newTable.length > 0) {
table = newTable;
}

//所谓重新的散列hash,就是将元素重新放入到扩容后的集合中
for(Entry<K, V> entry : entryList){
//重新put
put(entry.getKey(), entry.getValue());
}
}


/**
* @Description: 根据key获取hashcod码值
* @param key
* @return
*@date: 2019年7月13日 下午5:52:22
*/
public int hash(K key){
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}


/**
* @Description: 判断是否存在此key
* @param k key键
* @return
*@date: 2019年7月23日 下午4:52:22
*/
public boolean containsKey(K k) {
boolean flag = false;

int index = hash(k) & (defaultInitSize -1);
if (table[index] == null) {
return flag;
}
else {
//获取到数组下标位置元素
Entry<K, V> entry = table[index];
Entry<K, V> e = entry;
do {
if (k.equals(e.getKey())) {
flag = true;
return flag;
}
//获取数组下标位置对应链表中的下一个元素
e = e.next;
} while (e != null);
}

return flag;
}


/**
* @Description: 获取map集合所有的key
* @return
*@date: 2019年7月23日 下午5:52:22
*/
@Override
public Set<K> keySet() {
if (entryUseSize == 0) {
return null;
}
Set<K> entrySet = new HashSet<K>();
for(Entry<K, V> entry : table){
if (entry != null) {
do {
//将原来数组中的元素的key放到set集合中
entrySet.add(entry.getKey());
//如果此数组下标的位置存在链表的话,需要遍历下列表,将列表中元素的key取出来放到集合中
entry = entry.next;
} while (entry != null);
}
}
return entrySet;
}








//----------------------------------------内部类 Entry(存放key-value)----------------




/**
* @Title: Entry
* @Description: 实现了key-value简直对接口的java类
* @date: 2019年7月13日 下午6:12:16
*/
class Entry<K, V> implements MyMap.Entry<K, V>{
/**
* 键值对对象的key
*/
private K key;
/**
* 键值对对象的value
*/
private volatile V value;
/**
* 键值对对象指向下一个键值对对象的指针
*/
private Entry<K, V> next;

/**
* 无参构造
*/
public Entry() {

}


/**
* 有参构造
* @param key
* @param value
* @param next
*/
public Entry(K key, V value, Entry<K, V> next) {
super();
this.key = key;
this.value = value;
this.next = next;
}


/**
* 获取key
*/
@Override
public K getKey() {
return key;
}

/**
* 获取value
*/
@Override
public V getValue() {
return value;
}
}

}

3. 测试方法:

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
import org.junit.Test;


/**
* @Title: TestMyMap
* @Description:
* @date: 2019年7月13日 下午6:49:25
*/
public class TestMyMap {

/**
* @Description:单元测试
*
*@date: 2019年7月23日 下午7:07:22
*/
@Test
public void test() {
MyMap<String, String> map = new MyHashMap<>();

for (int i = 0; i < 100; i++) {
//插入键值对
map.put("key" + i, "value" + i);
}

for (int i = 0; i < 100; i++) {
System.out.println("key" + i + ",value is:" + map.get("key" + i));
}

//根据key获取value
System.out.println("\n"+"此key:key88 的value是 "+map.get("key88"));

//判断key是否存在
System.out.println(map.containsKey("key885")+" 此key:key885 不存在!");

//获取map集合中所有的key
System.out.println(Arrays.toString(map.keySet().toArray()));


MyMap<String, String> mapOther = new MyHashMap<>();
Set<String> keySet = mapOther.keySet();
//获取map集合中所有的key
System.out.println((keySet == null)?null:Arrays.toString(mapOther.keySet().toArray()));

}

}

不要忘记留下你学习的足迹 [点赞 + 收藏 + 评论]嘿嘿ヾ

一切看文章不点赞都是“耍流氓”,嘿嘿ヾ(◍°∇°◍)ノ゙!开个玩笑,动一动你的小手,点赞就完事了,你每个人出一份力量(点赞 + 评论)就会让更多的学习者加入进来!非常感谢! ̄ω ̄=