Hiten's Blog.

从反射分析HashMap存储原理

字数统计: 2.4k阅读时长: 12 min
2016/07/25 Share

最近发现一篇讲解HashMap的帖子,我开始回顾了尘埃很久的java基础知识,涉及HashMap相关,我可能会说HashMap相当于IOS里的字典,存取键值的,接受null类型的键值,线程不安全的,HashMap是根据key的hashCode存取,底层是Hash表实现,再往下问,比如两个key的hashCode相等怎么办,hashCode如何对应到bucket位置的,bucketIndex一样怎么存取,好像说不下去了,说实在的自己从来没有真正去理解HashMap源码,顿时觉得自己好菜比,于是赶紧看源码,脑补一下自己的无知。

来一张图

一言以蔽之,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

//默认数组容量
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

//最大数组容量2的30次方
static final int MAXIMUM_CAPACITY = 1 << 30;

//模式加载因子0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;

//空Entry
static final Entry<?,?>[] EMPTY_TABLE = {};

//存储Entry的数组
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;

//hashMap长度
transient int size;

//加载因子
final float loadFactor;

//需要调整大小的极限值(容量*装载因子)
int threshold;
  • 构造方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

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;
threshold = initialCapacity;
init();
}

HashMap一个有四种构造方法,最终都会执行这一个构造,构造方法主要两个参数,初始容量和加载因子,进行赋值;
table数组默认进来是一个空数组,其中HashMap(Map<? extends K, ? extends V> m)类型的构造,会传入一个Map进来,此时还要执行
nflateTable(threshold)和 putAllForCreate(m)方法,inflateTable给table设置初始大小

  • HashMap.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
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
static class Entry<K,V> implements Map.Entry<K,V> {

final K key; //键
V value; //值
Entry<K,V> next;//链表下一个
int hash; //hash值


Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}

public final K getKey() {
return key;
}

public final V getValue() {
return value;
}

public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}

public final boolean equals(Object o) {
//类型不一致,不行
if (!(o instanceof Map.Entry))
return false;
Map.Entry e = (Map.Entry)o;
Object k1 = getKey();
Object k2 = e.getKey();
//同一对象或者equals
if (k1 == k2 || (k1 != null && k1.equals(k2))) {
Object v1 = getValue();
Object v2 = e.getValue();
if (v1 == v2 || (v1 != null && v1.equals(v2)))
return true;
}
return false;
}

public final int hashCode() {
return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
}

public final String toString() {
return getKey() + "=" + getValue();
}

//当put时这个entry因为在hashMap里key相同而被被覆盖时,调用
void recordAccess(HashMap<K,V> m) {
}

//当这个entry被删除时,调用
void recordRemoval(HashMap<K,V> m) {
}
}
  • hash()
1
2
3
4
5
6
7
8
9
10
final int hash(Object k) {
int h = hashSeed;
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}

h ^= k.hashCode();
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}

这里就是Hash算法,传入的参数是key,其中hashSeed相当于种子,他的由来在initHashSeedAsNeeded(int capacity)方法中,
initHashSeedAsNeeded方法在初始化和table长度变化时都会调用, 通过hash()&leng-1算法得到的值,范围永远在0-capacity之间,具体的hash算法,这一篇不是重点。

  • 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
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
public V put(K key, V value) {
//如果table是空的,初始化一下数组
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
//如果key 为空,放空值
if (key == null)
return putForNullKey(value);
int hash = hash(key);//计算hash值

int i = indexFor(hash, table.length);//计算下标

//这个for循环很重要,目的是找到hash值相同且key相同的entry,然后覆盖
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
//覆盖
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
//如果不覆盖,则增加一个新的entry
modCount++;
addEntry(hash, key, value, i);
return null;
}

//增加一个entry(),hash值,key值,value,当前的下标
void addEntry(int hash, K key, V value, int bucketIndex) {
//当到达了threshold值,切当前这个table[bucketIndex]值不为空,触发增长
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);//扩容,已2倍数组长度增长
hash = (null != key) ? hash(key) : 0; //因为数组增长了,重新计算hash值
bucketIndex = indexFor(hash, table.length); //在新数组里边的bucketIndex
}

createEntry(hash, key, value, bucketIndex);
}


void resize(int newCapacity) {
Entry[] oldTable = table;//取出老数组
int oldCapacity = oldTable.length; //老数组长度
//如果老数组长度等于最大值,跳出
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}

Entry[] newTable = new Entry[newCapacity];
transfer(newTable, initHashSeedAsNeeded(newCapacity));
table = newTable;
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
//转换, rehash意思为是否需要重置hash值
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
for (Entry<K,V> e : table) {
while(null != e) {
Entry<K,V> next = e.next;
if (rehash) {//如果需要,重置hash值
e.hash = null == e.key ? 0 : hash(e.key);
}
int i = indexFor(e.hash, newCapacity);
//取出新table下表为i的第一个entry,将当前e的next设为它
e.next = newTable[i];
//重生设置表头
newTable[i] = e;
//循环到下一个
e = next;
}
}
}


//创建一个entry
void createEntry(int hash, K key, V value, int bucketIndex) {
//把原有的bucketIndex对应值取出
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);//把当前的entry设置到bucketIndex下,并将原有的设为这个新增entry的next
size++; //长度增加
}
  • get过程
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
public V get(Object key) {
//如果为空,返回空entry
if (key == null)
return getForNullKey();
Entry<K,V> entry = getEntry(key);

return null == entry ? null : entry.getValue();
}

//获取entry
final Entry<K,V> getEntry(Object key) {

if (size == 0) {
return null;
}

int hash = (key == null) ? 0 : hash(key);//计算hash值
//这个for循环很吊,先根据下标找到链表第一个entry,然后判断entry的key是否等于传入key,如果不等于查找链表下一个,直到相等为止
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}

至此,HashMap基本的get put的思想已经了解,总结一下就是,HashMap内部有一个数据table[],容量是以2的n次方增长,put过程是先计算key 的hash值,拿hash值和table[]容量,算出
bucketIndex值,然后在这个bucketIndex下entry判断这个hash对应的key有没有插入过,如果插入过,覆盖更新,否则,插入,插入时要判断数组长度,进行扩容,扩容的同时要重新计算hash值,然后重新计算bucketIndex,创建新的entry
,把新的entry设置到bucketIndex下,并将bucketIndex原来的值设为这个新增entry的next,取的过程其实比较简单,根据下标找第一个entry,顺着这条链表找到目标entry。

理论如此,但我们需要验证:1、table长度增长实际容量,2、table每个下标对应的链表顺序需要打印出来

反射实现:

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
public class Hash{
@Test
public void testHash() throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
//创建一个数据,用户存放key
String[] strings = new String[52];
for (int i = 0; i < strings.length; i++) {
char c = (char) (i + 65);
strings[i] = String.valueOf(c + "asdke");
}
HashMap<String, String> stringStringHashMap = new HashMap<>();

// Field loadFactor = stringStringHashMap.getClass().getDeclaredField("loadFactor");
// loadFactor.setAccessible(true);
// loadFactor.set(stringStringHashMap, Float.valueOf(0.5F));

Field table = stringStringHashMap.getClass().getDeclaredField("table");
table.setAccessible(true);
System.out.println("index"+" "+"key" + " " + "length" + " " + "bucketIndex");
for (int i = 0; i < strings.length; i++) {
String string = strings[i];
stringStringHashMap.put(string, "aa");
//获取table值
Object[] o = (Object[]) table.get(stringStringHashMap);
//对应table长度
System.out.println(i+" -- "+string + " -- " + o.length + " -- " + (hash(string) & (o.length - 1)));
}
//获取table值
Object[] o = (Object[]) table.get(stringStringHashMap);
for (int i = 0; i < o.length; i++) {
Object o1 = o[i];
if (o1 == null) {
continue;
}
List<Object> list = new ArrayList<>();
//遍历entry链表,放入list中
next(o1, list);
//打印链表
System.out.println("entry " + o1 + " next " + list.toString());
}
System.out.println(stringStringHashMap.toString());
}

/**
* 遍历entry
* @param target
* @param list
* @throws ClassNotFoundException
* @throws NoSuchFieldException
* @throws IllegalAccessException
*/
private void next(Object target, List<Object> list) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
Class c = Class.forName("java.util.HashMap$Entry");
Field next = c.getDeclaredField("next");
next.setAccessible(true);
Object o2 = next.get(target);
if (o2 == null) {
return;
}
list.add(o2);
next(o2, list);
}


/**
* 反射HashMap的hash方法
* @param object
* @return
*/
private int hash(Object object) {
Integer hash = 0;
try {
Class c = HashMap.class;
Object o = c.newInstance();
Method method = c.getDeclaredMethod("hash", Object.class);
method.setAccessible(true);
hash = (Integer) method.invoke(o, object);
} catch (Exception e) {
e.printStackTrace();
System.out.println(e.toString());
}
return hash.intValue();
}
}

执行结果

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
Aasdke -- 16 -- 9
Basdke -- 16 -- 13
Casdke -- 16 -- 2
Dasdke -- 16 -- 3
Easdke -- 16 -- 10
Fasdke -- 16 -- 5
Gasdke -- 16 -- 9
Hasdke -- 16 -- 12
Iasdke -- 16 -- 1
Jasdke -- 16 -- 6
Kasdke -- 16 -- 10
Lasdke -- 16 -- 3
Masdke -- 32 -- 28
Nasdke -- 32 -- 10
Oasdke -- 32 -- 16
Pasdke -- 32 -- 16
Qasdke -- 32 -- 21
Rasdke -- 32 -- 21
Sasdke -- 32 -- 31
Tasdke -- 32 -- 10
Uasdke -- 32 -- 26
Vasdke -- 32 -- 16
Wasdke -- 32 -- 12
Xasdke -- 32 -- 24
Yasdke -- 64 -- 38
Zasdke -- 64 -- 0
[asdke -- 64 -- 38
\asdke -- 64 -- 2
]asdke -- 64 -- 40
^asdke -- 64 -- 40
_asdke -- 64 -- 1
`asdke -- 64 -- 30
aasdke -- 64 -- 47
basdke -- 64 -- 56
casdke -- 64 -- 28
dasdke -- 64 -- 27
easdke -- 64 -- 50
fasdke -- 64 -- 13
gasdke -- 64 -- 43
hasdke -- 64 -- 3
iasdke -- 64 -- 0
jasdke -- 64 -- 52
kasdke -- 64 -- 27
lasdke -- 64 -- 63
masdke -- 64 -- 46
nasdke -- 64 -- 16
oasdke -- 64 -- 9
pasdke -- 64 -- 63
qasdke -- 64 -- 31
rasdke -- 128 -- 122
sasdke -- 128 -- 8
tasdke -- 128 -- 66
entry Zasdke=aa next []
entry _asdke=aa next []
entry Dasdke=aa next []
entry sasdke=aa next []
entry Tasdke=aa next []
entry Rasdke=aa next []
entry kasdke=aa next []
entry casdke=aa next []
entry [asdke=aa next []
entry ]asdke=aa next []
entry Gasdke=aa next []
entry gasdke=aa next []
entry Wasdke=aa next [Hasdke=aa]
entry Basdke=aa next []
entry masdke=aa next []
entry aasdke=aa next []
entry Oasdke=aa next []
entry Qasdke=aa next []
entry Xasdke=aa next [basdke=aa]
entry Easdke=aa next []
entry lasdke=aa next [pasdke=aa]
entry iasdke=aa next []
entry tasdke=aa next [\asdke=aa]
entry Lasdke=aa next [hasdke=aa]
entry Jasdke=aa next []
entry Aasdke=aa next [oasdke=aa]
entry Nasdke=aa next []
entry fasdke=aa next []
entry Vasdke=aa next [Pasdke=aa, nasdke=aa]
entry Iasdke=aa next []
entry Fasdke=aa next []
entry Kasdke=aa next []
entry dasdke=aa next []
entry `asdke=aa next []
entry qasdke=aa next []
entry Casdke=aa next []
entry Yasdke=aa next []
entry ^asdke=aa next []
entry easdke=aa next []
entry jasdke=aa next []
entry rasdke=aa next [Uasdke=aa]
entry Masdke=aa next []
entry Sasdke=aa next []

总结:整理了这么多,头萌萌哒,需要休息,下次再探究一下LinkedHashMap

CATALOG