hashMap和hashSet

一、hashMap结构

HashMap底层维护的是一个数组,我们向HashMap中所放置的==对象==实际上是存储在该数组当中;
当向HashMap中put一对键值对时,它会根据key的hashCode值计算出一个位置,该位置就是此对象准备往数组中存放的位置。如果该位置没有对象存在,就将此对象直接放进数组当中;
如果该位置已经有对象存在了,则顺着此存在的对象的链开始寻找。
如果此链中有对象,就会使用equals()方法进行比较,如果比较之后为false(说明不是同一个对象),则将该对象放到数组中,然后将数组中该位置以前存在的对象链接到此对象的后面。

hashMap底层结构是数组+链表+红黑树
当hash值相同时就会以链表形式向后拼接,当链表数量长度达到8时自动变为红黑树,remove小于8后自动转为链表。

二、hashSet

HashSet底层是通过HashMap实现的,看如下的构造函数,构造HashSet的时候底层就构造了一个HashMap

public HashSet() {
        map = new HashMap<>();
}
private static final Object PRESENT = new Object();
public boolean add(E e) {
        return map.put(e, PRESENT)==null;
}

hashSet添加的过程:
当hashSet添加对象时,会先调用hashCode方法比较hash值,然后根据hash值决定把对象放在对应位置。
根据hashCode方法比较hash值可能会出现两种情况:

  1. 1.hashCode不同,直接讲对象放进对应位置
  2. 2.hashCode相同。这时就不能判断对象是否相同,需要调用equals方法进行比较
  •          1. 如果equals方法为true则表示两个对象相同,属于重复添加,不进行添加。
  •          2. 如果equals方法为false,则表示两个对象并不是同一个对象。就会添加在hashcode对应的数组上并引出链,连接原来位置的对象,jdk8之后,当链表值达到8之后会自动转化为红黑树

三、hashMap补充

HashMap的特点

  • hashMap存取是无序的
  • 键和值的位置都可以是null,但键位置只能是一个null
  • 件位置是唯一的,底层数据结构控制键的
  • jdk1.8前数据结构是:数组+链表。jdk1.8之后:数组+链表+红黑树
  • 边界值>8,并且数组长度大于64,才将链表转换成红黑树,变成红黑树的目的是提高搜索速度,高效查询。

解决hash冲突的办法有哪些?HashMap用的哪种?

解决Hash冲突方法有:开放定址法再哈希法链地址法(HashMap中常见的拉链法)、简历公共溢出区。HashMap中采用的是链地址法。

  • 开放定址法也称为再散列法,基本思想就是,如果p=H(key)出现冲突时,则以p为基础,再次hash,p1=H(p),如果p1再次出现冲突,则以p1为基础,以此类推,直到找到一个不冲突的哈希地址pi。因此开放定址法所需要的hash表的长度要大于等于所需要存放的元素,而且因为存在再次hash,所以只能在删除的节点上做标记,而不能真正删除节点
  • 再哈希法(双重散列,多重散列),提供多个不同的hash函数,R1=H1(key1)发生冲突时,再计算R2=H2(key1),直到没有冲突为止。这样做虽然不易产生堆集,但增加了计算的时间。
  • 链地址法(拉链法),将哈希值相同的元素构成一个同义词的单链表,并将单链表的头指针存放在哈希表的第i个单元中,查找、插入和删除主要在同义词链表中进行,链表法适用于经常进行插入和删除的情况。
  • 建立公共溢出区,将哈希表分为公共表和溢出表,当溢出发生时,将所有溢出数据统一放到溢出区

注意开放定址法和再哈希法的区别是

  • 开放定址法只能使用同一种hash函数进行再次hash,再哈希法可以调用多种不同的hash函数进行再次hash

为什么要在数组长度大于64之后,链表才会进化为红黑树

因为红黑树是的出现是为了增加搜索效率,但是如果数组较小时候就出现红黑树反而会降低效率,红黑树需要进行左旋右旋,变色,这些操作来保持平衡。

JDK1.8以前HashMap的实现是数组+链表,即使哈希函数取得再好,也很难达到元素百分百均匀分布。当HashMap中有大量的元素都存放在同一个桶中时,这个桶下有一条长长的链表,此时HashMap就相当于单链表,假如单链表有n个元素,遍历的时间复杂度就从O(1)退化成O(n),完全失去了它的优势,为了解决此种情况,JDK1.8中引入了红黑树(查找的时间复杂度为O(logn))来优化这种问题
 

哈希表底层采用何种算法计算hash值?还有哪些算法可以计算出hash值?

hashCode方法是Object中的方法,所有的类都可以对其进行使用,首先底层通过调用hashCode方法生成初始hash值h1,然后将h1无符号右移16位得到h2,之后将h1与h2进行按位异或(^)运算得到最终hash值h3,之后将h3与(length-1)进行按位与(&)运算得到hash表索引

其他可以计算出hash值的算法有

  • 平方取中法
  • 取余数
  • 伪随机数法

当两个对象的hashCode相等时会怎样

hashCode相等产生hash碰撞,hashCode相等会调用equals方法比较内容是否相等,内容如果相等则会进行覆盖,内容如果不等则会连接到链表后方,链表长度超过8且数组长度超过64,会转变成红黑树节点

HashMap的put方法流程

  1. 首先根据key的值计算hash值,找到该元素在数组中存储的下标
  2. 如果数组是空的,则调用resize进行初始化
  3. 如果没有哈希冲突直接放在对应的数组下标中
  4. 如果key已经存在,就覆盖掉value
  5. 如果冲突了,并且当前哈希后是链表结构,就判断链表是否大于8,如果大于8,并且数组容量小于64,就进行扩容;如果链表节点数大于8,并且数组的容量大于64,则将结构转换为红黑树;
  6. 如果冲突后是红黑树就将节点挂在树上

HashMap的扩容方式

一般用什么作为HashMap的key?

一般用Integer、String这种不可变类当HashMap当key

  • 因为String是不可变的,当创建字符串时,它的hashcode被缓存下来,不需要再次计算,相对于其他对象更快
  • 因为获取对象的时候要用到equals()和hashCode()方法,那么键对象正确的重写这两个方法是非常重要的,这些类很规范的重写了hashCode()以及equals()方法

为什么Map桶中节点个数超过8才转为红黑树?

树节点占用空间是普通Node的两倍,如果链表节点不够多却转换成红黑树,无疑会耗费大量的空间资源,并且在随机hash算法下的所有bin节点分布频率遵从泊松分布,链表长度达到8的概率只有0.00000006,几乎是不可能事件,所以8的计算是经过重重科学考量的

  • 从平均查找长度来看,红黑树的平均查找长度是logn,如果长度为8,则logn=3,而链表的平均查找长度为n/4,长度为8时,n/2=4,所以阈值8能大大提高搜索速度
  • 当长度为6时红黑树退化为链表是因为logn=log6约等于2.6,而n/2=6/2=3,两者相差不大,而红黑树节点占用更多的内存空间,所以此时转换最为友好

HashMap为什么线程不安全?

  • 多线程下扩容死循环。JDK1.7中的HashMap使用头插法插入元素,在多线程的环境下,扩容的时候有可能导致环形链表的出现,形成死循环。因此JDK1.8使用尾插法插入元素,在扩容时会保持链表元素原本的顺序,不会出现环形链表的问题
  • 多线程的put可能导致元素的丢失。多线程同时执行put操作,如果计算出来的索引位置是相同的,那会造成前一个key被后一个key覆盖,从而导致元素的丢失。此问题在JDK1.7和JDK1.8中都存在
  • put和get并发时,可能导致get为null。线程1执行put时,因为元素个数超出threshold而导致rehash,线程2此时执行get,有可能导致这个问题,此问题在JDK1.7和JDK1.8中都存在

链接:hashmap14问

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值