提示:以下是本篇文章正文内容,下面案例可供参考
一、HashMap底层数据结构是什么?
示例:JDK1.8,HashMap底层数据结构是基于数组 + 链表 + 红黑树(链表长度达到指定长度 8 后,转成红黑树处理哈希碰撞问题)
二、使用步骤
1.put( key,value)
import java.util.HashMap;
import java.util.Map;
/**
* @ClassName HashMapDemo
* @Description TODO
* @Author ZCH
* @Date 2024/9/4 15:49
*/
public class HashMapDemo {
public static void main(String[] args) {
//初始容量 + 加载因子
Map<String, Integer> map2 = new HashMap<>(16,0.75F);
map2.put("putonekey", 40);
map2.put("putonekey", 50);
map2.clear();
}
}
V put(K key, V value);
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
2.hash(key) 用hash算法计算出key的hash值
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
//将 key 进行一个 hashCode计算得到 key的 hashCode值
//再拿hashCode值的高16位 异或 低16位 最终得到一个hash值
}
3.putVal(hash(key), key, value, false, true)
final V putVal(
int hash, //key 的 hash值 👆上面计算出来的值
K key, //存入的key
V value, //存入的value
boolean onlyIfAbsent, // 是否使用了 putIfAbsent(key,value)方法标识
boolean evict //是否链表头部插入节点 默认 true
)
{
Node<K,V>[] tab; //数组 元素是entry对象类型的节点 键值对。
Node<K,V> p; //当前位置所在的节点对象 键值对
int n, i; // n数组长度 i 数组的索引值
//数组为空时,通过 扩容方法 来初始化数组长度
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// hash值 & 数组长度-1 得到数组索引下标 上的节点 即当前位置上的节点
//数组索引位置上的节点若为null,则在这个数组索引位置上创建一个新的节点
//若存入的key计算出来的数组索引下标上没有节点时,就在这个定位上创建一个节点
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; //即将存储的节点元素
K k; //存入的key
//存入的key的hash值和当前定位上的节点的hash值相等 key值也相等
//意思就是 当前存入的key和当前节点上的key是同一个key 即若存入的key相同,则覆盖节点 把节点原封不动给了 新节点
if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode) //若数组索引位置上的节点 是 红黑树的实例 则证明 这个位置上,之前,曾经,历史上发生过很多次哈希碰撞,已经达到了链表转成红黑树的条件,那么现在即将存入的key就存到 红黑树上去
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else { //不是同一个key,也不是红黑树,那就是普普通通的正常存储 数组+链表
// 若索引位置上的节点有下一个节点,链表长度+1
for (int binCount = 0; ; ++binCount) {
//若索引位置上的节点没有下一个节点,创建一个新的节点放在 索引位置上的节点的后面,并判断此时链表的长度是否达到转红黑树的要求
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
//判断此时链表的长度是否达到转红黑树的要求
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;
//若索引位置上的节点有下一个节点,则将索引位置上节点的下一个节点 继续进行循环...找到索引位置上的下下个节点,同时将链表长度+1 ,继续找下一个节点,直到链表的最后一个节点...没有下一个节点了,创建一个新的节点放在链表的最后面
p = e;
}
}
//重新赋值
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null) // 是否使用了 putIfAbsent(key,value)方法标识
//把新值 赋值给新节点
e.value = value;
afterNodeAccess(e);
//put方法的返回值是 返回原来的值
return oldValue;
}
}
//并发修改数
++modCount;
//键值对数量 大于 扩容阈值时 ,则调用扩容方法进行扩容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
总结
1.将 key 进行一个 hashCode计算得到 key的 hashCode值,再拿key的hashCode值的高16位 异或 低16位 最终得到一个hash值
2.将经过hash算法计算得到的hash值 和 一个固定数组长度 进行一个 & 的运算 得到一个坐标 哈希桶数组的index
3. 根据上面计算得到的index 去获取 node节点:
如果index位置上没有Node 根据hash值创建一个节点
如果index位置上有节点: 1.是否有相同key,有相同key则将当前节点重新赋值返回;
2.当前节点是否经过哈希碰撞后产生了红黑树,如果产生了红黑树,加入红黑树;
3.如果不是相同key也不是红黑树,就在当前节点进行挖掘,挖呀挖呀,一挖一个不吱声,如果能挖到超过八米深,则种一棵红黑树;