您的位置:首页 > 其它

HashMap源码学习记录

2020-05-11 04:08 274 查看

HashMap源码学习记录

  • HashMap 新增方法 put

  • 2020.4.13 每天进步一点点

    HashMap是什么?

    HashMap是基于哈希表的Map接口实现,是以key-value存储的形式存在,即主要用来存放键值对HashMap不是同步的,这意味着他不是线程安全的。他的key、value都可以为null。此外HashMap中的映射不是有序的。

    HashMap的数据结构

    JDK1.8之前HashMap由数组=链表组成,数组是HashMap的主体。链表则是要未来解决哈希冲突而存在的。哈希冲突是指:两个对象调用的hashCode方法计算的 哈希码值一直导致计算的数组索引相同而存在的。
    JDK1.8之后当链表长度大于阈值(或者红黑树边界,默认值为8)且当期数组长度大于64时,此时索引位置上所有数据改为使用红黑数存储。
    HashMap 源码中包含了以下几个属性:
    // HashMap 初始化长度
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    // HashMap 最大长度
    static final int MAXIMUM_CAPACITY = 1 << 30; // 1073741824

    // 默认的加载因子 (扩容因子)
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    // 当链表长度大于此值且容量大于 64 时
    static final int TREEIFY_THRESHOLD = 8;

    // 转换链表的临界值,当元素小于此值时,会将红黑树结构转换成链表结构
    static final int UNTREEIFY_THRESHOLD = 6;

    // 最小树容量
    static final int MIN_TREEIFY_CAPACITY =

    HashMap 新增方法 put

    public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
    }
    
    /**
    * Implements Map.put and related methods
    *
    * @param hash hash for key 通过hashCode()方法 计算的hash值
    * @param key the key 键
    * @param value the value to put 值
    * @param onlyIfAbsent if true, don't change existing value 表示不要更改现有值
    * @param evict if false, the table is in creation mode.表示table处于创建模式
    * @return previous value, or null if none
    */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
    boolean evict) {
    Node<K,V>[] tab;//创建一个接收数组
    Node<K,V> p;//创建一个键值对
    int n, i;
    if ((tab = table) == null || (n = tab.length) == 0)
    //这个判断作用是判断数组是否存在,再一次存入数据对数组进行初始化。
    //table是HashMap初始化定义的数组未赋值所以table=null
    // n=tab.length表示数组的长度
    n = (tab = resize()).length;
    // 运用到resize()方法来创建数组,并且把数组的长度传给定义好的n
    // resize()这个方法也可以用来扩容
    if ((p = tab[i = (n - 1) & hash]) == null)
    //如果计算的数组索引位置上不存在数据。直接创建节点插入 (n - 1) & hash计算数组的索引
    //赋值给定义好的i. 。如果这个索引p=tab[i]位置上的值为null.则执行下一条语句
    
    tab[i] = newNode(hash, key, value, null);
    //tab[i] 为null,直接将新的key-value插入到计算的索引i位置
    else {//如果计算的位置上已有数据
    Node<K,V> e;
    K k;
    if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))
    // 判断p.hash==tab[i].hash的hash值与传入的hash进行比较,
    //p.key==tab[i].key 是否等于参数key 或者String.equals相等
    // ,则是覆盖原来的值,覆盖的方法在下面的  if (e != null) 判断中执行。
    e = p;
    //给e赋值p让其不为null,执行下面的 if (e != null) 判断语句执行覆盖操作。
    else if (p instanceof TreeNode) //判断是不是红黑树
    e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); //添加到红黑树
    else {
    //else是判断链表的情况下key没有相同的情况在尾部插入键值对
    for (int binCount = 0; ; ++binCount) {//循环方法添加数据
    if ((e = p.next) == null) {      //尾插法判断
    p.next = newNode(hash, key, value, null);
    //找到节点链表中next空的节点,创建新的节点插入
    if (binCount >= TREEIFY_THRESHOLD - 1)
    // -1 for 1st 链表长度大于 8(TREEIFY_THRESHOLD (8)),转换成红黑树
    treeifyBin(tab, hash);
    break;
    }
    //在链表的情况下key有相同的。情况下替换操作
    if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))
    //如果节点链表中有发现已有相同key,如果哈希和K的equals都相等,跳出循环。
    //执行下面的  if (e != null)替换tab[i=(n-1)&hash].next的值
    break;
    p = e;
    }
    }
    if (e != null) { // existing mapping for key  覆盖原值的方法
    V oldValue = e.value;
    if (!onlyIfAbsent || oldValue == null)
    e.value = value;
    afterNodeAccess(e);
    return oldValue;
    }
    }
    ++modCount;//用作修改计数
    if (++size > threshold)   //当前大小大于领界大小,扩容
    resize();
    afterNodeInsertion(evict);
    return null;
    }
    qintaipeng 原创文章 8获赞 0访问量 199 关注 私信
    内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: