CurrentHashMap源码分析

基础概念

//最大容量
    private static final int MAXIMUM_CAPACITY = 1 << 30;
    //默认容量
    private static final int DEFAULT_CAPACITY = 16;
    //最大数组大小
    static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    //默认并发量
    private static final int DEFAULT_CONCURRENCY_LEVEL = 16;	
    //负载因子
    private static final float LOAD_FACTOR = 0.75f;
    //转变树的阙值
    static final int TREEIFY_THRESHOLD = 8;
    //转变链表的阙值
    static final int UNTREEIFY_THRESHOLD = 6;
    //当容量小于64的时候,即便某个链表长度大雨了treeify_threshold,也不会转成树,而是进行扩容,如果当容量大于了64,那么当某个链表长度大于了这个treeify_threshold就会转换成树结构
    static final int MIN_TREEIFY_CAPACITY = 64;
  //和Hashmap一样,作为一个Node的数组
    transient volatile Node<K,V>[] table;
  /**
    * 下一个新的table,即扩容的时候不能为空
     * 简单来说,就是扩容的新数组。为原来数组两倍大小
  */
    private transient volatile Node<K,V>[] nextTable;
/**
* 用来控制初始化和扩容操作
* 有几种值:
* 0表示默认值
* -1:表示正在初始化
* -N(N>1):表示有N个线程正在扩容
* >1:
* 	如果table没有初始化,那么就需要进行初始化的大小
* 	如果已经初始化了,就是table容量
*/
private transient volatile int sizeCtl;

Node数据结构数据结构
在这里插入图片描述
ForwardNode数据结构:在扩容的时候使用,它的node的hash值都为-1,nextTable存储着上面nextTable的引用
在这里插入图片描述

几个下面要经常用的方法

/*扰动函数:
*将h的低16位和高16位做^操作,从而增加了随机性,碰撞旅能够减少10%
*/
static final int spread(int h) {
        return (h ^ (h >>> 16)) & HASH_BITS;
   }
   //找到容量c的最小2次幂
private static final int tableSizeFor(int c) {
        int n = c - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
 }
重要的三个原子操作
//Unsafe的getObjectVolatile方法用来获取指定位置的变量引用
//这里是获取table数组的第i个元素,其计算需要用i*单位元素大小+基础偏移量,从而获得了指定位置元素的其实内存地址
  static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
        return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
    }
//这里是利用比较和交换的方法,获取指定位置的元素,和自己的预期值c进行比较,如果相同则赋值v,如果不同那么就CAS失败,返回false。其底层还是Unsafe的compareAndSwapObject方法在起作用

    static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
                                        Node<K,V> c, Node<K,V> v) {
        return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
    }
//将tab的第i个元素设置为v,其底层也是调用了Unsafe的putObjectVolatile方法,其中i*单位元素大小+基础偏移量来获取指定位置的内存
    static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
        U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
    }
扩容相关:helpTransfer方法
final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
        Node<K,V>[] nextTab; int sc;
        if (tab != null && (f instanceof ForwardingNode) &&
            (nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
            int rs = resizeStamp(tab.length);
            while (nextTab == nextTable && table == tab &&
                   (sc = sizeCtl) < 0) {
                if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                    sc == rs + MAX_RESIZERS || transferIndex <= 0)
                    break;
                if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
                    transfer(tab, nextTab);
                    break;
                }
            }
            return nextTab;
        }
        return table;
    }
扩容相关:addCount方法

添加元素putVal方法

不管CurentHashMap怎么添加元素,都是最后调用的putVal方法的

final V putVal(K key, V value, boolean onlyIfAbsent) {
    	//key和value不能为空
        if (key == null || value == null) throw new NullPointerException();
        //调用扰动函数先找到这个key的hash值
        int hash = spread(key.hashCode());
        int binCount = 0;//用来计数当前这个位置有多少个元素,一边后续进行扩容或者链表转红黑树或者红黑树转链表

	//接下来就是死循环自旋了,处理并发操作,一直到操作成功的
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f;//用来遍历某个hash位置下的所有元素 
            int n, i, fh;//fh保存f对应的hash值
            if (tab == null || (n = tab.length) == 0)
            	//这里说明将CurrentHashMap的Node数组的创建延迟到了put方法中,调用上面的initTable进行初始化咯
                tab = initTable();
            else
            	//否则这时候就是已经初始化过咯
            	if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            		//如过在hash位置为空,说明这里的元素应该为空的,
            		//也就是数组那个位置还没有赋值,这时候赋值就行了
            		if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null))) break;                  
            		 // 这里利用CAS,如果和预期值相同说明可以进行交换设置预期值,并且可以跳出循环了
            		//否则就只能继续死循环,继续进行上面的过程,一直到放入元素成功的
            	}
            else 
            	if ((fh = f.hash) == MOVED)
            		//如果hash值为-1,说明当前这个节点正在参与扩容,应该调用helpTransfer去帮助他
            		tab = helpTransfer(tab, f);
            else {
            	//没有扩容也没有初始化,那么进入正常的模式
                V oldVal = null;
                //利用synchronized锁住f对象,即这个正在加入的数组位置的第一个元素
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                    	//如果tab的第i偏移量元素
                        if (fh >= 0) {
                        	//如果fh也就是f的hash值>0,说明是链表结构
                            binCount = 1;
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
                                //如果是同一key那么就替代掉,然后退出循环
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    oldVal = e.val;
                                    if (!onlyIfAbsent)
                                        e.val = value;
                                    break;
                                }
                                //不是同一key就继续往后遍历,直到尾节点为止,可见是尾插法
                                Node<K,V> pred = e;
                                if ((e = e.next) == null) {
                                    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) {
                                oldVal = p.val;
                                if (!onlyIfAbsent)
                                    p.val = value;
                            }
                        }
                    }
                }
                //看binCount即这个位置下元素的个数
                if (binCount != 0) {
	                //大于treeify_threshold就需要扩容或者转红黑树
	                //这个treeifyBin方法要看,当当下位置的元素达到了treeify_threshold的值了,还要判断是否总容量达到了MIN_TREEIFY_CAPACITY=64(默认64),如果达到了就转红黑树,没达到就扩容即可。
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        //添加binCount,里面还可以进行扩容的。
        addCount(1L, binCount);
        return null;
    }

在这里插入图片描述


Get操作😊


 public V get(Object key) {
        Node<K,V>[] tab; 
        Node<K,V> e, p; 
        int n, eh; 
        K ek;
        //先取key的hash值
        int h = spread(key.hashCode());
        //
        if ((tab = table) != null && (n = tab.length) > 0 &&(e = tabAt(tab, (n - 1) & h)) != null) {
         //如果table不空,而且table的长度大于0,而且h经过hash之后的数组位置不为空
        	if ((eh = e.hash) == h) {
        	//如果当前位置的hash值和这个key的值确实相等
        	//说明没有扩容操作,那么就可以直接读取
                if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                	//如果数组的当前位置的第一个元素是的,那么就直接返回吧。
                    return e.val;
            }
            else if (eh < 0)
            //这时候如果hash值小于0了,说明这个点是ForwardNode,正在处于扩容的阶段,它的hash值是-1,通过nextTable指向了新扩容的数组
            	//这时候就需要通过ForwardNode节点的find方法,取新扩容的数组去找
                return (p = e.find(h, key)) != null ? p.val : null;
        	//这时候经过上面的过程了,说明这个元素不在第一个节点,那么就需要进行遍历
            while ((e = e.next) != null) {
                if (e.hash == h &&
                    ((ek = e.key) == key || (ek != null && key.equals(ek))))
                    return e.val;
            }
        }
        return null;
    }
去看看find方法
Node<K,V> find(int h, Object k) {
            // loop to avoid arbitrarily deep recursion on forwarding nodes
            outer: for (Node<K,V>[] tab = nextTable;;) {
                Node<K,V> e; int n;
                //如果table为空或者k为空或者在k的位置为空直接返回null
                if (k == null || tab == null || (n = tab.length) == 0 || (e = tabAt(tab, (n - 1) & h)) == null)   
                		return null;
                for (;;) {
                    int eh; K ek;
                    //和之前的调用一样,如果确实第一个元素的hash值是这个,且对应其他条件相同的化,就直接返回这个元素
                    if ((eh = e.hash) == h && ((ek = e.key) == k || (ek != null && k.equals(ek))))
                        return e;
                    //如果hash还是小于0,说明这个位置处于扩容状态
                    if (eh < 0) {
                        if (e instanceof ForwardingNode) {
                        	//如果这个点确实是ForwardingNode实例,那么就需要指向nextTable节点去找
                            tab = ((ForwardingNode<K,V>)e).nextTable;
                            continue outer;
                        }
                        //否则的化就是Node实例咯,这样说明这个节点是已经扩容之后的了。
                        else
                            return e.find(h, k);
                    }
                    if ((e = e.next) == null)
                        return null;
                }
            }
        }

扩容机制

前面到处都有一些莫名其妙的方法,比如get中的find方法,以及扩容怎么高的,都怪怪的,现在就来了解一些。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小满锅lock

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值