前言:
Map集合的底层原理,源码解析
Cltr+F12 搜索
Cltr+B跟进
Cltr+Alt+左键,回到上一步
向上的箭头,表示该方法是重写后的
向后的箭头,表示该方法是来自哪个类或者哪个接口
解释:
HashMap
哈希值只跟键有关系,跟值没有一点关系
Node<K,V>[]table 哈希表结构中数组的名字
DEFAULT---INITIAL---CAPACITY 默认数组长度16
DEFAULT---LOAD---FACTOR默认加载因子为0.75
HashMap里面的每一个对象包含以下内容
链表中的键值对对象
包含:int hash //键的哈希值
finalK key //键
v value //值
Node<K,V>nex t //下一个节点的地址值
红黑树中的键值对对象
包含:int hash //键的哈希值
finalK key //键
v value //值
TreeNode<K,V>parent //父节点的地址值
TreeNode<K,V>left //左子节点的地址值
TreeNode<K,V>right//右子节点的地址值
boolean red 节点的颜色
数组里面的键值对对象需要分情况讨论
1.创建对象
HashMap<String,Integer> hm = new HashMap<>();
2.添加元素
hm.put("aaa" , 111);
hm.put("bbb" , 222);
hm.put("ccc" , 333);
hm.put("ddd" , 444);
hm.put("eee" , 555);
添加元素的时候至少考虑三种情况:
2.1数组位置为null
2.2数组位置不为null,键不重复,挂在下面形成链表或者红黑树
2.3数组位置不为null,键重复,元素覆盖
put方法的底层源码(添加第一个元素时)
参数一:键
参数二:值
返回值:被覆盖元素的值,如果没有覆盖,返回null
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
第一个参数hash(key),利用键计算出对应的哈希值,再把哈希值进行一系列的额外处理
简单理解
//参数一:键的哈希值
//参数二:键
//参数三:值
//参数四:如果键重复了是否保留
true表示老元素的值保留,不会覆盖
false,表示老元素的值不保留,会进行覆盖
//参数五暂时不了解
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
//定义一个局部变量,用来记录哈希表中数组的地址值。
Node<K,V>[] tab;
//临时的第三方变量,用来记录键值对对象的地址值
Node<K,V> p;
//表示当前数组的长度
int n;
//表示索引
int i;
//把哈希表中数组的地址值,赋值给局部变量tab
tab = table;
if (tab == null || (n = tab.length) == 0){
//1.如果当前是第一次添加数据,底层会创建一个默认长度为16,加载因子为0.75的数组
//2.如果不是第一次添加数据,会看数组中的元素是否达到了扩容的条件
//如果没有达到扩容条件,底层不会做任何操作
//如果达到了扩容条件,底层会把数组扩容为原先的两倍,并把数据全部转移到新的哈希表中
tab = resize();
//表示把当前数组的长度赋值给n
n = tab.length;
}
//拿着数组的长度跟键的哈希值进行计算,计算出当前键值对对象,在数组中应存入的位置
i = (n - 1) & hash;//index
//获取数组中对应元素的数据
p = tab[i];
if (p == null){
//底层会创建一个键值对对象,直接放到数组当中(第一个元素)
tab[i] = newNode(hash, key, value, null);
//threshold:记录的就是数组的长度 * 0.75,哈希表的扩容时机 16 * 0.75 = 12
if (++size > threshold){
resize();
}
//表示当前没有覆盖任何元素,返回null
return null;