HashMap基础知识

HashMap基础知识

HashMap 主要用来存放键值对,它基于哈希表的Map接口实现,是常用的Java集合之一。JDK1.8 之前 HashMap 由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。JDK1.8 以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)时,将链表转化为红黑树,以减少搜索时间。

HashMap的put操作

在这里插入图片描述

HashMap定位存储下标的方法

static final int hash(Object key) {   //jdk1.8 & jdk1.7 
     in th; 
     // h = key.hashCode() 为第一步 取hashCode值 
     // h ^ (h >>> 16)  为第二步 高位参与运算 
     return(key == null) ? 0: (h = key.hashCode()) ^ (h >>> 16); 
} 
static int indexFor(int h,int length) {  //jdk1.7的源码,jdk1.8没有这个方法,但是实现原理一样的 
     return h & (length-1); //第三步 取模运算 
}

resize的过程

// 下面这段就是把原来table里面的值全部搬到新的table里面
    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;
 
                // 如果该存储桶里面只有一个bin, 就直接将它放到新表的目标位置
                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) {
                            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;
                    }
                    if (hiTail != null) {
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }

首先,定义了两个新的链表分别是: lo 链表与 hi 链表,其中 loHead 与 hiHead 指向头部, loTail 与 hiTail 指向尾部。然后,从旧 table 的某个桶的头部开始,逐一将桶内链表上的每一个节点迁移到 lo 链表或 hi 链表中。决定进入 lo 链表或 hi 链表的依据是判断条件:(e.hash & oldCap) == 0。下面解释下为什么采用这种判断依据。首先, oldCap 的大小必然是2的n次幂,newCap 的大小必然是2的n+1次幂,在求桶的下标时使用公式 (capacity-1)&hash ,这个公式本质上取出了 hash值的低 n 位,这个 n 对应于 n 次幂。然而,当迁移时需要计算在新 table 下的桶下标,这是使用的公式是 (2*capacity-1) & hash ,这样就会比原来多取出一位,而二进制只有0/1,这也与两个新链表 hi/lo 对应。(e.hash & oldCap) == 0时,这个节点应该被丢进 lo 链表,否则进入 hi 链表。在将原 table 上某个桶的一个链表全部拆完后,将这两个链表分别链接到新 table 对应的桶中,而这两个桶下标间存在一种数值上的关系,即二者差为一个原来的 table 长度——oldCap。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值