HashMap面试题

HashMap的特性?

  • HashMap存储键值对实现快速存取,键-值(key-value)都允许为null,key不可重复,如重复则覆盖,并返回oldValue。
  • 非同步,线程不安全
  • 底层是hash表,不保证有序(插入顺序和存储顺序不同)

HashMap结构?

JDK1.7中的HashMap是由两种数据结构组成:数组 + 链表
在这里插入图片描述
JDK1.8中的HashMap是由三种数据结构组成:数组 + ( 链表 or 红黑树 )

在这里插入图片描述

二维结构。第一维是数组,第二维是链表

HashMap的扩容机制?put()是如何实现的?

Step1. 计算key的hashCode值

Step2. 如果hash表为空,调用resize()初始化hash表

Step3. 如果没有发生hash碰撞(计算出的数组下标对应的桶中有元素时),将元素直接添加到hash表中

Step4. 如果发生了hash碰撞(key的hashCode值相同),则进行三种判断

​ 1). 如果key相同(地址相同或equals内容相同),则进行值覆盖

​ 2). 如果为红黑树结构,调用红黑树的插入方法

​ 3). 如果为链表结构,尾插法进行插入,插入之后判断链表中元素个数是否超过转换成红黑树的阈值,遍历所有节点与插入元素key是否相同,相同则进行覆盖。

Step5. 如果hash表中元素个数大于阈值(capacity * load factor),则进行扩容。

HashMap的get()是如何实现的?

​ 对key的hashCode和数组长度length-1进行与运算,计算下标获取bucket位置,如果在桶的首位上就可以找到就直接返回,否则在树中找或者链表中遍历找,如果有hash冲突,则利用equals方法去遍历链表查找节点。

为什么HashMap数组长度必须为2的幂次方?

key的hashCode和数组长度减一做按位与运算得到index ,

让散列均匀分布, 减少hash碰撞

indexFor();

hash&(length-1) --> index

​ 只有当数组长度为2的幂次方的时候,我们进行数组长度减一操作才能拿到全部是1的值,这样进行按位与运算才能非常快速的拿到数组下标,并且分布式均匀的。

​ 如果数组长度不为2的幂次方,进行hash&(length-1)运算时,length-1得到的结果并不是每位都是1,在跟hash进行按位与运算的时候就会出现有些hash桶一直为空,造成严重的hash冲突。

Java1.7中hash();方法是为了抵御不良的hash函数造成的严重hash冲突.

key相同 ,值覆盖,返回oldValue;

threshold阈值=capacity*load , 每次扩容一倍 *2

为什么HashMap是线程不安全的?

​ 在没有外部同步的情况下,线程调度是随机的,谁先执行谁后执行都是随机的,HashMap在多线程环境下,两个线程同时修改HashMap结构,就有可能出现环形链表,这时候又恰巧在查找这个元素就会导致死循环。

​ HashMap1.7中扩容机制实际上是使用一个新的2倍于原来数组的来替换之前的数组,调用resize方法,resize方法里面调用的是transfer方法,用来转移数据到新的数组中,采用的是头插法,并发下操作容易在一个桶上形成闭环链表,这时如果调用get()方法获取一个不存在的key时,计算出的index正好是环形链表的下标就会出现死循环。

为什么HashMap链表转换红黑树的阈值是8?

​ 因为每个桶里所含元素的个数服从参数为0.5的泊松分布,一个桶中超过8的概率小于千万分之一,所以选择8作为阈值。

HashMap在JDK1.8相对于JDK1.7有什么改进?

1.7底层使用数组加链表实现,头插法(如果rehash数组下标形同,元素顺序会倒置)

1.8底层使用数组加链表|红黑树实现,尾插法(元素顺序不会倒置)

  • 改进了扩容时的插入顺序

  • 增加了函数方法forEach

1). 数组查找的时间复杂度为o(1),链表的查询时间复杂度为o(n),红黑树的查询时间复杂度为o(log(n))

2). 当链表长度比较大时,时间复杂度为o(n),所以1.8中使用红黑树(平衡二叉树)实现Resize效率低,初始化时指定初始容量。

为什么JDK1.8中HashMap不直接使用红黑树?而使用红黑树加链表?

​ 红黑树需要进行左旋,右旋的操作,而链表不需要

如果元素个数小于8个,红黑树的查询成本高,新增成本低

如果元素个数大于8个,红黑树的查询成本低,新增成本高

为什么 HashMap 中 String、Integer 这样的包装类适合作为 key 键?

  • String,Integer等这些类都被final修饰,保证了key的不变性,并且其内部也重写了hashCode方法,不容易出现hash计算错误,有效减少了hash碰撞,所以HashMap的key一定要重写hashCode方法。

ConcurrentHashMap和Hashtable的区别?

​ 在 JDK7 ConcurrentHashMap 使用 Segment 分段锁的方式实现线程安全, 而在 JDK8 就抛弃这种做法, 采用 CAS 算法来保证线程安全

​ ConcurrentHashMap 是一个并发散列映射表,它允许完全并发的读取,并且支持给定数量的并发更新。
​ 而HashTable和同步包装器包装的 HashMap,使用一个全局的锁来同步不同线程间的并发访问(锁整张hash表),同一时间点,只能有一个线程持有锁,也就是说在同一时间点,只能有一个线程能访问容器,这虽然保证多线程间的安全并发访问,但同时也导致对容器的访问变成串行化的了。

​ Hashtable的任何操作都会把整个表锁住,是阻塞的。好处是总能获取最实时的更新,比如说线程A调用putAll写入大量数据,期间线程B调用get,线程B就会被阻塞,直到线程A完成putAll,因此线程B肯定能获取到线程A写入的完整数据。坏处是所有调用都要排队,效率较低。

​ ConcurrentHashMap 是设计为非阻塞的。在更新时会局部锁住某部分数据,但不会把整个表都锁住。同步读取操作则是完全非阻塞的。好处是在保证合理的同步前提下,效率很高。坏处是严格来说读取操作不能保证反映最近的更新。例如线程A调用putAll写入大量数据,期间线程B调用get,则只能get到目前为止已经顺利插入的部分数据。

应该根据具体的应用场景选择合适的HashMap。

二叉树

节点的度(Degeree):节点的子树个数

树的度: 树的所有节点中最大的度数

叶节点: 度为零,没有子节点

二叉树遍历

  • 先序遍历访问

    1. 根节点

    2. 递归先序遍历左节点

    3. 递归先序遍历右节点

  • 中序遍历

    1. 递归中序遍历左节点

    2. 根节点

    3. 递归中序遍历右节点

  • 后序遍历

    1. 递归后序遍历左节点

    2. 递归后序遍历右节点

    3. 根节点

  • 层序遍历

二叉搜索树

​ 二叉搜索树: 一颗二叉树可以为空,如果不为空,满足一下性质:

  • 非空左子树的所有键值小于其根节点的键值
  • 非空右子树的所有键值大于其根节点的键值
  • 左右子树都是二叉搜索树

平衡二叉树

左右两边的高度差不大于1

红黑树

红黑树是自平衡的二叉树

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
回答: HashMapJava中的一个常用数据结构,它的底层是由hash数组和单向链表实现的。每个数组元素都是一个链表,通过Node内部类实现了Map.Entry接口来存储键值对。HashMap通过put和get方法来存储和获取数据。\[1\] 在重写equals方法时,我们需要同时重写hashCode方法。这是因为在HashMap中,查找value是通过key的hashCode来进行的。当找到对应的hashCode后,会使用equals方法来比较传入的对象和HashMap中的key对象是否相同。因此,为了保证正确的查找和比较,我们需要同时重写equals和hashCode方法。\[2\]\[3\] HashMap在什么时候进行扩容呢?当HashMap中的元素数量超过了负载因子(默认为0.75)与当前容量的乘积时,就会进行扩容。扩容是为了保持HashMap的性能,因为当元素数量过多时,链表的长度会变长,查找效率会下降。扩容的过程是创建一个新的数组,将原数组中的元素重新分配到新数组中,然后将新数组替换为原数组。\[3\] #### 引用[.reference_title] - *1* *2* *3* [史上最全Hashmap面试总结,51道附带答案,持续更新中...](https://blog.csdn.net/androidstarjack/article/details/124507171)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值