Hashcode底层代码

一、HashMap底层实现结构

在JDK1.7以前,HashMap的底层数据结构的实现是数组 + 链表的实现方式。但是在1.8之后HashMap的实现是数组 + 链表 + 红黑树

在理解Hash和扩容流程之前,我们得先了解下HashMap的几个字段:

 int threshold; // 所能容纳的key-value对极限
 final float loadFactor; // 负载因⼦
 int modCount; 
 int size;


loadFactor:负载因子
        默认为0.75 ,是决定扩容阈值的重要因素之一
threshold:扩容的阈值
        扩容阈值的计算公式:threshold = length * LoadFactor,length为数组的长度

        threshold就是在此loadFactor和length(数组长度)对应下允许的最大元 素数目,超过这个数目就重新resize(扩容) ,扩容后的HashMap容量是之前容量的两倍
modCount:记录内部结构发生变化的次数
        内部结构发生变化指的是结构发生变化,例如put新键值 对,但是某个key对应的value值被覆盖不属于结构变化
size:HashMap中存在的键值对数量
        注意和table的长度length 、容纳 最大键值对数量threshold的区别
        这里存在一个问题,即使负载因子和Hash算法设计的再合理,也免不了会出现拉链过长的情况,一旦出 现拉链过长,则会严重影响HashMap的性能。于是,在JDK1.8版本中,对数据结构做了进一步的优 化,引入了红黑树。而当链表长度太长(默认超过8)时,链表就转换为红黑树,利用红黑树快速增删 改查的特点提高HashMap的性能,其中会用到红黑树的插入、删除、查找等算法。

二、确定哈希桶数组索引位置

        不管增加、删除、查找键值对,定位到哈希桶数组的位置都是很关键的第一步。前面说过HashMap的 数据结构是数组和链表的结合,所以我们当然希望这个HashMap里面的元素位置尽量分布均匀些,尽 量使得每个位置上的元素数量只有一个,那么当我们用hash算法求得这个位置的时候,马上就可以知道 对应位置的元素就是我们要的,不用遍历链表,大大优化了查询的效率。 HashMap定位数组索引位 置,直接决定了hash方法的离散性能。

⽅法⼀:
static final int hash(Object key) { //jdk1.8 & jdk1.7
	 int h;
	 // h = key.hashCode() 为第⼀步 取hashCode值
	 // h ^ (h >>> 16) 为第⼆步 ⾼位参与运算
	 // 1001 0111 0011 0101 1101 1110 1001 1111 => hash code
     // 0000 0000 0000 0000 1001 0111 0011 0101 => 右移 16 位
     // 1001 0111 0011 0101 0100 1001 1010 1010 => 异或运算
     // 让同一个 key 的高位与地位都参与 hash 运算
     // 目的: 降低 hash 碰撞的概率
     // 最终返回一个 异或运算 后的一个更混乱的 hash 值
	 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
⽅法⼆:
static int indexFor(int h, int length) { //jdk1.7的源码,jdk1.8没有这个⽅法,但是实现原理⼀样的
 	return h & (length-1); //第三步 取模运算
}

三、HashMap的put方法

public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
}


final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {

        Node<K, V>[] tab; // Node 数组 => hash 表, 额外的变量
        Node<K, V> p; // 当前 key 存放索引位置的元素
        int n, // table 数组的长度
                i; // 当前 key 经过运算后的索引位置 => (长度 - 1) & hash

        // 如果 table 数组为 null 或数组的长度为 0, 就进行扩容 => 初始化
        if ((tab = table) == null || (n = tab.length) == 0)
            // 第一次扩容实际就是 table 的初始化
            n = (tab = resize()).length;

       
        if ((p = tab[i = (n - 1) & hash]) == null) // 说明索引位置没有值

            // 如果该索引位置没有值, 就直接创建一个新的节点, 并将其存在这个位置上
            tab[i] = newNode(hash, key, value, null);

        else { // 如果这个索引位置已经有值

            Node<K, V> e; // 上一个相同 key 的节点对象, 与本次存入的新的 key 完全相同的 Node 对象
            K k; // 当前索引位置上的 key

            // 判断当前索引位置的元素与新存入的 key 是否完全相同
            // 第一种情况: 直接拿头节点进行比较, 不关心他是树节点, 还是链表节点
            if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                // 如果这个位置上的 key 与即将存入的 key 完全相同, 就直接将当前节点赋值给 e
                e = p;

                // 第二种情况: 判断是否是树节点, 也就是判断当前节点是否已经发生过 hash 冲突, 且已经变成红黑树
            else if (p instanceof TreeNode) // 判断当前节点是否已经变成树节点 => 判断是否已经变成红黑树
                // 往红黑树中存入当前新的 key/value, 并返回存入后的对象赋值给 e
                e = ((TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value);
            else {
                // 第三种情况: 如果以上两者都不是, 就默认为链表, 判断是否有下一节点, 进行循环判断
                for (int binCount = 0; ; ++binCount) { // 计数, 计算链表的长度
                    // 判断当前节点是否有下一个节点, 如果为 null
                    if ((e = p.next) == null) {
                        // 将即将存入的 key/value 存入一个新的节点, 并设置到当前节点的 next
                        p.next = newNode(hash, key, value, null);

                        // 判断当前链表长度是否大于树化的阈值(7)
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            // 链表长度 >= 8, 那么就准备进行转成树
                            treeifyBin(tab, hash);
                        break; // 这次循环结束
                    }

                    // 此时 e = 当前正在遍历的链表元素
                    // 判断该元素的 hash 与 key 是否与即将存入的 key 完全相同
                    if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                        break; // 如果相同, 直接结束循环

                    p = e; // 将当前遍历的节点, 赋值给之前索引位置的节点
                }
            }
            // 如果找到了与本次要存入的 key 完全相同的节点, 直接将本次存入新的 value 替换旧节点的 value
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null) // 如果配置了只有在不存在时才存入 或 原来的值为 空
                    e.value = value; // 将新的 value 覆盖原来旧的值
                afterNodeAccess(e);
                return oldValue;
            }
        }

        ++modCount; // 并发修改数的记录
        if (++size > threshold) // 存入键值对的数量+1, 且判断是否大于扩容阈值
            resize(); // 扩容
        afterNodeInsertion(evict);
        return null;
    }

四、HashMap的扩容原理

final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;//oidCap原数组长度
        int oldThr = threshold;//threshold:数组使用超过threshold,扩容
        int newCap, newThr = 0;//newCap:新数组长度,newThr:新数组threshold
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {//MAXIMUM_CAPACITY=1<<30:最大长度
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&//扩容一倍
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
            	//DEFAULT_INITIAL_CAPACITY=16:默认长度
                newThr = oldThr << 1;
        }
        else if (oldThr > 0) 
            newCap = oldThr;
        else {               
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
            //DEFAULT_LOAD_FACTOR=0.75
        }
        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;
                    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;
                            if ((e.hash & oldCap) == 0) {
              //当容量扩充一倍时,同一索引下的链表中的节点在新数组中可能会出现两种变化,1:对应的索引不变
              //2:对应的索引+原长度
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
             //将位置不变的节点组成一个链表,lohead为他们的第一个节点,尾节点为lotail
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
             //将位置变为原位置+原长度的节点组成一个链表,头节点为hihead,尾节点为hitail
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;                                                             
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                      //原数组原位置最后一个节点要么为不变位置的节点,要么为变位置的节点,
                        //lonTail.next和hitail.next总有一个不为空
                    }
                }
            }
        }
        return newTab;
    }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值