HashMap

  传送门:Java 8系列之重新认识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. 多线程下扩容的环形引用问题(死循环)

  当单向链表的相邻的两个节点扩容后还在同一个链表, 且位置互换, 则可能出现环形引用的问题.

原文地址:https://www.cnblogs.com/virgosnail/p/9497537.html