对HashMap的理解
-
-
hashMap初始化长度为16,之后的每一次扩容都翻倍扩。
-
hashMap内部维护了一个增长因子,默认为0.75;集合中保存的元素的个数 >= 数组长度 * 0.75后就会扩容
-
每次在调用map集合的put方法时,首先根据键的hashCode方法,计算出其在数组中的对应下标
-
-
如果数组下标处已经有保存元素,如果该处是一个链表,则直接将新插入的元素保存在链表中(尾插法);如果链表的长度大于等于8,并且数组长度大于等于64,则链表进化为红黑树。否则对数组进行扩容,而不转换为红黑树
如果该下标处不是链表,而是红黑树,则将新插入的节点插入到红黑树中;如果在删除元素时,到红黑树的长度不足6时,则红黑树退化为链表。
Map<String,Object> map = new HashMap<>();
map.put("a",1)
源码分析:
//用于保存集合数据的Node对象数组
transient Node<K,V>[] table;
static class Node<K,V> implements Map.Entry<K,V> {
final int hash; //键经过计算后的hash值
final K key; //键
V value; //值
Node<K,V> next; //下一个Node节点的引用。形成链表的关键属性
}
public HashMap() { //无参构造方法
this.loadFactor = DEFAULT_LOAD_FACTOR; // 将默认的增长因此 0.75赋值给属性loadFactor
}
map.put("f", 6); //调用put方法,将键及值插入到map集合
map.put("f", 7); // map.get("f") 值是多少? 6
源码分析:
public V put(K key, V value) {
//hash(key) 计算键的hash值
return putVal(hash(key), key, value, false, true);
}
/*计算键的hash值*/
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
/*真正实现将键和值插入到map集合的方法*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; //准备一个空的Node数组,将来和table会做关联
Node<K,V> p; //准备一个空的Node对象
int n, i;
//检测table数组是否为空
if ((tab = table) == null || (n = tab.length) == 0)
//计算新数组的长度
n = (tab = resize()).length; // resize()会在集合中尚未插入元素时执行一次 16
/*
15&hash -> 不管hash值是多少,最终算出来的结果必定是0-15之间
*/
if ((p = tab[i = (n - 1) & hash]) == null)//根据hash计算该hash对应的下标处是否有存在元素
tab[i] = newNode(hash, key, value, null); //该下标没有保存Node,则创建Node并保存到该下标
else {//下标为i出已经保存了一个Node节点
Node<K,V> e; //临时的Node节点
K k; //临时的键
if (p.hash == hash && //发生hash碰撞,键重复
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode) //判断节点是否为红黑树
//如果当前节点p是一个TreeNode类型(红黑树),则直接已树的形式插入
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {//将新的node节点已链表的形式插入到集合
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {//获取链表的尾部
//尾插法,将Node插入到链表的最后
p.next = newNode(hash, key, value,