说说HashMap的原理和扩容
HashMap是由数组 + 链表 + 红黑树(JDK1.8加入) 构成的
一、HashMap有一个很重要的属性 Node[] table
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;
}
// 其他代码..
}
-
这个类的作用是就是存储,本质就是一个映射(键值对)。也就是说一个
键值对(key:value)
就是一个Node。 -
Node是存储在 Node[] table 中的,存储的方式是 链表结构 或 红黑树
二、HashMap是通过 hash 表来存储的。
-
hash表为了解决冲突,采用的是链地址法,就是数组和链表的组合。
-
每个数组元素都是一个链表结构。当数据被 hash 后,将数据放到对应下标的链表上。这个数组就是 Node[] table。也被称之为
hash桶数组
。 -
当key被hashCode()后,就需要确定
键值对
的存储位置。如果定位的是同一个位置,那么就是hash碰撞。如果hash桶数组
很大,那么即使Hash算法较差也很少发生碰撞。如果hash桶数组
很小,即使再好的Hash算法也会发生碰撞。这就需要权衡空间成功 和 时间成本
。
三、HashMap中的基本参数
所以为了权衡,HashMap中有几个默认配置
int threshold; // 键值对的极限值
final float loadFactor; // 负载因子,默认是 0.75
int modCount; //扩容的次数
transient Node<K,V>[] table; //hash桶数组。默认长度 length 必须是 2 的幂次方 是 16
-
length 必须是 2 的幂次方
-
threshold = length * loadFactor
四、HashMap的扩容
-
如果我们放的数据过多,会导致 Node 中的链表过长,影响性能,所以引入了
红黑树
存储。 -
当链表的长度大于
8
时,链表就转化为了红黑树,利用红黑树增删查改的特点提高HashMap的性能。 -
确定元素在
hash桶中
中位置的hash算法。
static final int hash(Object key) {
int h;
// h = key.hashCode() 取hashCode的值
// h ^ (h >>> 16) 高位参与运行
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
- 扩容是针对 Node[] 扩容。参考下方源码。
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)
n = (tab = resize()).length; // resize()扩容
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
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 转化为红黑树
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;//扩容次数 + 1
if (++size > threshold) // 超过了最大容量,resize() 扩容
resize();
afterNodeInsertion(evict);
return null;
}