HashMap相关整理

HashMap是什么?
HashMap是一种使用了Hash算法的Java中的数据结构,内部主要由数组链表实现。
备注链表是为了应付Hash碰撞时使用,如果没有碰撞,则不需要链表

HashMap的底层接口是Map
Map和Colletction没有关系

HashMap设计-均匀分布
因为HashMap设计是通过,Hash值的不同,放到不同的index下,所以,存在一些的设计,使得index的计算更加均匀。

HashMap当发生Hash碰撞时,从头插入还是从尾插入?
Java8之前是头插入,Java8之后是尾插入。
为什么修改?头插入时,HashMap在并发情况下的put操作在一定情况下会造成死循环

HashMap在并发put操作时,为什么可能造成死循环?
首先,要明白,HashMap在put操作时的自动扩容内部顺序:当发生Hash碰撞时,先把元素放到计算所得索引的链表上,再新建数组进行扩容。而不是先新建数组,然后在把该元素放上去。

     //put方法的最后
        if (++size > threshold)
            resize();

死循环和扩容代码相关

/**
 * Transfers all entries from current table to newTable. 
 */
void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    for (Entry<K,V> e : table) {

        while(null != e) {
            //(关键代码)
            Entry<K,V> next = e.next;
            if (rehash) {
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            int i = indexFor(e.hash, newCapacity);
            e.next = newTable[i];
            newTable[i] = e;
            e = next;
        } // while  

    }
}

简单解释,头插、尾插
使⽤头插会改变链表的上的顺序,但是如果使⽤尾插,在扩容时会保持链表元素原本的顺序,就不会出
现链表成环的问题了。如果出现了环形列表,当扩容时候,就会在这个方法进入死循环。

HashMa的线程安全
1、扩容的时候,死循环
2、put操作时,当发生哈希碰撞,此时HashMap按照平时的做法是形成一个链表。如果有多个并发,可能导致出现,线程A认为索引处为空,进入判断准备进行插入。然后被挂起,线程B也认为索引处为空,把数据插入。之后,线程A进行运行,把线程B的数据覆盖。

HashMap存放元素时的索引是如何计算的?
1、计算Hash值,hash=key的hashCode 与(他自身)的异或

    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); // ^异或;>>> 无符号右移
    }

为什么要右移16位

2、计算索引

//Java7中的方法,Java8没有单独的方法但是原理相同。length表示HashMap的容量
static int indexFor(int h, int length) {
    return h & (length-1);  //X % 2^n = X & (2^n – 1) //用位运算(与)来代替取模运算,故length必须是2n的倍数
}

为什么在计算索引时,要用位运算代替取模运算?
因为位运算直接对内存数据进行操作,不需要转成十进制,所以位运算要比取模运算的效率更高

HashMap的默认初始化长度是多少,为什么?
16=2^4,为什么不用2的其他次方?
倾向认为是一个经验值。

HashMap的容量必须是2的幂
就算你自定义初始容量不是2的幂,HashMap内部也会自动找到最接近的2的幂,并且设置。
为什么?
在计算Hash值时,用了用位运算(与)来代替取模运算,故length必须是2n的倍数

为什么HashMap要提前扩容?
猜测,可能为了Hash的均匀分布。

为什么重写equals⽅法的时候需要重写hashCode⽅法
1、首先,明确,equals相等,可以认为他们的hashCode必须相等。这是规定,因为Hash变换中,相同的、值通过Hash变换的值相等。
2、有些类,比方 Set 存储的是不重复的对象,依据 hashCode 和 equals 进行判断,所以 Set 存储的
对象必须重写这两个方法。
3、如果自定义对象作为 Map 的键,那么必须重写 hashCode 和 equals 。

HashMap什么时候会触发扩容操作?
HashMap中元素数超过容量*加载因子时,HashMap会进行扩容。

HashMap的负载因子为什么是0.75
加载因子过高,例如为1,虽然减少了空间开销,提高了空间利用率,但同时也增加了查询时间成本;

加载因子过低,例如0.5,虽然可以减少查询时间成本,但是空间利用率很低,同时提高了rehash操作的次数。

在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减少rehash操作次数,所以,一般在使用HashMap时建议根据预估值设置初始容量,减少扩容操作。

选择0.75作为默认的加载因子,完全是时间和空间成本上寻求的一种折衷选择,

关于红黑树和HashMap
当链表长度超过阈值(8)时,将链表转换为红黑树

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值