热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

问遍了身边的面试官朋友,我整理出这份Java集合高频面试题(2022年最新版)

微信搜索【程序员囧辉】,关注这个坚持分享技术干货的程序员。我的最新文章:百万级QPS,支撑淘宝双11商品浏览需要哪些技术前言大家好&#

微信搜索【程序员囧辉】,关注这个坚持分享技术干货的程序员。

我的最新文章:百万级QPS,支撑淘宝双11商品浏览需要哪些技术

前言

大家好,我是囧辉,面试系列开篇:Java 基础高频面试题(2021年最新版),发表后受到不少同学的喜欢。

今天我们继续下一个重要的面试内容:集合框架。HashMap 作为 Java 中最靓的仔,毋庸置疑将是本文的主角。

可能有些同学看过我之前的 HashMap 文章:面试阿里,HashMap 这一篇就够了,会想:辉哥果然又颓废了、堕落了,估计是将之前的内容就照搬过来水一篇,鄙视,取关,不看也罢,*()&*……&*。

你们不对劲,你辉哥是这种人?当然不是的,本文的 HashMap 部分在之前的基础上进行了补充和完善,希望大家能看到完善的点。

最后,本文按 BAT 面试标准给出解析,希望在这金三银四的日子里,祝你一臂之力,拿下大厂 Offer。


面试系列

我自己前前后后加起来总共应该参加了不下四五十次的面试,拿到过几乎所有一线大厂的 offer:阿里、字节、美团、快手、拼多多等等。

每次面试后我都会将面试的题目进行记录,并整理成自己的题库,最近我将这些题目整理出来,并按大厂的标准给出自己的解析,希望在这金三银四的季节里,能助你一臂之力。

面试文章持续更新中... 

内容链接地址
面试经验分享921天,从小厂到入职阿里
两年Java开发工作经验面试总结
4 年 Java 经验,阿里网易拼多多面试总结、心得体会
5 年 Java 经验,字节、美团、快手核心部门面试总结(真题解析)
复习2个月拿下美团offer,我都做了些啥 
如何准备好一场大厂面试 
简历如何写一份让 HR 眼前一亮的简历(附模板)
Offer 选择跳槽,如何选择一家公司
Java 基础Java 基础高频面试题(2021年最新版)
一道有意思的“初始化”面试题
集合(HashMap)Java 集合框架高频面试题(2021年最新版)
面试阿里,HashMap 这一篇就够了
并发编程面试必问的线程池,你懂了吗?
面试必问的CAS,你懂了吗?
MySQL面试必问的 MySQL,你懂了吗?
MySQL 8.0 MVCC 核心原理解析(核心源码)
Spring面试必问的 Spring,你懂了吗?
Mybatis面试题:mybatis 中的 DAO 接口和 XML 文件里的 SQL 是如何建立关系的?
Redis面试必问的缓存使用:如何保证数据一致性、缓存设计模式
面试必问的 Redis:Memcached VS Redis
面试必问的 Redis:高可用解决方案哨兵、集群
面试必问的 Redis:主从复制
面试必问的 Redis:RDB、AOF、混合持久化
面试必问的 Redis:数据结构和基础概念
JVMJava虚拟机面试题精选(二)
Java虚拟机面试题精选(一)
分布式面试必问的分布式锁,你懂了吗?
算法位图法:判断一个数是否在40亿个整数中?

正文

1、介绍下 HashMap 的底层数据结构吧。

在 JDK 1.8,HashMap 底层是由 “数组+链表+红黑树” 组成,如下图所示,而在 JDK 1.8 之前是由 “数组+链表” 组成,就是下图去掉红黑树。


3、为什么要改成“数组+链表+红黑树”?

通过上题可以看出,“数组+链表” 已经充分发挥了这两种数据结构的优点,是个很不错的组合了。

但是这种组合仍然存在问题,就是在定位到索引位置后,需要先遍历链表找到节点,这个地方如果链表很长的话,也就是 hash 冲突很严重的时候,会有查找性能问题,因此在 JDK1.8中,通过引入红黑树,来优化这个问题。

使用链表的查找性能是 O(n),而使用红黑树是 O(logn)。


4、那在什么时候用链表?什么时候用红黑树?

对于插入,默认情况下是使用链表节点。当同一个索引位置的节点在新增后超过8个(阈值8):如果此时数组长度大于等于 64,则会触发链表节点转红黑树节点(treeifyBin);而如果数组长度小于64,则不会触发链表转红黑树,而是会进行扩容,因为此时的数据量还比较小。

对于移除,当同一个索引位置的节点在移除后达到 6 个(阈值6),并且该索引位置的节点为红黑树节点,会触发红黑树节点转链表节点(untreeify)。


5、为什么链表转红黑树的阈值是8?

我们平时在进行方案设计时,必须考虑的两个很重要的因素是:时间和空间。对于 HashMap 也是同样的道理,简单来说,阈值为8是在时间和空间上权衡的结果(这 B 我装定了)。

JDK1.8:底层结构为:数组+链表+红黑树;实现线程安全的方式:CAS + Synchronized

区别:

1、JDK1.8 中降低锁的粒度。JDK1.7 版本锁的粒度是基于 Segment 的,包含多个节点(HashEntry),而 JDK1.8 锁的粒度就是单节点(Node)。

2、JDK1.8 版本的数据结构变得更加简单,使得操作也更加清晰流畅,因为已经使用 synchronized 来进行同步,所以不需要分段锁的概念,也就不需要 Segment 这种数据结构了,当前还保留仅为了兼容。

3、JDK1.8 使用红黑树来优化链表,跟 HashMap 一样,优化了极端情况下,链表过长带来的性能问题。

4、JDK1.8 使用内置锁 synchronized 来代替重入锁 ReentrantLock,synchronized 是官方一直在不断优化的,现在性能已经比较可观,也是官方推荐使用的加锁方式。


23、ConcurrentHashMap 的并发扩容

ConcurrentHashMap 在扩容时会计算出一个步长(stride),最小值是16,然后给当前扩容线程分配“一个步长”的节点数,例如16个,让该线程去对这16个节点进行扩容操作(将节点从老表移动到新表)。

如果在扩容结束前又来一个线程,则也会给该线程分配一个步长的节点数让该线程去扩容。依次类推,以达到多线程并发扩容的效果。

例如:64要扩容到128,步长为16,则第一个线程会负责第113(索引112)~128(索引127)的节点,第二个线程会负责第97(索引96)~112(索引111)的节点,依次类推。

具体处理(该流程后续可能会替换成流程图):

1)如果索引位置上为null,则直接使用 CAS 将索引位置赋值为 ForwardingNode(hash值为-1),表示已经处理过,这个也是触发并发扩容的关键点。

2)如果索引位置的节点 f 的 hash 值为 MOVED(-1),则代表节点 f 是 ForwardingNode 节点,只有 ForwardingNode 的 hash 值为 -1,意味着该节点已经处理过了,则跳过该节点继续往下处理。

3).否则,对索引位置的节点 f 对象使用 synchronized 进行加锁,遍历链表或红黑树,处理每个一节点,这边和 HashMap 的扩容类似,会构造出2个链表:ln(索引位置为原索引位置)、hn(索引位置为:原索引位置+老表容量),处理完该位置的节点后,最后将 ln 和 hn 放到对应位置,然后继续处理下一个索引位置。

ForwardingNode:一个特殊的 Node 节点,hash 值为-1(源码中定义成 MOVED),其中存储 nextTable 的引用。 只有发生扩容的时候,ForwardingNode才会发挥作用,作为一个占位符放在 table 中表示当前节点已经被处理(或则为 null )。


24、ConcurrenHashMap 和 Hashtable 的区别?

1)底层数据结构: 

ConcurrentHashMap:1)JDK1.7 采用 分段的数组+链表 实现;2)JDK1.8 采用 数组+链表+红黑树,跟 JDK1.8 的 HashMap 的底层数据结构一样。

Hashtable: 采用 数组+链表 的形式,跟 JDK1.8 之前的 HashMap 的底层数据结构类似。

2)实现线程安全的方式(重要):

ConcurrentHashMap:

1)JDK1.7:使用分段锁(Segment)保证线程安全,每个分段(Segment)包含若干个 HashEntry,当并发访问不同分段的数据时,不会产生锁竞争,从而提升并发性能。

2)JDK1.8:使用 synchronized + CAS 的方式保证线程安全,每次只锁一个节点(Node),进一步降低锁粒度,降低锁冲突的概率,从而提升并发性能。

Hashtable:使用 synchronized 修饰方法来保证线程安全,每个实例对象只有一把锁,并发性能较低,相当于串行访问。


25、ConcurrentHashMap 的 size() 方法怎么实现的?

JDK 1.7:先尝试在不加锁的情况下尝进行统计 size,最多统计3次,如果连续两次统计之间没有任何对 segment 的修改操作,则返回统计结果。否则,对每个segment 进行加锁,然后统计出结果,返回结果。

JDK 1.8:直接统计 baseCount 和 counterCells 的 value 值,返回的是一个近似值,如果有并发的插入或删除,实际的数量可能会有所不同。

该统计方式改编自 LongAdder 和 Striped64,这两个类在 JDK 1.8 中被引入,出自并发大神 Doug Lea 之手,是原子类(AtomicLong 等)的优化版本,主要优化了在并发竞争下,AtomicLong 由于 CAS 失败的带来的性能损耗。

值得注意的是,JDK1.8中,提供了另一个统计的方法 mappingCount,实现和 size 一样,只是返回的类型改成了 long,这也是官方推荐的方式。

public int size() {long n &#61; sumCount();return ((n <0L) ? 0 :(n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :(int)n);
}
// 一个ConcurrentHashMap包含的映射数量可能超过int上限&#xff0c;
// 所以应该使用这个方法来代替size()
public long mappingCount() {long n &#61; sumCount();return (n <0L) ? 0L : n; // ignore transient negative values
}
final long sumCount() {CounterCell[] as &#61; counterCells; CounterCell a;long sum &#61; baseCount;if (as !&#61; null) {for (int i &#61; 0; i }

26、比较下常见的几种 Map&#xff0c;在使用时怎么选择&#xff1f;


30、ArrayList 和 Vector 的区别。

Vector 和 ArrayList 的实现几乎是一样的&#xff0c;区别在于&#xff1a;

1&#xff09;最重要的的区别&#xff1a; Vector 在方法上使用了 synchronized 来保证线程安全&#xff0c;同时由于这个原因&#xff0c;在性能上 ArrayList 会有更好的表现。

2&#xff09; Vector 扩容后容量默认变为原来 2 倍&#xff0c;而 ArrayList 为原来的 1.5 倍。

有类似关系的还有&#xff1a;StringBuilder 和 StringBuffer、HashMap 和 Hashtable。


31、ArrayList 和 LinkedList 的区别&#xff1f;

1、ArrayList 底层基于动态数组实现&#xff0c;LinkedList 底层基于双向链表实现。

2、对于随机访问&#xff08;按 index 访问&#xff0c;get/set方法&#xff09;&#xff1a;ArrayList 通过 index 直接定位到数组对应位置的节点&#xff0c;而 LinkedList需要从头结点或尾节点开始遍历&#xff0c;直到寻找到目标节点&#xff0c;因此在效率上 ArrayList 优于 LinkedList。

3、对于随机插入和删除&#xff1a;ArrayList 需要移动目标节点后面的节点&#xff08;使用System.arraycopy 方法移动节点&#xff09;&#xff0c;而 LinkedList 只需修改目标节点前后节点的 next 或 prev 属性即可&#xff0c;因此在效率上 LinkedList 优于 ArrayList。

4、对于顺序插入和删除&#xff1a;由于 ArrayList 不需要移动节点&#xff0c;因此在效率上比 LinkedList 更好。这也是为什么在实际使用中 ArrayList 更多&#xff0c;因为大部分情况下我们的使用都是顺序插入。

5、两者都不是线程安全的。

6、内存空间占用&#xff1a; ArrayList 的空 间浪费主要体现在在 list 列表的结尾会预留一定的容量空间&#xff0c;而 LinkedList 的空间花费则体现在它的每一个元素都需要消耗比 ArrayList 更多的空间&#xff08;因为要存放直接后继和直接前驱以及数据&#xff09;。


32、HashSet 是如何保证不重复的&#xff1f;

HashSet 底层使用 HashMap 来实现&#xff0c;见下面的源码&#xff0c;元素放在 HashMap 的 key 里&#xff0c;value 为固定的 Object 对象。当 add 时调用 HashMap 的 put 方法&#xff0c;如果元素不存在&#xff0c;则返回 null 表示 add 成功&#xff0c;否则 add 失败。

由于 HashMap 的 Key 值本身就不允许重复&#xff0c;HashSet 正好利用 HashMap 中 key 不重复的特性来校验重复元素&#xff0c;简直太妙了。

private transient HashMap map;// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT &#61; new Object();public boolean add(E e) {return map.put(e, PRESENT)&#61;&#61;null;
}

33、TreeSet 清楚吗&#xff1f;能详细说下吗&#xff1f;

“TreeSet 和 TreeMap 的关系” 和上面说的 “HashSet 和 HashMap 的关系” 几乎一致。

TreeSet 底层默认使用 TreeMap 来实现。而 TreeMap 通过实现 Comparator&#xff08;或 Key 实现 Comparable&#xff09;来实现自定义顺序。

private transient NavigableMap m;
private static final Object PRESENT &#61; new Object();TreeSet(NavigableMap m) {this.m &#61; m;
}
public TreeSet() {this(new TreeMap());
}public boolean add(E e) {return m.put(e, PRESENT)&#61;&#61;null;
}

34、介绍下 CopyOnWriteArrayList&#xff1f;

CopyOnWriteArrayList 是 ArrayList 的线程安全版本&#xff0c;也是大名鼎鼎的 copy-on-write&#xff08;COW&#xff0c;写时复制&#xff09;的一种实现。

在读操作时不加锁&#xff0c;跟ArrayList类似&#xff1b;在写操作时&#xff0c;复制出一个新的数组&#xff0c;在新数组上进行操作&#xff0c;操作完了&#xff0c;将底层数组指针指向新数组。适合使用在读多写少的场景。

例如 add(E e) 方法的操作流程如下&#xff1a;使用 ReentrantLock 加锁&#xff0c;拿到原数组的length&#xff0c;使用 Arrays.copyOf 方法从原数组复制一个新的数组&#xff08;length&#43;1&#xff09;&#xff0c;将要添加的元素放到新数组的下标length位置&#xff0c;最后将底层数组指针指向新数组。


35、Comparable 和 Comparator 比较&#xff1f;

1、Comparable 是排序接口&#xff0c;一个类实现了 Comparable接口&#xff0c;意味着“该类支持排序”。Comparator 是比较器&#xff0c;我们可以实现该接口&#xff0c;自定义比较算法&#xff0c;创建一个 “该类的比较器” 来进行排序。

2、Comparable 相当于“内部比较器”&#xff0c;而 Comparator 相当于“外部比较器”。

3、Comparable 的耦合性更强&#xff0c;Comparator 的灵活性和扩展性更优。

4、Comparable 可以用作类的默认排序方法&#xff0c;而 Comparator 则用于默认排序不满足时&#xff0c;提供自定义排序。

耦合性和扩展性的问题&#xff0c;举个简单的例子&#xff1a;

当实现类实现了 Comparable 接口&#xff0c;但是已有的 compareTo 方法的比较算法不满足当前需求&#xff0c;此时如果想对两个类进行比较&#xff0c;有两种办法&#xff1a;

1&#xff09;修改实现类的源代码&#xff0c;修改 compareTo 方法&#xff0c;但是这明显不是一个好方案&#xff0c;因为这个实现类的默认比较算法可能已经在其他地方使用了&#xff0c;此时如果修改可能会造成影响&#xff0c;所以一般不会这么做。

2&#xff09;实现 Comparator 接口&#xff0c;自定义一个比较器&#xff0c;该方案会更优&#xff0c;自定义的比较器只用于当前逻辑&#xff0c;其他已有的逻辑不受影响。


36、List、Set、Map三者的区别?

List&#xff08;对付顺序的好帮手&#xff09;&#xff1a; 存储的对象是可重复的、有序的。

Set&#xff08;注重独一无二的性质&#xff09;&#xff1a;存储的对象是不可重复的、无序的。

Map&#xff08;用 Key 来搜索的专业户&#xff09;: 存储键值对&#xff08;key-value&#xff09;&#xff0c;不能包含重复的键&#xff08;key&#xff09;&#xff0c;每个键只能映射到一个值。


37、Map、List、Set 分别说下你了解到它们有的线程安全类和线程不安全的类&#xff1f;

Map

线程安全&#xff1a;CocurrentHashMap、Hashtable

线程不安全&#xff1a;HashMap、LinkedHashMap、TreeMap、WeakHashMap

List

线程安全&#xff1a;Vector&#xff08;线程安全版的ArrayList&#xff09;、Stack&#xff08;继承Vector&#xff0c;增加pop、push方法&#xff09;、CopyOnWriteArrayList

线程不安全&#xff1a;ArrayList、LinkedList

Set

线程安全&#xff1a;CopyOnWriteArraySet&#xff08;底层使用CopyOnWriteArrayList&#xff0c;通过在插入前判断是否存在实现 Set 不重复的效果&#xff09;

线程不安全&#xff1a;HashSet&#xff08;基于 HashMap&#xff09;、LinkedHashSet&#xff08;基于 LinkedHashMap&#xff09;、TreeSet&#xff08;基于 TreeMap&#xff09;、EnumSet


38、Collection 与 Collections的区别

Collection&#xff1a;集合类的一个顶级接口&#xff0c;提供了对集合对象进行基本操作的通用接口方法。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式&#xff0c;常见的 List 与 Set 就是直接继承 Collection 接口。

Collections&#xff1a;集合类的一个工具类/帮助类&#xff0c;提供了一系列静态方法&#xff0c;用于对集合中元素进行排序、搜索以及线程安全等各种操作。


最后

面试系列持续创作中&#xff0c;有疑问的地方欢迎留言&#xff0c;我看到后都会回复。

原创不易&#xff0c;如果你觉得本文写的还不错&#xff0c;对你有帮助&#xff0c;请通过【点赞】和【关注】让我知道&#xff0c;支持我写出更好的文章。

我是囧辉&#xff0c;我的目标是帮助大家拿到心仪的大厂 Offer&#xff0c;我们下期见。


推荐阅读
  • 一次上线事故,30岁+的程序员踩坑经验之谈
    本文主要介绍了一位30岁+的程序员在一次上线事故中踩坑的经验之谈。文章提到了在双十一活动期间,作为一个在线医疗项目,他们进行了优惠折扣活动的升级改造。然而,在上线前的最后一天,由于大量数据请求,导致部分接口出现问题。作者通过部署两台opentsdb来解决问题,但读数据的opentsdb仍然经常假死。作者只能查询最近24小时的数据。这次事故给他带来了很多教训和经验。 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • 本文介绍了Java工具类库Hutool,该工具包封装了对文件、流、加密解密、转码、正则、线程、XML等JDK方法的封装,并提供了各种Util工具类。同时,还介绍了Hutool的组件,包括动态代理、布隆过滤、缓存、定时任务等功能。该工具包可以简化Java代码,提高开发效率。 ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • 本文介绍了前端人员必须知道的三个问题,即前端都做哪些事、前端都需要哪些技术,以及前端的发展阶段。初级阶段包括HTML、CSS、JavaScript和jQuery的基础知识。进阶阶段涵盖了面向对象编程、响应式设计、Ajax、HTML5等新兴技术。高级阶段包括架构基础、模块化开发、预编译和前沿规范等内容。此外,还介绍了一些后端服务,如Node.js。 ... [详细]
  • 2022年的风口:你看不起的行业,真的很挣钱!
    本文介绍了2022年的风口,探讨了一份稳定的副业收入对于普通人增加收入的重要性,以及如何抓住风口来实现赚钱的目标。文章指出,拼命工作并不一定能让人有钱,而是需要顺应时代的方向。 ... [详细]
  • 本文介绍了操作系统的定义和功能,包括操作系统的本质、用户界面以及系统调用的分类。同时还介绍了进程和线程的区别,包括进程和线程的定义和作用。 ... [详细]
  • MySQL中的MVVC多版本并发控制机制的应用及实现
    本文介绍了MySQL中MVCC的应用及实现机制。MVCC是一种提高并发性能的技术,通过对事务内读取的内存进行处理,避免写操作堵塞读操作的并发问题。与其他数据库系统的MVCC实现机制不尽相同,MySQL的MVCC是在undolog中实现的。通过undolog可以找回数据的历史版本,提供给用户读取或在回滚时覆盖数据页上的数据。MySQL的大多数事务型存储引擎都实现了MVCC,但各自的实现机制有所不同。 ... [详细]
  • svnWebUI:一款现代化的svn服务端管理软件
    svnWebUI是一款图形化管理服务端Subversion的配置工具,适用于非程序员使用。它解决了svn用户和权限配置繁琐且不便的问题,提供了现代化的web界面,让svn服务端管理变得轻松。演示地址:http://svn.nginxwebui.cn:6060。 ... [详细]
  • Centos下安装memcached+memcached教程
    本文介绍了在Centos下安装memcached和使用memcached的教程,详细解释了memcached的工作原理,包括缓存数据和对象、减少数据库读取次数、提高网站速度等。同时,还对memcached的快速和高效率进行了解释,与传统的文件型数据库相比,memcached作为一个内存型数据库,具有更高的读取速度。 ... [详细]
  • 本文介绍了H5游戏性能优化和调试技巧,包括从问题表象出发进行优化、排除外部问题导致的卡顿、帧率设定、减少drawcall的方法、UI优化和图集渲染等八个理念。对于游戏程序员来说,解决游戏性能问题是一个关键的任务,本文提供了一些有用的参考价值。摘要长度为183字。 ... [详细]
  • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • 如何提高PHP编程技能及推荐高级教程
    本文介绍了如何提高PHP编程技能的方法,推荐了一些高级教程。学习任何一种编程语言都需要长期的坚持和不懈的努力,本文提醒读者要有足够的耐心和时间投入。通过实践操作学习,可以更好地理解和掌握PHP语言的特异性,特别是单引号和双引号的用法。同时,本文也指出了只走马观花看整体而不深入学习的学习方式无法真正掌握这门语言,建议读者要从整体来考虑局部,培养大局观。最后,本文提醒读者完成一个像模像样的网站需要付出更多的努力和实践。 ... [详细]
author-avatar
愁撒_651
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有