HashMap

HashMap面试高频题

HashMap和HashTabel的区别

相同点

  1. 两者都是基于hash表实现的,同样每个元素是一个k-v对,内部也是通过链表解决冲突问题,容量不足同样也会自动扩容
  2. 同样实现了Serializable接口,支持序列化,实现了Cloneable接口,能被克隆

不同点

  1. 继承的父类不同
    1. HashMap继承的自AbstractMap类型,但二者都实现Map接口.
    2. Hashtable继承自Dictionary类,Dictionary类是一个已经被废弃的类了,子类自然很少生使用了
  2. HashMap属于线程不安全的,HashTable线程安全
    1. 解决办法就是使用ConcurrentHashMap
    2. HashMap的迭代器(Iterator)是fail-fast迭代器,故当有其他线程改变了HashMap的结构,(增加或者移除元素)抛出ConcurrentModificationException异常,而Hashtable的enumerator迭代器不是fail-fast的。但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。这条同样也是Enumeration和Iterator的区别
  3. 包含的contains方法不同
    1. HashMap是没有Contains方法的,而包括,而包括ContainsValue和containsKey方法,而Hashtable则保留了该方法,包含了这些个方法
  4. 是否允许null值
    1. HashMap是允许有k-v值为null值,用containsValue和containsKey方法判断是否包含对应的键值对;Hash键值对都不能为空
  5. hash计算方式不同
    1. 为了得到元素的位置,首先需要根据元素的 KEY计算出一个hash值,然后再用这个hash值来计算得到最终的位置。
    2. HashMap有和hash方法重新计算的key的hash值,因为hash冲突高,所以通过一种方法重算hash值方法;不过也是先要使用hashCode的方法计算出一个key的hash值,再将hash值与右移16位后相异或,从而得到新的hash值,减少hash碰撞,减少equals方法的使用,提高效率

铺垫位运算

hashMap中使用位运算来解决乘除的运算,能够更快的计算出 2^n次幂

key存储

  1. 底层key的实现是数组加链表,外加红黑树的数据结构,到达一定
  2. 当数组中出现hash冲突时将另一个值放入到链表中,由于链表边长后查询效率非常的低,
  3. 所以当链表长度达到一定的阈值后就会转化为红黑树
  4. 好的hash函数不同而值尽量生成不同的hash值

map.put()底层实现

  1. hash值的生成t=Integer类型
  2. int类型的hash值就是本身
  3. float类型的把小点去掉就是他的hash值
  4. Long类型的hash值就是, 右移后得到新的值和原先的值进行异或,得到就是新的hash值,每一个值都参与到hash计算中来
  5. String字符串的hash值计 ,避免出现乘法的出现,使用位运算,让每一个字符都参数hash运算

重写hashcode和equals时,

  1. 原则是hash值使用哪些值进行hash值的运算那么equals也得使用哪些只进行运算,引用数据类型的重写的方式,参照String类型的重写方式即可

put()方法的调用讲解

  1. 传进来的可以先计算出hash值针对key

  2. 在重写hashcode和equals的基础上再对hash值再进行扰动计算,右移动16位位运算

  3. 一开始new map的时候并未开始初始化长度,

  4. 计算出初始化的容量,第一次初始化逻辑1<<<4 =16,创建出一个数组将数据放入一个他table中

  5. 数组的长度为16,拓容阈值为12 其实是数组长度乘以0.75得到的,

  6. 当然hashMap允许我们传入一个初始化容量值,这个值如果传进去的不是2的幂次数时会自动tableset的方法会往上去找到一个2的幂次数,设置为初始化的容量值

  7. 首先需要先声明一下这里比较的都是key,key可以是对象引用数据类型,也可以是基本数据类型

    1. 根据计算出来的索引值,找到数组上的位置是否为空,
      1. 如果为空:那么直接放上去就行,
      2. 如果不为空:就需要先使用==比较key的地址值一不一样,
        1. **如果一样:**新的值将旧的值直接覆盖,将旧的value返回
        2. **如果不一样:**再使用equals进行比较了如果一样了就覆盖,将旧的value返回,他们是不是同一个对象,如果是那么就要进行值的覆盖,如果不是就进行尾插法进行添加
        3. 尾插法
          1. 链表长度大于八,会变成红黑树的逻辑,
            1. 但是会判断一下,如果数组长度总长度未大于64,只会进行拓容不会变为红黑树
            2. 在转为红黑树前会将单向的链表转化为双向的链表,方便后续的遍历(拓容后的迁移)
            3. 拓容逻辑又是,因为hash值是根据数组长度来进行hash值的计算的,长度变了那么计算的出来的hash值也会变化,但是由于底层设定数组的长度只能是2次幂,这个巧妙的设计呢就会出现原先是一个链表的,拓容后分为两个链表,这两个链表,低位和高位链表,的hash槽位置:如果是 j 另一个就是j+16 (16是旧数组长度值,这个值不固定,那么j当然也是通过旧的数组长度计算出来的),这些都是key hash值和数组长度-1计算出来的,具体细节需要多看源码

    在这里插入图片描述8. new的时候未触发数组,第一次时put进去时才会触发数组长度值

在这里插入图片描述
在这里插入图片描述

为什么是不直接将key作为hash值的而是与高位的十六位做异或运算

  1. 不过也是先要使用hashCode的方法计算出一个key的hash值,再将hash值与右移16位后的hash值相异或,从而得到新的hash值,减少hash碰撞,减少equals方法的使用,提高效率
  2. 就是要进行扰动计算,就是编码者为使用者在进行一次hash值的计算,减少hash冲突

为什么是16?为什么必须是2的幂?如果输入值不是2的幂比如10会怎么样?

  1. 源码中为了避免乘除及求模计算,使用位运算来进行代替求模来进行哈希值的计算,而位运算则是二进制的位运算这样就必须的2次幂
  2. 首先由上面的得知如果传入的数组长度不是2的幂次方那么hash&len-1得到的hash值就不会是值的取模值,这样有可能会出现大量的hash冲突
  3. 这时就需要规范hash值的生成,数组长度必须得是2的幂次方
  4. 当然要是设置进去的不是二的幂次方也不要紧有Tableset的方法
  5. 然而new 一个map的时候是可以设定map的容量值的,但是,值设定有会是不固定的,有可能不是2的次幂那么就需要方法去计算,输入进去的初始化容量值去匹配最接近2的幂次的值,比如输入3 匹配4
  6. 输入7 匹配8 输入11匹配16 需要调用这样的一个方法,

为何重写hashcode一定要要重写equals

  1. 重写Hashcode,如果两个对象的额hash值不相等那么他们一定是不相等的,如果hash值就能进行值的判断,那么就不需要去调用equals方式进行比较那么比较速度相对较快,因为hash值是一开始就计算好的,重写hashcode更多体现的额是hash的复用提高比较效率,而不是减少equals的使用这么一说,因为如果hash冲突过多,equals调用的次数也相对较多,所以呢重写只是为了充分利用hash值,hash值不相等根本就不会去再调用equals方法了

谈一下hashMap中什么时候需要进行拓容,拓容resize()又是如何实现的?

  1. 当我们加入的元素大于拓容阈值时触发拓容,(当数组的长度大于当前容量*0.75时会进行一个扩容,数组长度)

  2. 需要注意的是不是数组长度的为16,有16个位置,并不是将12个位置都是用完毕才进行托容的例如下面的情况也会进行拓容

  3. 在这里插入图片描述

  4. 0.75这个负载因子计算出来的值代表的是一个拥挤程度,大于这个程度就应该拓容了

  5. 没有大于拓容阈值,链表长度大于八,数组长度小于64也会拓容

  6. 拓容后进行元素的迁移,迁移到新的数组上时

    1. 数组上只有一个值,直接使用 newTab[e.hash&(newCap-1)] = e,通俗就是重新计算hash槽位然后直接放进去就行
    2. 数组上是个链表
      1. 在这里插入图片描述

      2. 分低位链表和高位链表,低位就是原来的为位置,高位就是i+16的位置

      3. 然后根据计算将原先链表的数据分别归类于高低位的链表,形成两个链表,需要注意的是,最后两个表位的数据还存在着关系,最后表位数据指向一个null

      4. 红黑树的迁移则是将红黑树分割成两段的双向链表,如果说分割后这两个双向链表的长度小于6那么就会退化为单项链表,也就不会生成这个红黑树了,当然如果长度满足就会进行树化,

        1. 细节优化:当然也可能高位或者低位是没有元素,说明就算迁移后元素组成也没有变化,那样就不需要再树化了直接把头迁移就行

谈一下当两个对象的hashCode相等时会怎么样?

  1. 之后会进行两个可以的equals对比,如果可以相同key,相同直接value值会被覆盖,不同直接往链表上添加或者红黑树上

什么时候链表会变成红黑树呢

当加入第九个元素的是否才会由链表

并且数组长度大于64变为,先将整个链表转化为双向链表,目的是方便遍历和操作,然后再会进行红黑树的转化

什么时候链表会变成红黑树呢

当加入第九个元素的是否才会由链表

并且数组长度大于64变为,先将整个链表转化为双向链表,目的是方便遍历和操作,然后再会进行红黑树的转化

总结:

在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值