HashMap 细品底层源码(三)

剖析put方法

前言

上问中介绍了map的get方法以及hash方法的思想,简单的提及了关于二叉查询树以及红黑树的结构,本文将会介绍hashmap中的put方法。

正文

先看下源码

// 调用hash处理后,调用putval方法
 public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

接下来看下调用的putVal 方法

 
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        // 判断有没有初始化,没有就初始化map,调用resize扩容方法
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
            // 计算出存储下标的位置,并判断数组下标内是否有数据
        if ((p = tab[i = (n - 1) & hash]) == null)
        	// 下标内为空,就创建数据,将入参放进去
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            // 判断下标内的头结点是否与入参的hash,key相等,相等就覆盖
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
                // 不相等,判断是否树结点,是放入红黑树中
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
            // 不是树结点,那就是链表,遍历链表
                for (int binCount = 0; ; ++binCount) {
                // 链表没有遍历到与入参相等的hash与科研,就在链表最后插入
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        // 链表长度大于阈值的话即8,就调用treeifyBin方法尝试转换成红黑树
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    // 遍历找到链表中有节点的与入参相同,就停止遍历
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            // 对e的value进行处理
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                // value为null或者onlyIfAbsent为false,onlyIfAbsent意思是的值不存在才插入
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                    // 回调 linkhashmap的方法,将e节点移交链表尾部
                afterNodeAccess(e);
                return oldValue;
            }
        }
        // 修改次数+1
        ++modCount;
        // 判断当前的容量是否超过阈值,超过进行扩容
        if (++size > threshold)
            resize();
         // 回调 linkhashmap的方法,删除旧节点
        afterNodeInsertion(evict);
        return null;
    }

resize扩容方法做为hashmap其中的一个核心点本文将不展开,后续将会对其详细剖析。treeifyBin转换成红黑树也放到后续介绍红黑树的时候具体展开,继续往下,先看下afterNodeAccess 这个方法。

// hashmap 定义的方法由linkhashmap实现,将入参节点做成链表的尾结点
void afterNodeAccess(Node<K,V> e) { // move node to last
        LinkedHashMap.Entry<K,V> last;
        // accessOrder为ture 以及参数e不是链表尾节点才走入内部
        // accessOrder为true代表查询方式排序
        // tail为链表的尾节点
        if (accessOrder && (last = tail) != e) {
            LinkedHashMap.Entry<K,V> p =
                (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
            p.after = null;
            // e的before即指向上个节点为空,说明e相当于头节点
            if (b == null)
            // 链表的将a节点变更为头节点
                head = a;
            else
            // 将b的下个节点指向a,以前是指向e
                b.after = a;
            if (a != null)
            // 将a的befor从e变更成b
                a.before = b;
            else
            // last结点指向b,last节点之前存的是tail尾节点
                last = b;
                // 尾节点为空
            if (last == null)
            // p赋值为头节点
                head = p;
            else {
            // p做为last的后一个节点
                p.before = last;
                last.after = p;
            }
            // 即尾节点指向p
            tail = p;
            // 修改数+1
            ++modCount;
        }
    }

继续,往下看afterNodeInsertion 这个方法,这个方法的是删除最旧的数据

 void afterNodeInsertion(boolean evict) { // possibly remove eldest
        LinkedHashMap.Entry<K,V> first;
        if (evict && (first = head) != null && removeEldestEntry(first)) {
            K key = first.key;
            removeNode(hash(key), key, null, false, true);
        }
    }

然而这个方法却不会调用,看下removeEldestEntry(first)) 这个方法

 protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
        return false;
    }

直接返回false,所以是不会走进if里面,如果硬要实现,那只能实现linkhashmap的removeEldestEntry 方法。
不知是否有读者疑惑modCount这个字段是干嘛的,笔者也疑惑过,后查询不少资料才确定modCount这个字段的含义。众所周知,hashmap并非线程安全的map,不安全即存在并发问题。所以hashmap内部迭代的时候就通过modCount的值来判断是否有并发问题。
先看下内部类HashIterator的源码

 abstract class HashIterator {
        Node<K,V> next;        // next entry to return
        Node<K,V> current;     // current entry
        int expectedModCount;  // for fast-fail
        int index;             // current slot

        HashIterator() {
        // 初始化的时候将modcount值赋给expectedModCount
            expectedModCount = modCount;
            Node<K,V>[] t = table;
            current = next = null;
            index = 0;
            if (t != null && size > 0) { // advance to first entry
                do {} while (index < t.length && (next = t[index++]) == null);
            }
        }

        public final boolean hasNext() {
            return next != null;
        }

        final Node<K,V> nextNode() {
            Node<K,V>[] t;
            Node<K,V> e = next;
            // 如果后续有修改过modCount,导致两边不相等则抛异常
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            if (e == null)
                throw new NoSuchElementException();
            if ((next = (current = e).next) == null && (t = table) != null) {
                do {} while (index < t.length && (next = t[index++]) == null);
            }
            return e;
        }

        public final void remove() {
            Node<K,V> p = current;
            if (p == null)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            current = null;
            K key = p.key;
            removeNode(hash(key), key, null, false, false);
            expectedModCount = modCount;
        }
    }

这段代码思想很简单,就是fast-fail机制,通过判断modCount是否等于expectedModCount,在put,以及remove等方法操作时,会修改modCount,所以在迭代器的nextNode(),remove()方法里,都先执行判断。
expectedModCount是否与modCount相等,如果有其他线程的操作,导致modCount的值修改,就抛出异常。

后话

本文中介绍了hashmap的put方法的思想,以及介绍了modCount字段的含义,接下来剖析一下hashmap的核心点之一resize扩容方法以及1.8中的重压特性红黑树的数据结构。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值