HashMap
HashMap底层原理剖析(超详细!!!)
一、散列表结构
散列表结构就是数组+链表的结构
二、什么是哈希?
Hash也称散列、哈希,对应的英文单词Hash,基本原理就是把任意长度的输入,通过Hash算法变成固定长度的输出
这个映射的规则就是对应的哈希算法,而原始数据映射后的二进制就是哈希值
不同的数据它对应的哈希码值是不一样的
哈希算法的效率非常高
三、HashMap原理讲解
3.1继承体系图
3.2Node数据结构分析
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;计算得到哈希值
final K key;
V value;
Node<K,V> next;
}
interface Entry<K, V> {
K getKey();
V getValue();
V setValue(V value);
3.3底层存储结构
当链表长度到达8时,升级成红黑树结构
3.4put数据原理分析
首先put进去一个key----value
根据key值会计算出一个hash值
经过扰动使数据更散列
构造出一个node对象
最后在通过路由算法得出一个对应的index
3.5什么是哈希碰撞?
当传入的数据key对应计算出的hash值的后四位和上一个一样时,这时候计算出的index就会一致,就会发生碰撞,导致数据变成链表
例如:
(16-1)------->0000 0000 0000 1111
“张三”------->0100 1101 0001 1011
“李四”-------->1011 1010 0010 1011
此时,就会发现,张三和李四计算出的hash值转化为二进制的后四位一致,导致计算出index一致
3.6JDK8为什么引入红黑树?
哈希碰撞,会带来链化,效率会变低
引入红黑树会提高查找效率
3.7扩容机制
每次扩容为初始容量的2倍
eg:16------->32
为了防止数据过多,导致线性查询,效率变低,扩容使得桶数变多,每条链上数据变少,查询更快
四、手撕源码
1.HashMap核心属性分析
树化阈值-----8和64
负载因子0.75
threshold扩容阈值,当哈希表中的元素超过阈值时,触发扩容
loadFactory负载因子0.75,去计算阈值 eg:16*0.75
size-------当前哈希表中元素个数
modCount--------当前哈希表结构修改次数
2.构造方法分析
public HashMap(int initialCapacity, float loadFactor) {
//校验 小于0报错
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
//capacity大于最大值取最大值
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
//负载因子不能小于等于0
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
//tableSizeFor方法
this.threshold = tableSizeFor(initialCapacity);
}
---------------------------------------------------------
//传入一个初始容量,默认负载因子0.75
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
---------------------------------------------------------
//无参数,负载因子默认0.75
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
---------------------------------------------------------
//传入一个map的对象
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
3.put方法分析
public V put(K key, V value) {
//返回putVal方法,给key进行了一次rehash
return putVal(hash(key), key, value, false, true);
}
----------------------------------------------------------
static final int hash(Object key) {
//让key对应的hash值的高16位也参与运算
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
----------------------------------------------------------
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict)
{
//tab:引用当前HashMap的散列表
//p:表示当前散列表的元素
//n:表示散列表数组的长度
//i:表示路由寻址的结果
Node<K,V>[] tab; Node<K,V> p; int n, i;
---------------------------------------------------------- //延迟初始化逻辑,当第一次调用putVal的时候,才去初始化HashMap对象的散列表大小
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
------------------------------------------<