1.Base 1.7:

HashMap 成员变量:
1.static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
初始化桶大小,因为底层是数组,所以这是数组默认的大小。
2.static final int MAXIMUM_CAPACITY = 1 << 30;
桶最大值。
3.static final float DEFAULT_LOAD_FACTOR = 0.75f;
默认的负载因子(0.75)
4.transient Set> entrySet;
table 真正存放数据的数组。
5.transient int size;
Map 存放数量的大小。
6.int threshold;
桶大小,可在初始化时显式指定。
7.final float loadFactor;
负载因子,可在初始化时显式指定。

put流程:

  • 判断当前数组是否需要初始化。
  • 如果 key 为空,则 put 一个空值进去。
  • 根据 key 计算出 hashcode。
  • 根据计算出的 hashcode 定位出所在桶。
  • 如果桶是一个链表则需要遍历判断里面的 hashcode、key 是否和传入 key 相等,如果相等则进行覆盖,并返回原来的值。
  • 如果桶是空的,说明当前位置没有数据存入;新增一个 Entry 对象写入当前位置。

存入的过程会调用addEntry()方法:
当调用 addEntry 写入 Entry 时需要判断是否需要扩容。
如果需要就进行两倍扩充,并将当前的 key 重新 hash 并定位。
而在 createEntry 中会将当前位置的桶传入到新建的桶中,如果当前桶有值就会在位置形成链表。

get流程:

  • 首先也是根据 key 计算出 hashcode,然后定位到具体的桶中。
  • 判断该位置是否为链表。
  • 不是链表就根据 key、key 的 hashcode 是否相等来返回值。
  • 为链表则需要遍历直到 key 及 hashcode 相等时候就返回值。
  • 啥都没取到就直接返回 null 。

Base 1.8:
重要区别:

  • TREEIFY_THRESHOLD 用于判断是否需要将链表转换为红黑树的阈值。
  • HashEntry 修改为 Node。

get流程:

  • 首先将 key hash 之后取得所定位的桶。
  • 如果桶为空则直接返回 null 。
  • 否则判断桶的第一个位置(有可能是链表、红黑树)的 key 是否为查询的 key,是就直接返回 value。
  • 如果第一个不匹配,则判断它的下一个是红黑树还是链表。
  • 红黑树就按照树的查找方式返回值。
  • 不然就按照链表的方式遍历匹配返回值。

put流程:

  • 判断当前桶是否为空,空的就需要初始化(resize 中会判断是否进行初始化)
  • 根据当前 key 的 hashcode 定位到具体的桶中并判断是否为空,为空表明没有 Hash 冲突就直接在当前位置创建一个新桶即可。
  • 如果当前桶有值( Hash 冲突),那么就要比较当前桶中的 key、key 的 hashcode 与写入的 key 是否相等,相等就赋值给 e,在第 8 步的时候会统一进行赋值及返回。
  • 如果当前桶为红黑树,那就要按照红黑树的方式写入数据。
  • 如果是个链表,就需要将当前的 key、value 封装成一个新节点写入到当前桶的后面(形成链表)。
  • 接着判断当前链表的大小是否大于预设的阈值,大于时就要转换为红黑树。
  • 如果在遍历过程中找到 key 相同时直接退出遍历。
  • 如果e != null 就相当于存在相同的 key,那就需要将值覆盖。
  • 最后判断是否需要进行扩容。

    2.HashMap为什么线程不安全

    1.HashMap在put操作的过程中,因为方法不是同步的,比如两个线程进行put的时候,插入的位置都是x位置,始终会造成一个的数据的丢失(它会覆盖之前的数据)
    2.在进行get和put操作并发的过程中,容易导致get的值为null。多线程的情况下,比如A线程在put的过程中需要扩容,在扩容后,B线程的get操作会查到新的hash表,此时的hash表为空,查出的数据也就为null
    3.环形链