HashMap
HashMap 里面是一个数组,然后数组中每个元素是一个单向链表。
链表中存储的是一个个Node 节点, Node 包含四个属性:key, value, hash 值和用于单向链表的 next。
1.基本属性
// 节点数组, 数组长度始终保持 2^n,可以扩容,扩容后数组大小为当前的 2 倍 transient Node<K,V>[] table; // 存储的元素个数 transient int size; // 扩容的阈值,等于 capacity * loadFactor int threshold; // 负载因子,默认为 0.75 final float loadFactor; // 默认数组长度 16 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 链表长度大于 8 时链表结构转为红黑树 static final int TREEIFY_THRESHOLD = 8;
2. put()
2.1 计算数组下标
根据键(Key)的hash值 除以数组长度, 计算出此节点存储在数组的位置
一般计算方式为 key.hash() % table.length
当数组长度为 2^n 时, key.hash() & (table.length-1) 等价于 key.hash() % table.length, 但是位运算的效率远远高于取余
2.2 判断key相等
确定数组下标后, 遍历该数组下标的单项链表, 根据 == 或者 key的equals() 判断键是否相等, 若存在相等的键, 则直接覆盖value,否则新增节点
2.3 结构转换
当单向节点的长度大于8时,链表结构转化为红黑树
3. 扩容
Java7 是先扩容后插入新值的,Java8 先插值再扩容
扩容后的数组长度为原数组长度的2倍
扩容后需要重新计算节点在数组中的位置.
原数组下标 = hash & oldSize
新数组下标 = hash & 2 * oldSize
新数组的下标结果 会比 原来的下标结果 多一位0或1(二进制只能为0或1), 为0时表示还在原来的数组位置, 为1时表示原下标位置+原数组长度
4. 多线程下扩容的环形引用问题(死循环)
当单向链表的相邻的两个节点扩容后还在同一个链表, 且位置互换, 则可能出现环形引用的问题.
如果文章对您有所帮助,可以点一下推荐