HashMap底层实现原理
在JDK1.7及以前的版本中 ,HashMap 底层由 数组+ 链表 实现。
新建一个 HashMap的时候就会初始化一个数组。数组中的元素我们称为 Entry,每个 Entry其实就是一个 键值对,并且包含一个指向下一个元素的引用,这就构成了链表。当需要存储一个 键值对(Entry对象) 时,会根据hash算法来决定其在数组中的位置。当需要取出一个 Entry 对象时,也会根据 hash算法找到其在数组中的存储位置,再根据 equals 方法从该位置的链表中取出 Entry。
HashMap 结合了 ArrayList 的查询效率高的特点以及 LinkedList 插入效率高的特点,但是如果我们要存储的数据过于庞大,肯定会造成多次 哈希冲突,这样一来,链表上的节点就会堆积很多,在做查询的时候效率又会变得很低,失去了 HashMap本来的特点
在JDK1.8 及之后的版本中,HashMap 底层由 数组 + 链表 + 红黑树 实现。当节点数不大于 8 时,还是一个链表结构,只不过插入节点时变成了尾插法,当节点数大于 8 之后,将链表结构转换为红黑树结构,复杂度也从 O(n) 变成了 O(logn).
HashMap.put
put方法在hashmap中起插入的作用,即在当前的hashmap上添加新元素。
主要流程:
- 判断table数组是否为空,或者为 null,否则执行 resize() 扩容操作
- 使用 key.hashcode() 将 key 转换为 数组下标 i , 如果 table[i] =null,直接新建节点添加即可,转入 6 ;如果 table[i] != null,则转向 3;
- 判断 table[i] 的首个元素是否和 key 一样,如果相同(equals判断)则直接覆盖 value,否则转向 4
- 判断 table[i] 是否为 红黑二叉树,如果是,则直接插入键值对,否则转向 5
- 遍历 table[i],判断链表长度是否大于 8 ,若是 则把链表转换为 红黑树,进行插入操作;否则进行链表插入操作;如果链表中已存在 key 则直接覆盖 value
- 插入成功后,判断实际存在的键值对数量是否超过了 threshold ,如果操作则需要扩容。
public v put(K key, V value) {
if (key == null)
return putForNullKey(value);
// 通过 key 计算hash值
int hash = hash(key);
// 通过 key 找到数组中待存放的下标
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))) {
// 如果两个 hash 值相同,或者键相同,则修改 value
V oldValue = e.value;
e.value = Value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
// 可能两个 hash 值不同,但计算出的 index 一样,这就发生了 hash碰撞
// 使用头插法,将其放在链表头部
addEntry (hash, key, value, i)
return null;
}
HashMap.get
get方法在hashmap中起取值的作用,即根据 key 获取对应的 value值
主要流程:
- 首先判断数组是否为空,如果为空直接返回null;
- 使用 hashcode() 计算 key的索引 i ,如果 table[i] 为空,返回null;否则判断key值是否相同,若相同则返回该key的value
- 如果 key 值不相同,则往后遍历;
- 如果 table[i] 是红黑树,则调用红黑树的 getTreeNode() 进行查找
- 如果 table[i] 是链表,则遍历链表查找
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
//若哈希表不为空,并且当前索引位置有元素
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
//若在一个桶中,并且当前索引位置的key值与要查找的key值相等
//说明找到了,返回该值
if (first.hash == hash &&
((k = first.key) == key || (key != null && key.equals(k))))
return first;
//若key值不相同,且还有元素
if ((e = first.next) != null) {
//如果此时已经树化,则调用红黑树的getTreeNode()查找
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
//若没有树化,则在从前往后遍历链表,直到找到该元素
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
//若找不到则返回null
return null;
}