在 JDK 1.8 版本之前,HashMap 底层的数据结构是数组 + 链表,如下图:
在 1.8 及以后是数组 + 链表 + 红黑树
重要的几个变量
- DEFAULT_INITIAL_CAPACITY = 1 << 4; Hash 表默认初始容量
- MAXIMUM_CAPACITY = 1 << 30; 最大 Hash 表容量
- DEFAULT_LOAD_FACTOR = 0.75f;默认加载因子
- TREEIFY_THRESHOLD = 8;链表转红黑树阈值
- UNTREEIFY_THRESHOLD = 6;红黑树转链表阈值
- MIN_TREEIFY_CAPACITY = 64;链表转红黑树时 hash 表最小容量阈值,达不到优先扩容
存放数据
Map <String,Employee> map = new HashMap <> ();
Employee e0 = new Employee("zhangshan");
map.put("zhangshan", e0);
会对 “zhangshan” 进行一次 hash 运算, 把 “zhangshan” 这个字符串映射成一个小于数组长度的整型值。就像下面这样:
int i = hash("zhangshan")
假如 i 等于 1,就会把 e0 构造成一个节点,放入数组下标为 1 的位置。数组存放的是一个节点,该节点有指向下一个节点的指针 next
, 如下:
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node <K,V> next;
}
int i = hash("zhangshan")
把字符串映射成一个整型,不同的字符串可能映射成相同的位置,有下面这种可能:
hash("zhangshan") == hash("lisi")
这就是 hash 碰撞,出现碰撞后,会以链表的方式追加在后面,就形成了上图中的结构。
如何确定 key 在数组中的位置
先看 jdk 1.7 中的实现:
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
// 获取 key 对应的整型 hash值
int hash = hash(key);
// 再将这个hash值转换为小于这个数组的整型值 i,然后将节点插入数组i位置
int i = indexFor(hash, table.length);
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;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;}
其中 hash
方法如下:
final int hash(Object k) {
int h = hashSeed;
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h ^= k.hashCode