HashMap源码详解

HashMap过程

结构:
Node数组+链表

1.当执行put方法时,首先判断table是否为空,如果为空就执行resize方法进行扩容
2.更具hashcode&(n-1),定位到table的位置。
3.先判断table这个位置为不为空
4.如果为空,就把这个数据变成一个node插入在这个位置
5.如果不为空判断这个节点的类型
6.如果为红黑树类型就以红黑树类型的插入
7.如果不是红黑树类型的就以node节点的形式插入到链表的最后面,然后判断数量是否大于8。如果大于8,则调用链表变红黑树的方法。treeifyBin这个方法会判断容量是否小于64,如果小于64就扩容,不是则转换红黑树
8.插入完成之后,判断是否大于容量乘以阈值
9.如果超过容量乘以阈值,就调用resize进行阔容

resize扩容:

前面的一大堆代码,是扩容的容量的值得判断。扩容容量为原来得2倍
然后更具新的容量,new出新的table
然后遍历旧的table
如果旧的table的某一项只有一个节点,就更具hashcode计算新的位置插入到新的table中
如果不止一个节点,就判断首个节点的类型,如果是红黑树类型的。就执行红黑树split方法。
因为红黑树中的节点,只有地位和高位两种,所以分别用TreeNode记录下这些节点
如果这些节点的数量小于6,就变成链表类型的插入到对应的位置
否则从新构建红黑树。地位的是在原位置不动,高位移动到(低位+n)的位置
如果是链表类型的,会用4个指针记录下,高位和地位的位置整体移动。低位在原来的位置,高位移动到(低位+n)的位置

几个重要的参数解释

  //默认容量是16
  static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
  //最大容量是2^30
  static final int MAXIMUM_CAPACITY = 1 << 30;
  //加载因子是0.75
  static final float DEFAULT_LOAD_FACTOR = 0.75f;
  //链表达到8,变成红黑树
  static final int TREEIFY_THRESHOLD = 8
  //红黑树减少到6,变成链表
  static final int UNTREEIFY_THRESHOLD = 6;
  //总容量达到64时,才会出现链表变成红黑树的情况,否则就算链表长度为8也不变
  static final int MIN_TREEIFY_CAPACITY = 64;
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;
    }

resize扩容

final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        //获取原来table中的元素个数
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        //获取扩容的阈值,在构造方法中传了容量,则为大于等于指定容量的 最小2的指数
        int oldThr = threshold;
        //新的容量和阈值
        int newCap, newThr = 0;
        //如果原table不为空
        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
        }
        /**
        * 从构造方法我们可以知道
        * 如果没有指定initialCapacity, 则不会给threshold赋值, 该值被初始化为0
    	* 如果指定了initialCapacity, 该值被初始化成大于initialCapacity的最小的2的次幂
		* 这里这种情况指的是原table为空,并且在初始化的时候指定了容量,
		* 则用threshold作为table的实际大小
		*/
		//构造方法中设置容量的值了
        else if (oldThr > 0)
            newCap = oldThr;
        //构造方法中没有指定容量,则使用默认值
        else {               // zero initial threshold signifies using defaults
            //默认是16
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        // 计算指定了initialCapacity情况下的新的 threshold
        //newThr没有前面没有设置
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            //阈值是加载因子*newCap大小
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;


    /**从以上操作我们知道, 初始化HashMap时, 
    *  如果构造函数没有指定initialCapacity, 则table大小为16
    *  如果构造函数指定了initialCapacity, 则table大小为threshold,
    *  即大于指定initialCapacity的最小的2的整数次幂
    *  从下面开始, 初始化table或者扩容, 实际上都是通过新建一个table来完成
    */ 

        @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) {
                  /** 这里注意, table中存放的只是Node的引用,这里将oldTab[j]=null只是清除旧表的引用, 
                   * 但是真正的node节点还在, 只是现在由e指向它
                   */
                    oldTab[j] = null;
                    //桶中只有一个节点,直接放入新桶中
                    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;
                            //1.8的优化,链表的低位还是在原来位置,高位会移动到 n+oldCap的位置
                            //e.hash & oldCap== 0表示位置没有移动
                            //记录着低位的头节点和尾节点位置
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            //记录高位的头节点和尾节点的位置
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        //低位还是在原来的数组的位置
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        //高位移动到j+oldCap的位置
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

tableSizeFor()这个方法的作用是找到大于等于给定容量的最小2的次幂值

 static final int tableSizeFor(int cap) {
        int n = cap - 1;
        //向右移动1位,并且和当前的n取或操作
        //这样高位的右边也是1
        n |= n >>> 1;
        //经过上面的操作,高位和次高位变成1了,已经有两个1了
        //所有接下移动两位,高位第3位和第4也会变成1。
        n |= n >>> 2;
        //经过>>>1和>>>2操作,一共有4个1了
        n |= n >>> 4;
        n |= n >>> 8;
        //最多移动16位,因为int使用32位来存储的
        n |= n >>> 16;
        //返回n+1
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }
    /*
    我们这里假设n的初始值为9,那么9的二进制表示是
	00000000 00000000 00000000 00001001
	那么经历一次右移之后,二进制表示是
	00000000 00000000 00000000 00000100
	这两个值进行或运算之后,就是
	00000000 00000000 00000000 00001101
	这里我们如果看最高的1的话,那么可以发现右移1位并且或运算之后,使得最高位的右边一位也是1.
	同理,我们可以下一句右移2位并且进行或运算之后,使得最高两位1的后两位也是1.
	我们之后运行到最后一个右移,可以发现n的值变为了
	00000000 00000000 00000000 00001111
	当然该值大于1并且小于最大值那么+1之后,该值就变成了
	00000000 00000000 00000000 00010000
	惊讶的发现这个值不就是2的次幂嘛!!!

	此时我们回到第一句-1,如果给定的n已经是2的次幂,但是不进行-1操作的话,那么得到的值就是大于给定值的最小2的次幂值。
	至于为什么右移到16位,可以得到的最大值是32个1
	11111111 11111111 11111111 11111111
	这个是因为java的int类型用4个字节32位来进行存储的。最往后没有意义。
    */
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值