HashMap、ConcurrentHashMap对象put(K key, V value)方法实现

以下源码版本为java8,与java7版本的HashMap源码有所差异,请区分。

HashMap对象put(K key, V value)源码

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
     * @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.
     * @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)
            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;
            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) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        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;
                }
            }
            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;
    }

put执行过程:
1、首先检查hashmap是否为空,为空的话执行resize,相当于初始化一个map。
2、hashmap非空时,计算tab数组下标[(n - 1) & hash],判断数组对象是否为空,为空时新建一个node节点。
3、数组对象非空,tab[i]非空,首先判断该节点的key与即将put的key值是否相同,相同的话先讲tab[i]对应的node存储起来。
4、继续判断tab[i]是否为红黑树对象,若tab节点为红黑树,则执行一次树对象put操作。
5、接下来处理tab[i]节点为链表对象,通过一个计数器binCount统计链表长度。如果tab[i]对象p的next为null,则链表到头了,这个时候新建一个node<key,value>节点为p.next。
6、如果链表长度计数器binCount>7(8-1),换句话说,链表长度大于8时,则进行红黑色转换。如果不满足转换条件,链表种插入新节点完毕,无需其他操作。
7、遍历链表过程中,发现key值相同的节点时,直接break,执行最后面的value覆盖即可。
8、针对存在相同key的节点,执行value覆盖,并返回旧值。
9、针对新增node节点的情况,若tab大小超过阈值(容量*负载因子),执行resize扩容操作,返回null。
https://www.cnblogs.com/jzb-blog/p/6637823.html

resize执行过程:
由于java版本差异,jdk7与jdk8扩容机制还是存在差异,先看看jdk7版本的扩容实现,比较好理解:

java7版本的扩容方式即新建一个数组,遍历老数组元素,计算每个元素的下标[(length-1)&hash],然后按照下标填充元素。

//传入新的容量  
void resize(int newCapacity) {   
	//引用扩容前的Entry数组
    Entry[] oldTable = table;      
    int oldCapacity = oldTable.length;  
    //扩容前的数组大小如果已经达到最大(2^30)了  
    if (oldCapacity == MAXIMUM_CAPACITY) {  
    	//修改阈值为int的最大值(2^31-1),这样以后就不会扩容了  
        threshold = Integer.MAX_VALUE; 
        return;  
    }  
  	//初始化一个新的Entry数组  
    Entry[] newTable = new Entry[newCapacity];  
    //!!将数据转移到新的Entry数组里  
    transfer(newTable);     
    //HashMap的table属性引用新的Entry数组                      
    table = newTable;          
    //修改阈值                 
    threshold = (int) (newCapacity * loadFactor);  
} 
/** 
这里就是使用一个容量更大的数组来代替已有的容量小的数组,transfer()方法将原有Entry数组的元素拷贝到新的Entry数组里。
**/
void transfer(Entry[] newTable) {  
	//src引用了旧的Entry数组  
    Entry[] src = table;                   
    int newCapacity = newTable.length;  
    //遍历旧的Entry数组
    for (int j = 0; j < src.length; j++) {   
    	//取得旧Entry数组的每个元素  
        Entry<K, V> e = src[j];             
        if (e != null) {  
        	//释放旧Entry数组的对象引用(for循环后,旧的Entry数组不再引用任何对象)  
            src[j] = null;
            do {  
                Entry<K, V> next = e.next;  
                //!!重新计算每个元素在数组中的位置  
                int i = indexFor(e.hash, newCapacity); 
                e.next = newTable[i]; //标记[1]  
                newTable[i] = e;      //将元素放在数组上  
                e = next;             //访问下一个Entry链上的元素  
            } while (e != null);  
        }  
    }  
}  
static int indexFor(int h, int length) {  
    return h & (length - 1);  
}  

java8后的版本扩容机制引入了红黑树,扩容原理类似,但实现细节变更了:
参考文章:https://blog.csdn.net/z69183787/article/details/64920074?locationNum=15&fps=1

/**
     * Initializes or doubles table size.  If null, allocates in
     * accord with initial capacity target held in field threshold.
     * Otherwise, because we are using power-of-two expansion, the
     * elements from each bin must either stay at same index, or move
     * with a power of two offset in the new table.
     *
     * @return the table
     */
    final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {
        	// 老容量超过最大容量,则不进行扩容,任其碰撞
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            // 扩容两倍
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) 
        	// initial capacity was placed in threshold
            newCap = oldThr;
        else {              
        	// zero initial threshold signifies using defaults
        	// 空map初始化
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
        // 新建扩容数组
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        if (oldTab != null) {
        	// 遍历老数组
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    // 老数组节点为孤点时,即子节点为null,通过e.hash & (newCap - 1)获取数组下标,将节点填充到该数组对象中
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                    // 节点为红黑色时,内部分裂机制还不理解
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            // 元素位置没有发生变化
                            // 原hash与老容量进行与运算,loHead、loTail位置不变时的头尾节点
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            // 元素位置发生变化
                            // hiHead、hiTail位置变化后新的头节点和尾节点
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        // 位置不变时,(e.hash & oldCap) == 0,数组当前下标指向loHead
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        // 位置变化时,数组下标变为[j + oldCap],指向头节点
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

ConcurrentHashMap对象put(K key, V value)源码
参考文章:https://www.cnblogs.com/snowater/p/8087166.html

public V put(K key, V value) {
        // 核心是调用putVal方法
        return putVal(key, value, false);
    }

    public V putIfAbsent(K key, V value) {
        // 如果key存在就不更新value
        return putVal(key, value, true);
    }

    /** Implementation for put and putIfAbsent */
    final V putVal(K key, V value, boolean onlyIfAbsent) {
        // key或value 为null都是不允许的,因为Forwarding Node就是key和value都为null,是用作标志位的。
        if (key == null || value == null) throw new NullPointerException();
        // 根据key计算hash值,有了hash就可以计算下标了
        int hash = spread(key.hashCode());
        int binCount = 0;
        // 可能需要初始化或扩容,因此一次未必能完成插入操作,所以添加上for循环
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            // 表还没有初始化,先初始化,lazily initialized
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            // 根据hash计算应该插入的index,该位置上还没有元素,则直接插入
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            	// 空节点直接新增元素,然后退出循环
                if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))
                    break;                   // no lock when adding to empty bin
            }
            // static final int MOVED = -1; // hash for forwarding nodes
            // 说明f为ForwardingNode,只有扩容的时候才会有ForwardingNode出现在tab中,因此可以断定该tab正在进行扩容
            else if ((fh = f.hash) == MOVED)  
                // 协助扩容            
                tab = helpTransfer(tab, f);   
            else {
                V oldVal = null;
                // 节点上锁,hash值相同的节点组成的链表头结点
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        if (fh >= 0) { 
                        	// 是链表节点
                            binCount = 1;
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
                                // 遍历链表查找是否包含该元素
                                if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) {
                                    oldVal = e.val;  // 保存旧的值用于当做返回值
                                    if (!onlyIfAbsent)
                                        e.val = value;  // 替换旧的值为新值
                                    break;
                                }
                                Node<K,V> pred = e;
                                if ((e = e.next) == null) {
                                    // 遍历链表,如果一直没找到,则新建一个Node放到链表结尾
                                    pred.next = new Node<K,V>(hash, key, value, null);
                                    break;
                                }
                            }
                        }
                        else if (f instanceof TreeBin) { // 是红黑树节点
                            Node<K,V> p;
                            binCount = 2;
                            // 去红黑树查找该元素,如果没找到就添加,找到了就返回该节点
                            if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key, value)) != null) {
                                // 保存旧的value用于返回
                                oldVal = p.val;
                                if (!onlyIfAbsent)
                                    p.val = value; // 替换旧的值
                            }
                        }
                    }
                }
                if (binCount != 0) {
                    if (binCount >= TREEIFY_THRESHOLD)
                        // 链表长度超过阈值(默认为8),则需要将链表转为一棵红黑树
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        // 如果只是替换,并未带来节点的增加则直接返回旧的value即可
                        return oldVal;
                    break;
                }
            }
        }
        // 元素总数加1,并且判断是否需要扩容
        addCount(1L, binCount);
        return null;
    }

put操作总结:
1、初始化判断,将节点直接插入tab[i];
2、节点为链表的话,遍历链表定位相同key的位置,有相同的就替换,没有相同的就将新node插入到链表末尾。
3、节点为红黑树的话,遍历树节点,指向相应的更新、新增节点操作。

get操作总结:
1、通过tab[(n - 1) & hash])获取数组下标;
2、遍历链表、红黑树。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值