HashMap源码学习

4 篇文章 1 订阅

HashMap是Map最主要实现类,用的非常多,所以对于HashMap的底层实现还是很重要的

阅前须知:

HashMap是线程非安全的,因此效率相较于HashTable会高一些

HashMap存放key-value键值对,且可以存放null的key和value值

HashMap的实现依赖数组+链表+红黑树

HashMap中遇到的概念点:

       1. table   存放键值对的数组,数组类型是Node<K,V> 

       2.threshold   临界值,当存放数据的数组实际长度大于临界值时触发resize()扩容

       3.loadFactor   负载因子,用于控制触发数组扩容的系数(当前数组的长度*负载因子=临界值)

 

 

  • 首先来看看HashMap的构造方法:
  
    /**
     * 构造一个具有指定初始容量和负载因子的空HashMap
     */
    public HashMap(int initialCapacity, float loadFactor)

    /**
     * 构造一个具有指定初始容量和默认负载因子的空HashMap
     */
    public HashMap(int initialCapacity)

    /**
     * 构造一个具有默认初始容量和负载因子的空HashMap
     */
    public HashMap()

    /**
     * 通过HashMap构造器放入一个Map直接初始化
     */
    public HashMap(Map<? extends K, ? extends V> m)

hashmap拥有上述四个构造方法,前三个实现思路一致,所以主要过一遍是空参构造。

  • 创建实例代码

HashMap  map= new HashMap();

 创建hashMap空参实例时,会默认负载因子为0.75,但并未初始化容量(jdk8中)

     /**
     * 未指定负载因子的值时的默认值为0.75.
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;   

     /**
     * 需要注意的是jdk8未初始化容量大小而是当首次使用put方法时才给默认值,但jdk7时默认给了初始化值为16
     */
    public HashMap() {
         
        this.loadFactor = DEFAULT_LOAD_FACTOR; // 这里只是设置负载因子为默认值,未初始化容量,             
    }
   
  •  put()方法
map.put("key","value");

使用put()方法添加数据时,调用putVal()

 public V put(K key, V value) {
        /** 
          * hash(key), key的Hash值
          * key, 要存放的key值
          * value,要存放的key值
          * onlyIfAbsent 为true时,不会覆盖已存在的key所对应的value值 
          */
            
        return putVal(hash(key), key, value, false, true);
    }

 进入putVal方法后,首先判断table是否为空,若为空就调用resize()方法,初始化长度为16,临界值为16*0.75=12

  /**
    * 当table(Node数组)为null或长度为0时,调用resize()方法,初始化数组长度
    *
    */
    if ((tab = table) == null || (n = tab.length) == 0){
        n = (tab = resize()).length;
       }

  /**
    * 调用resize()方法,判断table为空,将数组当前大小赋值为0,且初次调用put()时threshold也为0,
    */
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;

  /**
    * 此时代码会进入赋值操作,赋值初始容量为16,临界值为16*0.75
    */
     else {               
            newCap = DEFAULT_INITIAL_CAPACITY;//16
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);//0.75*16
        }

 继续往下走,通过(n-1) & hash 计算出的值作为tab的下标i,并把tab[i]的引用指向新的数组p,如果p为null,则调

用NewNode()创建该链表的第一个节点并赋值给tab[i]

    if ((p = tab[i = (n - 1) & hash]) == null)
         tab[i] = newNode(hash, key, value, null);

    /**
        * newNode方法
        */
    // Create a regular (non-tree) node
    Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
        return new Node<>(hash, key, value, next);
    }

 如果P不为null,当P的hash值与当前要添加的key的hash值、P的Key值与要添加的key相等时,判断两者key相同,将p的引用指向e,如果p是红黑树结构,那么插入后仍是红黑树结构

        //n为tab的长度,i为tab的下标       
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            //定义即将插入的节点
            Node<K,V> e; K k;
            //若判断e节点key的hash值与p节点相等且key值相等或符合equals方法,则 
            //判断两者key相同,将p的引用指向e
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;//p表示tab上的某Node节点
            else if (p instanceof TreeNode)
                //若添加的节点时红黑树结构,则调用putTreeVal,同样得到的也是红黑树结构
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                //遍历链表
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        //当p.next为空时说明链表到头了,直接将新加节点的引用给P.next
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            //当链表的长度大于8时,调用treeifBin方法,这个方法的作用是判断是否                                                            
                            //要将链表转换成红黑树结构,当链表长度超过8但是不超过64时,会对链表
                            //调用resize()进行扩容,当超过64时,才会将链表转换成红黑树结构
                            treeifyBin(tab, hash);
                        //如未超过则break
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }

继续走下一步,针对已存在key进行的处理,若OnlyifAbsent为true或原有节点的value为空时,将插入节点的value值设置为已存在节点的value,返回原有的value值

 if (e != null) { // 当key已存在时
      V oldValue = e.value;
      if (!onlyIfAbsent || oldValue == null)
          e.value = value;
          afterNodeAccess(e);//未做任何实质性操作
          return oldValue;
  }

 最后,当数组长度超过临界值时调用resize()对数组进行扩容,默认新数组是原有数组的2倍

        ++modCount;
        if (++size > threshold)
            resize();//扩容
        afterNodeInsertion(evict);//无实质作用
        return null;

最后,推荐一篇我觉得写得很好的博客

hmi1024的博客 Java8 HashMap源码的简单分析(1)

 
 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值