Java集合-HashMap

目录
二、使用 (常用方法、构造方法、遍历方式)
三、底层机制及源码剖析 (扩容机制、put源码)

 


一、概要

HashMap 是 Map接口的一个实现类,它的使用频率是这些实现类中最高的。

image

  1. HashMap 的实现不是同步的,这意味着它不是线程安全的。

  2. 它的key、value都可以为null,但是key不能重复。

  3. 此外,HashMap中的映射不是有序的,因为底层是以 hash表的方式来存储的(jdk8的 HashMap底层:数组+链表+红黑树)


二、使用
  1. 常用方法:

    与Map接口的一样。

    image

    put方法:当 put 两个键相同的 k-v 的时候,后面 put 的 value 会覆盖掉前面的v alue

    public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
    }

  2. 四个构造方法:

    (1)HashMap(int initialCapacity, float loadFactor)int 类型为自己设置的初始化容量,float 类型为自己设置的加载因子。

    (2)HashMap(int initialCapacity)其实调用的也是第一个构造器,第二个参数是默认加载因子0.75。

    public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    (3)HashMap()无参构造器,里面初始化加载因子,第一次 put 才会扩容到16.

    public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

    (4)HashMap(Map<? extends K, ? extends V> m)包含“子Map”的构造函数

  3. 遍历方式:实现了Map接口,使用Map接口遍历的方式来遍历
    获取集合(keySet,entrySet,values),然后遍历(迭代器,增强for循环)
    注意转型
    image


三、底层机制及源码剖析

image

  1. 扩容机制:
    和 HashSet的完全一样,因为 HashSet 底层就是 HashMap。
    参考文章:Java-HashMap工作原理及实现
    链接:Java-HashSet
    结论:
    image
    6)Java8中,若一条链表的元素已经至少有8个(即插入第 9个时),会进入treeifyBin方法(进行树化的方法),里面再判断table表的大小,若小于64,则继续使用resize方法对table表进行扩容,若大于或等于64,才进行树化。

  2. put() 源码:

    public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
    }

    image

    putVal() 源码:
    参考文章(针对语句:if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))):http://bbs.itheima.com/thread-218310-1-1.html

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
    boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i; //定义了辅助变量
    //table 就是 HashMap 的一个数组, 类型是 Node[]
    //进入👇的if:当前 table 是 null,或者其大小=0
    //就是第一次扩容,空间为16
    if ((tab = table) == null || (n = tab.length) == 0)
    n = (tab = resize()).length;
    //进入👇的if:p为null(表示该索引的table表处还没有存放过元素)
    //i = (n - 1) & hash]:根据key,得到hash 去计算该key应该存放到table表的哪个索引位置
    //p = tab[i = (n - 1) & hash]):并把这个位置的对象,赋给 p
    if ((p = tab[i = (n - 1) & hash]) == null)
    tab[i] = newNode(hash, key, value, null);
    //进入👇的else:p不为空(即tab[i]不为null)
    else {
    Node<K,V> e; K k;
    //进入👇的if:p不为空;当前索引位置对应的链表的第一个元素和准备添加的 key 的 hash值一样
    //(判断头结点)
    //(&&)并且满足以下两个条件之一,就不进行插入
    //(||)(1)准备加入的 key 和 p 指向的 Node 结点的 key 是同一个对象
    //(||)(2)p 指向的 Node 结点的 key 的equals() 和准备加入的key比较后相同
    if (p.hash == hash &&
    ((k = p.key) == key || (key != null && key.equals(k))))
    e = p;
    //进入👇的else if :p不为空;p 是一棵红黑树结点
    //调用 putTreeVal方法 ,来进行添加
    else if (p instanceof TreeNode)
    e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
    //进入👇的else:p不为空;如果table对应索引位置,已经是一条链表
    else {
    for (int binCount = 0; ; ++binCount) {
    if ((e = p.next) == null) {//头结点的 next为空,直接插入
    p.next = newNode(hash, key, value, null);
    //每次成功在一条链表上插入后,会判断该链表是否至少有了 8 个结点
    //若是,则进入 treeifyBin方法中判断是否需要进行树化:
    // if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
    // resize(); //【MIN_TREEIFY_CAPACITY 为64】
    //由上面代码可知,若 table表长度小于 64,则会调用resize方法继续对table表进行扩容;
    //若大于64,则进行树化。转换成TreeNode,然后每次插入会进入 27 行代码的else if中。
    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
    //TREEIFY_THRESHOLD:8
    treeifyBin(tab, hash);
    break;
    }
    if (e.hash == hash &&
    ((k = e.key) == key || (key != null && key.equals(k))))
    break;
    p = e;
    }
    }
    //进入👇的if:插入失败
    //覆盖掉该结点的 value
    if (e != null) { // existing mapping for key
    V oldValue = e.value;
    //进入👇的if:
    //* @param onlyIfAbsent if true, don't change existing value
    //onlyIfAbsent 在 put 方法里可以看到传入的是false,所以这里的 !onlyIfAbsent 为 true
    if (!onlyIfAbsent || oldValue == null)
    e.value = value;
    afterNodeAccess(e);
    return oldValue;
    }
    }
    ++modCount;
    //这里的 size 是:每加入一个结点 Node (k,v,h,next),size++
    if (++size > threshold)
    resize();
    afterNodeInsertion(evict);
    return null;
    }

posted @ 2021-10-18 18:05  Wiiiimp  阅读(60)  评论(0)    收藏  举报