第一部分 基础概念(1-15 题)
1.Java 集合分为哪两大类
答:Java集合顶层分为Collection单列集合 和Map双列集合 两大类。
1、Collection(单列集合):存放单个独立元素,继承Iterable接口,支持迭代遍历,包含List、Set、Queue三大子体系,绝大多数实现类非线程安全;
2、Map(双列集合):存放Key-Value键值对映射关系,Key唯一不可重复、Value允许重复,不继承Iterable,不能直接foreach遍历;
补充:Java集合底层全部依托数组、链表、红黑树、跳表、堆五大数据结构实现。
2.Collection 和 Collections 区别
答:二者本质完全不同,一个是顶层接口 ,一个是工具类 。
1、Collection:是Java集合单列顶层根接口,派生List、Set、Queue,定义集合通用增删查改方法;
2、Collections:是java.util包下的集合工具类,静态方法居多,不可实例化;
补充:常用方法:排序、二分查找、集合反转、批量加锁、生成不可变集合,专门辅助操作集合。
3.List、Set、Queue 三者区别
答:三者都是Collection单列集合,核心特性、使用场景差异极大:
1、List(列表):有序、可重复、带有下标索引 ,支持根据下标精准查询,常用实现类:ArrayList、LinkedList;
2、Set(集合):无序、不可重复、无下标索引 ,主要用于数据去重,常用实现类:HashSet、LinkedHashSet、TreeSet;
3、Queue(队列):遵循先进先出FIFO规则 ,用于缓冲、排队、线程通信,常用实现类:ArrayDeque、阻塞队列;
补充:三者绝大多数实现类非线程安全,并发场景需使用JUC并发集合。
4.迭代器作用
答:统一集合遍历方式,支持遍历中安全删除元素。
5.foreach 循环底层原理
答:foreach 是Java增强for循环,底层完全基于迭代器Iterator实现 ,编译阶段编译器会自动生成迭代器代码。
1、语法简洁,无需手动定义索引、无需获取迭代器;
2、遍历过程中不允许修改集合元素个数 (增/删),否则抛出并发修改异常;
3、适用所有实现Iterable接口的单列集合,不支持普通数组下标操作、无法获取遍历下标。
6.fail-fast 快速失败原理
答:fail-fast 是集合的并发修改保护机制,用于防止集合在遍历过程中被非法修改。
1、集合内部维护一个 modCount 修改计数器 ,每次增删元素都会自增;
2、迭代器初始化时会记录当前修改次数 expectedModCount;
3、遍历期间一旦发现 modCount != expectedModCount,直接抛出 ConcurrentModificationException 并发修改异常;
补充:普通非线程安全集合(ArrayList、HashSet)默认采用该机制,**快速抛出异常、避免脏数据**。
7.fail-safe 安全失败原理
答:fail-safe 是安全失败遍历机制,多用于并发集合,遍历过程**不会抛出并发修改异常**。
1、遍历不直接操作原集合,会**拷贝一份数据快照**进行遍历读取;
2、采用读写分离思想:读操作访问快照,写操作修改原集合;
3、遍历期间无法感知集合最新改动,存在数据弱一致性 ;
补充:典型实现类:CopyOnWriteArrayList、CopyOnWriteArraySet,适合读多写少的并发场景。
8.isEmpty 和 size ()==0 区别
答:功能一致,isEmpty 执行效率更高,优先使用。
9.数组与集合区别
答:数组长度固定,集合长度可动态扩容;数组可存基本类型与对象,集合只存对象。
10.泛型作用
答:泛型是Java编译期类型约束机制,本质用于约束集合存储的数据类型。
1、保障类型安全 :编译阶段校验数据类型,防止存入错误数据;
2、避免强制类型转换 :取出元素无需手动强转,简化代码;
3、消除泛型擦除带来的隐患 :提高代码可读性、复用性;
补充:泛型只在编译期生效,运行期会发生泛型擦除,不存在泛型类型。
11.Iterable 和 Iterator 区别
答:二者为迭代相关核心接口,职责分工明确,缺一不可。
1、Iterable(可迭代接口):位于java.lang包,是集合顶层接口,内部仅有iterator()方法,用于生成迭代器 ;实现该接口代表当前集合允许被遍历;
2、Iterator(迭代器接口):位于java.util包,拥有hasNext()、next()、remove()方法,负责执行具体遍历逻辑 ,完成元素判断、获取、删除;
补充:foreach循环语法要求集合必须实现Iterable接口,这也是Map不能直接foreach遍历的根本原因。
12.ListIterator 特点
答:支持正向、反向双向遍历,遍历中可修改集合元素。
13.同步集合与并发集合区别
答:二者均为线程安全集合,底层加锁机制、并发性能差距极大。
1、同步集合:采用全局独占锁 (synchronized),锁住整个集合,并发时所有线程互斥等待,吞吐量低、性能差;典型实现:Vector、HashTable、synchronizedList;
2、并发集合:采用细粒度锁 + CAS乐观锁 ,仅锁定局部节点,无锁竞争、读写分离,高并发吞吐量高、性能优异;典型实现:ConcurrentHashMap、CopyOnWriteArrayList;
补充:业务开发优先使用并发集合,淘汰老旧同步集合。
14.不可变集合特点
答:创建后无法增删改元素,天然线程安全。
15.Arrays.asList 常见坑
答:返回固定长度集合,不能新增删除;传入基本类型数组会出现解析异常。
第二部分 List 集合(16-35 题)
16. ArrayList 底层实现
答:ArrayList 底层基于可变长度的 Object 动态数组 实现,是 List 接口最常用实现类。
1、JDK1.7 之后采用懒加载机制 ,初始化创建空常量数组,不占用内存;
2、首次添加元素时自动扩容为默认容量10,后续按规则动态扩容;
3、内存地址连续,支持随机快速访问,增删需要后置位移复制;
补充:非线程安全,适合查询多、增删少、单线程业务场景。
17.ArrayList 扩容机制
答:ArrayList 采用1.5倍动态扩容机制 ,底层依靠数组拷贝完成数据迁移。
1、JDK1.7及以上采用懒加载,初始化为空数组,首次add添加元素时,自动初始化容量为10;
2、当集合元素个数size超过数组容量阈值,触发自动扩容,新容量为原容量的1.5倍;
3、底层调用Arrays.copyOf(),开辟新数组、拷贝旧数组全部元素,迁移完成后回收旧数组;
补充:数组扩容无法原地扩容,频繁扩容会产生大量无用数组、耗费性能;大数据量业务建议手动指定初始化容量,规避扩容损耗。
18.ArrayList 默认空数组原因
答:懒加载设计,初始化不占用内存,节省系统资源。
19. ArrayList 最大存储容量
答:Integer.MAX_VALUE - 8。
20. ArrayList 查询快增删慢原因
答:内存连续查询快;中间位置增删需要大量移动元素,效率低。
21.LinkedList 底层结构
答:LinkedList 底层基于双向循环链表 实现,无固定容量、不涉及数组扩容机制。
1、内部定义Node节点类,包含item数据、prev前驱指针、next后继指针,双向指向节点;
2、维护头尾指针,可快速定位首尾元素,物理内存不连续,仅靠指针关联节点;
3、无扩容机制,每新增一个元素单独创建节点,动态占用内存;
补充:不支持随机访问,没有索引快速定位,遍历只能从头尾节点开始查找。
22.LinkedList 优缺点
答:首尾增删效率极高,随机查询遍历效率极低。
23.ArrayList 与 LinkedList 区别
答:二者同为 List 实现类,底层结构不同,性能与使用场景差异明显。
1、底层结构:ArrayList 基于动态数组 ;LinkedList 基于双向链表 ;
2、查询性能:ArrayList 内存连续,支持随机索引访问,查询速度极快;LinkedList 无索引,只能逐一遍历,查询慢;
3、增删性能:ArrayList 中间增删需移动大量元素,效率低;LinkedList 仅修改指针引用,头尾增删速度快;
4、内存占用:ArrayList 数组预留冗余空间,存在内存浪费;LinkedList 节点保存指针,额外内存开销更大;
补充:日常开发查询多、遍历多优先用ArrayList;频繁头尾增删、极少查询选用LinkedList,二者均非线程安全。
24.Vector 集合特点
答:方法加锁线程安全,默认 2 倍扩容,性能低下,现已淘汰。
25.不推荐使用 Stack 原因
答:底层依赖 Vector 性能差,推荐使用 ArrayDeque 替代栈结构。
26.三种线程安全 List 集合
答:Java 提供三种线程安全的 List,分别为 Vector、synchronizedList、CopyOnWriteArrayList,底层加锁机制与性能差异巨大。
1、Vector:古老集合,所有方法加 synchronized 全局锁,锁粒度大、并发性能差,扩容2倍,生产基本淘汰;
2、Collections.synchronizedList:通过装饰器模式包装普通List,同样采用全局锁,迭代遍历依旧需要手动加锁,并发性能一般;
3、CopyOnWriteArrayList:JUC并发集合,采用写时复制+读写分离,无并发修改异常,读多写少场景性能优异;
补充:优先级排序:CopyOnWriteArrayList > synchronizedList > Vector,生产禁止使用Vector。
27.CopyOnWriteArrayList 核心原理
答:底层采用写时复制 + 读写分离 并发思想,是JUC包下线程安全的List集合。
1、读操作:直接读取原数组,无锁、并发读取效率极高;
2、写操作:添加、删除、修改时,先对原数组进行完整拷贝 生成新数组,在新数组中完成修改;
3、修改完成后将原数组引用指向新数组,写操作采用ReentrantLock可重入锁保证线程安全;
补充:遍历读取的是旧数组快照,不会抛出并发修改异常,存在数据弱一致性,只适合读多写少业务场景。
28.CopyOnWriteArrayList 缺点
答:频繁写入内存占用高,遍历数据存在弱一致性。
29.ArrayList 线程不安全表现
答:多线程并发添加出现元素覆盖、数组越界、数据丢失问题。
30.创建 ArrayList 指定初始化容量好处
答:提前分配内存,减少频繁扩容次数,提升运行效率。
31.List 集合遍历删除元素注意事项
答:仅迭代器可删除,禁止普通 for、foreach 循环删除元素。
32.List 集合快速去重方式
答:日常开发常用两种主流去重方式,分别适配有序、无序业务场景,简单高效。
1、LinkedHashSet 去重:利用其元素不可重复、保留插入顺序的特性,有序去重 ,代码简洁、无额外依赖;
2、Java8 Stream 流式去重:使用distinct()方法,底层基于hashCode+equals判断,写法极简,支持链式编程;
补充:HashSet去重会打乱顺序,若需保留原顺序优先使用LinkedHashSet;自定义对象去重必须重写hashCode和equals方法。
33.List 批量添加数据优化方案
答:批量添加数据核心优化思路为减少集合扩容次数、降低数组拷贝开销 ,提升批量写入性能。
1、提前预估批量数据总量,初始化集合时指定合理初始容量,规避多次自动扩容;
2、优先使用addAll()批量添加方法,底层优化拷贝逻辑,比循环add()效率更高;
3、大批量数据可采用分批写入,避免单次加载海量数据造成内存峰值过高;
补充:ArrayList每次扩容都会产生新数组、拷贝元素,大批量数据不指定容量会严重损耗性能,是开发高频优化点。
34.ArrayDeque 优势
答:高性能双端队列,替代栈与普通队列,不允许存储 null 值。
35.List 集合常用排序方式
答:开发中常用两种主流排序方式,适配普通对象、自定义对象,写法简洁、性能优异。
1、Collections工具类排序:使用Collections.sort(),支持自然排序与自定义Comparator比较器,底层为优化后的归并排序,适配JDK低版本;
2、Java8 Stream流式排序:通过sorted()方法排序,代码极简、链式编程,结合Lambda表达式简化自定义比较逻辑,可读性更强;
补充:排序建议优先使用Stream排序;自定义对象排序必须指定比较器,防止类型转换异常,且排序后不改变原集合顺序。
第三部分 Set 集合(36-55 题)
36.Set 集合通用特点
答:无序、元素不可重复、无下标索引。
37.HashSet 底层实现原理
答:HashSet 底层直接封装 HashMap ,本质是简化版的 HashMap,专为单列去重设计。
1、内部维护一个 HashMap 成员变量,存入集合的元素,最终作为 HashMap 的 Key 进行保存;
2、为节约内存,所有元素对应的 Value 统一存入一个静态空 Object 常量占位;
3、继承哈希表特性,无序、不可重复,查询存取速度快,不保证元素存储顺序;
补充:依赖 HashMap 的哈希机制完成去重,去重逻辑完全遵循 hashCode() + equals() 双重校验规则。
38.HashSet 去重核心原理
答:HashSet 依托哈希表机制 实现元素去重,采用 hashCode() + equals() 双重校验判定元素是否重复。
1、首先调用元素hashCode()方法计算哈希值,通过位运算确定数组下标;哈希值不同,直接判定为新元素,完成存入;
2、若哈希值相同,发生哈希冲突,进一步调用equals()方法做内容比对;
3、equals返回true判定元素重复,舍弃当前元素;返回false判定为不同元素,挂在链表尾部;
补充:先比哈希再比内容,**hashCode用于快速筛选、equals用于精准判定**;自定义对象必须手动重写这两个方法,否则去重失效。
39.自定义对象存入 Set 必须重写方法
答:必须重写 hashCode () 与 equals (),否则无法完成正确去重。
40.LinkedHashSet 特点
答:LinkedHashSet 继承自 HashSet,是有序且不可重复 的集合,底层依托 LinkedHashMap 实现。
1、底层结构:哈希表 + 双向链表,额外维护一条双向链表记录元素插入顺序;
2、排序特性:保留元素插入顺序 ,不具备自动排序能力,存取顺序一致;
3、去重规则:完全沿用HashSet的hashCode()+equals()双重校验去重机制;
补充:性能略低于HashSet,链表会产生额外内存开销;适合需要保留插入顺序+数据去重的业务场景。
41.TreeSet 底层原理
答:TreeSet 底层直接封装 TreeMap ,是具备自动排序能力的单列集合,底层依托红黑树数据结构实现。
1、内部维护TreeMap对象,存入元素作为TreeMap的Key,Value统一使用静态空对象占位;
2、底层基于红黑树 ,具备自动平衡、天然排序、查询效率高的特点,时间复杂度O(logN);
3、不保留插入顺序,根据元素规则自动升序排序,依赖比较器判定元素大小与重复;
补充:非线程安全,不允许存入null元素,排序方式分为自然排序、自定义比较器排序,去重依据compareTo()方法返回值。
42.TreeSet 两种排序方式
答:TreeSet 包含自然排序(Comparable) 与自定义比较器排序(Comparator) 两种排序方式,优先级区分明确。
1、自然排序:元素实体类实现Comparable接口,重写compareTo()方法,定义默认比较规则,适用于固定排序逻辑;若未实现接口直接存入,会抛出类型转换异常;
2、自定义比较器排序:创建TreeSet时传入Comparator外部比较器,重写compare()方法,灵活定制排序规则,无需修改元素实体类源码;
补充:优先级规则:外部比较器Comparator 高于 内部比较器Comparable;TreeSet依靠比较方法返回值判定重复,返回0即判定元素重复。
43.TreeSet 去重判定依据
答:compareTo 方法返回 0 即判定元素重复。
44.Set 集合快速选型
答:开发中根据去重、顺序、排序 三大业务需求快速选型,三款常用Set实现类分工明确、界限清晰。
1、HashSet:底层哈希表,无序无排序,存取速度最快;仅做单纯数据去重、不关心元素顺序时优先使用,日常开发使用率最高;
2、LinkedHashSet:哈希表+双向链表,保留插入顺序;需有序去重 、保证存取顺序一致场景选用,性能略低于HashSet;
3、TreeSet:底层红黑树,自带自然排序;无需手动排序、要求元素按规则升序排列时选用,支持自定义比较器定制排序逻辑;
补充:三者均非线程安全;并发去重场景优先使用CopyOnWriteArraySet,禁止直接使用普通Set。
45.EnumSet 集合特点
答:EnumSet 是专为枚举类量身定制 的专用Set集合,也是所有Set集合中性能最优、内存占用最小的实现类。
1、存储限制:仅允许存放枚举类型元素,不支持存入其他任意类型对象,类型约束极强;
2、底层结构:底层采用位向量实现,占用极小内存,运算效率极高,远超HashSet、TreeSet;
3、排序特性:天然按照枚举定义顺序排序,有序且不可重复,无哈希冲突、无树结构开销;
补充:非线程安全,并发场景需手动同步;日常开发枚举批量筛选、遍历场景优先选用,不存放枚举绝不使用。
46.Set 集合允许存储 null 值吗
答:HashSet 允许存储一个 null,TreeSet 不允许存储 null。
47.TreeSet 禁止 null 原因
答:存入 null 调用比较方法直接触发空指针异常。
48.HashSet 是否线程安全
答:非线程安全,高并发环境禁止直接使用。
49.Java 中有序 Set 实现类
答:LinkedHashSet。
50.HashSet 默认初始化参数
答:HashSet 底层封装 HashMap,初始化参数完全沿用 HashMap 底层常量。
1、默认初始容量:16,集合底层哈希表默认数组长度,且容量必须保持2的幂次方;
2、默认负载因子:0.75,作为扩容阈值,当元素占用量达到容量75%触发扩容;
3、默认扩容倍数:2倍,每次扩容容量翻倍,重新哈希散列迁移元素;
补充:负载因子0.75是时间与空间平衡值,兼顾哈希冲突概率与内存利用率,生产不建议随意修改。
51.负载因子 0.75 设计优势
答:负载因子0.75是开发者权衡哈希冲突概率、内存空间利用率 得出的最优临界值。
1、若负载因子过大(如1.0):数组填满才扩容,极度节省内存,但哈希冲突概率剧增,链表长度变长,查询效率大幅下降;
2、若负载因子过小(如0.5):提前频繁扩容,减少哈希冲突,但大量数组空间闲置,造成严重内存浪费;
3、0.75平衡临界点:兼顾内存利用率与哈希冲突概率,散列分布均匀,时间复杂度与空间复杂度达到最优平衡;
补充:该数值为源码固定常量,生产环境不建议手动修改,改动会破坏哈希表最优性能结构。
52.负载因子大小带来的影响
答:数值越大冲突越多,数值越小内存浪费越多。
53.Set 集合主流遍历方式
答:日常开发中Set集合共有三种主流遍历方式,适配不同业务场景,无下标不可通过普通for循环遍历。
1、迭代器遍历:通过iterator()获取迭代器,手动判断遍历、获取元素,遍历过程中可安全删除元素,兼容性最强;
2、foreach遍历:语法简洁、代码极简,底层基于迭代器实现,适合简单遍历;禁止遍历中增删元素,防止触发并发修改异常;
3、Stream流式遍历:JDK8新增特性,结合Lambda表达式,支持过滤、排序、统计等链式操作,代码优雅可读性高;
补充:优先推荐foreach与Stream遍历;需要遍历删除元素强制使用迭代器,杜绝非法修改引发异常。
54.Set 集合转 List 集合写法
答:开发中常用三种Set转List方式,适配不同JDK版本,语法简洁、应用场景区分明确。
1、构造方法转换:new ArrayList<>(set),最简单通用,底层批量拷贝元素,适合绝大多数普通转换场景;
2、Java8 Stream流转:set.stream().collect(Collectors.toList()),支持转换中途过滤、排序、去重等链式操作,灵活性最高;
3、Collections工具类:Collections.addAll(),适合手动指定固定容量集合,减少扩容开销;
补充:转换后无序Set变为有序List,顺序取决于原Set底层结构;生产优先使用构造方法,需要加工数据优先使用Stream流转。
55.集合排序优先级
答:Java集合中存在两种比较排序接口,明确外部比较器Comparator 优先级高于 内部比较器Comparable 。
1、Comparable:内部比较器,绑定实体类内部,重写compareTo()方法,定义固定默认排序规则,侵入实体类源码;
2、Comparator:外部比较器,独立于实体类之外,重写compare()方法,灵活临时修改排序规则,无需改动源码;
3、优先级执行逻辑:若同时存在两种比较规则,优先采用外部Comparator比较逻辑,内部Comparable规则会被覆盖;
补充:开发固定排序用Comparable,临时多变排序优先用Comparator,TreeSet、TreeMap均遵循该优先级规则。
第四部分 Queue 队列集合(56-70 题)
56.队列核心特点
答:遵循先进先出 FIFO 原则。
57.ArrayDeque 核心特点
答:数组实现双端队列,性能强悍,禁止存储 null 元素。
58.PriorityQueue 优先级队列原理
答:PriorityQueue 是无界优先级队列 ,底层基于可变长度数组实现的最小堆 ,可自动按照优先级排序元素。
1、底层结构:采用动态数组存储堆节点,物理结构为数组,逻辑结构为完全二叉树,默认构建最小堆;
2、排序规则:默认自然升序排序,堆顶为最小值;支持自定义Comparator比较器,灵活实现最大堆;
3、存取逻辑:入队自动上浮调整堆结构,出队移除堆顶元素后自动下沉重构堆,保证堆顶永远为极值;
补充:非线程安全、不允许存入null元素,不保证遍历顺序;适合任务权重排序、定时优先级处理场景,并发优先级推荐使用PriorityBlockingQueue。
59.阻塞队列核心作用
答:实现生产者消费者模型,队列满自动阻塞、队列空自动阻塞唤醒。
60.Java 七大阻塞队列
答:JUC包下提供七大阻塞队列,全部实现BlockingQueue接口,天然支持生产者消费者阻塞模型,常用于线程池、消息缓冲、流量削峰。
1、ArrayBlockingQueue:有界数组阻塞队列 ,固定容量、单锁机制,读写共用一把锁,并发性能一般,公平锁可选;
2、LinkedBlockingQueue:无界链表阻塞队列 ,默认无上限、双锁机制,读写分离锁,并发吞吐量高,线程池默认常用队列;
3、SynchronousQueue:无容量同步队列 ,不存储元素,一对一线程传递,无缓冲,适合瞬时高并发任务;
4、DelayQueue:延时无界阻塞队列 ,基于优先级堆,元素到期才能取出,用于订单超时、定时任务;
5、PriorityBlockingQueue:优先级无界阻塞队列 ,底层最小堆,自动排序,适合带权重优先级任务处理;
6、TransferQueue:精准传输阻塞队列 ,生产者等待消费者接收完毕才返回,保障数据百分百被消费;
7、LinkedBlockingDeque:双向链表阻塞双端队列 ,头尾均可存取,适合工作窃取、双向任务调度;
补充:有界队列防止无限扩容内存溢出,无界队列无需手动设置容量;线程池高频使用前三种阻塞队列。
61.数组阻塞队列与链表阻塞队列区别
答:数组有界单锁,链表无界双锁。
62.SynchronousQueue 队列特点
答:SynchronousQueue 是无容量、无缓冲阻塞队列 ,也是七大阻塞队列中最特殊的同步传输队列。
1、存储特性:内部不维护任何存储容器,容量永久为0,无法缓存数据,生产者存入元素后必须等待消费者消费;
2、传输模式:严格一对一线程匹配,做到生产与消费实时握手,无多余积压任务;
3、锁机制:采用CAS非阻塞算法,吞吐量极高,支持公平与非公平两种传输模式;
补充:适合瞬时高并发、任务无积压、快速转发场景;Executors.newCachedThreadPool()线程池底层默认使用该队列,不适合大批量缓冲任务。
63.DelayQueue 延时队列常用场景
答:订单超时取消、定时延时任务、会员到期处理。
64.队列四类存取 API
答:抛出异常、返回布尔值、永久阻塞、超时阻塞。
65.双向队列适用场景
答:双向队列(Deque)具备头尾双向存取、既可队列又可栈 的特性,两端均可执行增删操作,灵活性远高于普通单向队列。
1、栈结构模拟:利用队首入队、队首出队,实现后进先出逻辑,替代老旧Stack类,执行效率更高;
2、普通队列业务:遵循先进先出,做常规数据缓冲、排队处理,适配简单流量削峰;
3、首尾高频操作:频繁在集合头部、尾部新增或删除元素,如消息头尾插入、历史记录追加;
4、工作窃取场景:多线程任务调度,线程可从队列两端获取任务,均衡线程负载;
补充:常用实现类为ArrayDeque、LinkedBlockingDeque,日常优先使用ArrayDeque,性能优于链表实现。
66.阻塞队列与非阻塞队列区别
答:队列满空时处理策略不同,阻塞队列会阻塞线程。
67.线程池常用工作队列
答:线程池内置三款核心工作队列,适配不同并发流量、任务积压场景,均为阻塞队列,配合线程池实现任务缓冲与复用。
1、ArrayBlockingQueue:有界数组队列,容量固定、单锁机制;适合流量可控、防止无限堆积 场景,手动设置队列长度,达到容量上限触发拒绝策略,避免内存溢出;
2、LinkedBlockingQueue:无界链表队列,双锁并发高、吞吐量强;默认无上限,适合持续平稳、无突峰 业务,CachedThreadPool、FixedThreadPool底层默认使用;
3、SynchronousQueue:无容量同步队列,不缓存任务;适合瞬时高并发、无任务积压 场景,每来一个任务必须有线程处理,newCachedThreadPool专属队列;
补充:日常业务优先有界队列防止OOM;流量平稳用链表队列;瞬时爆并发采用同步队列,严格区分业务选型。
68.PriorityBlockingQueue 介绍
答:PriorityBlockingQueue 是无界优先级阻塞队列 ,结合了优先级排序与阻塞队列两大特性,是线程安全的优先级队列。
1、底层结构:底层基于可变长度数组实现的最小堆,逻辑为完全二叉树,自动对元素进行优先级排序,无固定容量限制;
2、排序规则:默认自然升序、堆顶为最小值,支持自定义Comparator比较器灵活修改排序权重;
3、阻塞特性:队列空时出队线程阻塞,队列满时不会阻塞,因无界可无限扩容,不会触发拒绝策略;
补充:内部采用ReentrantLock保证线程安全,不允许存入null元素;适合多线程下权重任务排序、定时优先级调度场景,缺点是海量数据扩容会消耗内存。
69.使用队列实现栈思路
答:借助两个队列来回转移元素完成后进先出。
70.使用栈实现队列思路
答:拆分入栈与出栈两个栈,实现先进先出。
第五部分 Map 集合(71-90 题)
71.Map 集合整体结构
答:Map是双列键值对集合 ,顶层为Map根接口,不继承Iterable接口,无法直接迭代遍历,整体分为三大分支实现类,存储K-V键值映射关系。
1、底层层级:顶层Map接口,派生抽象父类AbstractMap,再衍生三大主流实现分支;
2、无序哈希分支:HashMap、LinkedHashMap、WeakHashMap,底层依托哈希表,查询速度快,无序或保留插入顺序;
3、有序树形分支:TreeMap,底层依托红黑树,可根据Key自动排序,存取时间复杂度O(logN);
4、古老线程分支:HashTable、Properties,古老同步集合,采用全局锁,性能差基本淘汰;
补充:通用特性:Key唯一不可重复、Value可重复,允许单个null键、多个null值(除并发Map外);高并发专用ConcurrentHashMap,单独属于JUC并发包,采用分段锁优化并发读写。
72.HashMap JDK1.7 与 JDK1.8 核心区别
答:JDK1.8 针对1.7底层结构、插入逻辑、哈希算法、并发缺陷做全方位优化,是HashMap历史性升级,核心区别分为五点。
1、底层结构:1.7采用数组+单向链表 ;1.8采用数组+单向链表+红黑树 ,链表过长自动树化,优化高哈希冲突查询效率;
2、元素插入:1.7采用头插法 ,新节点插入链表头部;1.8改为尾插法 ,新节点追加链表尾部,避免并发循环链表死循环;
3、哈希算法:1.7扰动次数多、逻辑繁琐;1.8简化哈希扰动,一次高位异或运算,兼顾散列效果与运算效率;
4、扩容迁移:1.7扩容后全部元素重新哈希散列;1.8优化迁移逻辑,元素仅拆分至原位置、原位置+旧容量两处,无需重复计算哈希;
5、树化机制:1.7无树化逻辑,链表无限变长查询退化;1.8满足阈值自动树化、扩容触发降级,平衡查询性能;
补充:二者均非线程安全;1.7最大痛点为并发下头插法导致循环链表、CPU占用飙升,1.8彻底修复该致命缺陷。
73.HashMap 核心常量参数
答:默认容量 16、负载因子 0.75、树化阈值 8、降级阈值 6、最小树化容量 64。
74.HashMap 完整 put 存入流程
答:以JDK1.8源码逻辑为准,HashMap存入元素历经哈希计算、下标定位、冲突处理、树化判断、扩容校验 五大核心流程,步骤清晰、面试高频必考。
1、哈希计算:对Key做哈希扰动运算,高位异或低位,减少哈希冲突;若Key为null,哈希值固定为0;
2、下标定位:通过位运算 (容量-1) & 哈希值,快速计算元素在数组中的存储下标;
3、判断数组节点:若该下标位置为空,直接新建普通节点存入元素,流程结束;
4、处理哈希冲突:若下标位置已有元素,判断节点类型;若是红黑树节点,直接插入红黑树并平衡;若是链表节点,循环遍历链表,逐一比对key;
5、元素覆盖/新增:遍历链表过程中,key相同则直接覆盖旧value;遍历至链表末尾无重复key,追加新节点;
6、树化判定:新增节点后判断链表长度,链表≥8且数组容量≥64,自动树化为红黑树;
7、扩容校验:全部元素插入完成后,集合size自增,判断是否超过扩容阈值,超出则触发2倍扩容,重新迁移元素。
补充:全程采用尾插法,无链表反转死循环风险;插入优先覆盖重复key,保证Key唯一性,是HashMap核心存储逻辑。
75.哈希扰动函数作用
答:让哈希值高位参与运算,大幅降低哈希冲突概率。
76.采用 (n-1)&hash 计算下标原因
答:HashMap 采用 (容量-1) & 哈希值 位运算计算数组下标,替代传统取模运算,核心目的为提升运算效率、保证散列均匀 。
1、运算效率极高:位运算直接操作二进制底层,无需除法、取余运算,CPU执行速度远快于 % 取模运算,极致优化存取性能;
2、散列分布均匀:因容量固定为2的幂次方,(n-1)二进制全部为1,与hash做与运算可完整保留哈希值低位,使下标分布更均匀,减少哈希冲突;
3、简化扩容迁移:扩容后元素仅需判断高位哈希位,自动拆分至原下标或原下标+旧容量处,无需重新复杂计算下标;
补充:该公式硬性依赖容量为2的幂次方,若容量非2的幂,(n-1)二进制存在0空位,会导致部分下标永远无法存入,造成空间浪费、冲突暴涨。
77.HashMap 容量必须为 2 的幂次方
答:HashMap 硬性规定底层数组容量必须为2的幂次方 ,是适配位运算、优化哈希散列、简化扩容逻辑的底层设计,缺一不可。
1、适配位运算下标:依靠公式 (n-1)&hash 计算下标,容量为2的幂时,n-1二进制全部补1,哈希值低位完整保留,散列均匀;若非2的幂,二进制存在空位0,导致部分下标永久无法存储,造成空间浪费、冲突暴涨;
2、简化扩容迁移:扩容永远翻倍,扩容后元素仅通过哈希高位判断分流,自动拆分至原下标、原下标+旧容量两处,无需重复计算哈希,迁移效率极高;
3、优化哈希散列:2的幂容量搭配高位扰动算法,让元素分散更均匀,有效降低链表过长概率,优化查询性能;
补充:若手动传入非2的幂初始化容量,底层会自动向上取最近的2的幂;该底层设计牺牲少量存储空间,换取极致运算性能,是时间与空间的经典取舍。
78.HashMap 扩容执行流程
答:以JDK1.8源码为准,HashMap采用2倍扩容、高低位拆分迁移 机制,无需重新计算哈希,大幅优化扩容性能,流程规范且面试高频。
1、扩容触发时机:集合元素个数size超过扩容阈值(容量 * 负载因子0.75),触发自动扩容;若链表树化、数组容量不足64,也会优先扩容;
2、创建新数组:原容量向左位移一位,容量翻倍,同时更新扩容阈值;
3、元素拆分迁移:遍历原数组所有节点,根据哈希值高位特征拆分元素;借助二进制高位判断,将元素分为低位组、高位组;
4、高低位分流:低位哈希位为0,保留在原下标 ;高位哈希位为1,迁移至原下标+旧容量 位置;
5、节点迁移规则:链表节点采用尾插法顺序迁移,避免链表反转;红黑树节点拆分后,节点数量≤6自动降级为链表;
6、引用替换:全部元素迁移完毕,将原数组引用指向新数组,等待GC回收旧数组内存;
补充:JDK1.8扩容无需重新hash,仅判断高位;彻底规避1.7头插法导致的并发循环链表问题,扩容安全性、迁移效率大幅提升。
79.JDK1.7 HashMap 并发死循环原因
答:多线程环境下头插法,链表反转形成循环链表。
80.HashMap 支持 null 键 null 值
答:仅允许一个 null 作为 key,value 可无限存入 null。
81.LinkedHashMap 核心特点
答:LinkedHashMap 是有序可重复双列集合 ,直接继承HashMap,在哈希表基础上额外维护双向链表,兼顾查询性能与顺序特性,原生支持LRU缓存淘汰逻辑。
1、底层结构:哈希表 + 双向链表 ,复用HashMap数组、链表、红黑树结构,额外新增头尾指针串联所有节点;
2、排序模式:包含两种排序规则,默认插入顺序 、访问顺序可手动开启;访问顺序模式下,被访问节点自动移至链表尾部;
3、去重规则:沿用HashMap机制,Key唯一不可重复,允许存储null键、null值;
4、LRU实现:重写removeEldestEntry方法可自定义缓存容量,自动淘汰最久未使用节点,轻松实现简易缓存;
补充:非线程安全,性能略低于HashMap,链表产生少量额外内存开销;业务常用于有序键值存储、本地简易缓存、数据追溯场景。
82.TreeMap 底层原理
答:TreeMap 是可排序双列集合 ,底层基于红黑树 数据结构实现,天然根据Key进行有序排序,适合需要键值自动排序的业务场景。
1、底层结构:采用自平衡二叉查找树(红黑树),每个节点包含Key、Value、左子节点、右子节点、父节点、颜色标记,时间复杂度稳定O(logN);
2、排序规则:默认按照Key自然升序排序,支持自定义外部比较器;排序逻辑、去重规则完全复用TreeSet比较机制;
3、去重逻辑:依靠比较器返回值判定重复,返回0判定Key重复,直接覆盖旧Value,不依赖hashCode与equals方法;
4、节点维护:插入、删除元素后自动左旋、右旋、变色,完成红黑树自平衡,防止树结构退化成链表;
补充:非线程安全,不允许存入null类型Key,允许存储null值;对比HashMap无序特性,TreeMap牺牲存取速度换取有序性,适合需要持续排序、范围查找的业务。
83.HashTable 被淘汰核心原因
答:全表全局锁并发性能极差,不支持 null 键值。
84.Properties 集合作用
答:专门用于读取项目 properties 配置文件。
85.WeakHashMap 实现原理
答:WeakHashMap 是弱引用键的哈希映射集合 ,底层基于哈希表实现,核心特性为Key采用弱引用机制,自动回收无效缓存数据,专为内存敏感缓存场景设计。
1、引用机制:Key统一包装为弱引用对象,在JVM垃圾回收时,若Key无外部强引用,无论内存是否充足,都会被GC强制回收;
2、底层结构:采用数组+单向链表结构,无红黑树、无树化逻辑,算法保留JDK1.7简单哈希架构,结构轻量化;
3、自动清理:内部绑定引用队列,被GC回收的Key会进入队列,每次增删改查时主动清除失效Entry,自动清理空洞、释放内存;
4、存储限制:Value为强引用,必须依赖Key存活;若Key被回收,强引用Value会随节点一并清除,避免内存泄漏;
补充:非线程安全、允许null键null值;典型用途:临时缓存、本地脱敏缓存、资源解绑容器,适合短期临时数据存储,无需手动删除数据。
86.JDK1.7 ConcurrentHashMap 底层
答:JDK1.7版本采用分段锁Segment机制 ,核心思想为拆分容器、分散锁竞争,在HashTable全局锁基础上大幅优化并发性能。
1、底层结构:采用Segment数组 + 哈希表 双层结构,外层为分段数组Segment,内层每一段独立维护哈希表(数组+单向链表);
2、分段锁原理:默认分割16个Segment分段,每一段独立持有一把ReentrantLock可重入锁,不同分段线程互不抢占锁,并发度最高支持16个线程同时写入;
3、存取逻辑:写入数据先通过哈希定位至指定Segment分段,加锁后再对内层哈希表完成存取,仅锁定当前分段,不影响其他分段;
4、初始化参数:默认分段数量16、不可扩容,单个分段默认容量16、负载因子0.75,扩容仅针对单个分段独立扩容;
补充:劣势明显,分段数量固定无法动态扩容、内存占用偏高、哈希冲突严重链表过长;JDK1.8彻底舍弃分段锁,改用细粒度节点锁优化并发能力。
87.JDK1.8 ConcurrentHashMap 底层
答:JDK1.8彻底舍弃1.7分段锁,采用数组+单向链表+红黑树 结构,结合CAS乐观锁 + synchronized悲观锁 实现细粒度加锁,大幅提升并发吞吐量。
1、底层结构:和HashMap一致,采用数组、链表、红黑树三层结构,保留树化、降级机制,哈希算法、高位拆分扩容逻辑完全复用;
2、加锁机制:摒弃重量级Segment分段锁,只锁定数组当前下标首节点 ,锁粒度极度细化,锁竞争大幅降低;无冲突采用CAS自旋写入,冲突严重启用synchronized加锁;
3、节点优化:新增TreeBin、ForwardingNode特殊节点,分别标记红黑树根节点、扩容迁移节点,辅助并发扩容;
4、扩容机制:支持多线程协助扩容,检测到扩容状态的线程会主动帮忙迁移数据,加快扩容进度,避免单线程迁移瓶颈;
5、辅助类设计:内部保留CounterCell计数单元格,分散统计元素个数,解决高并发下size统计卡顿问题;
补充:不允许存储null键null值,默认初始容量16、负载因子0.75;相较于1.7,内存占用更低、并发度更高、冲突处理更优秀,是生产高并发键值存储首选集合。
88.ConcurrentHashMap 不允许 null 键值原因
答:高并发下无法区分数据为空还是数据不存在。
89.Map 集合最高效遍历方式
答:Map主流存在四种遍历方式,entrySet遍历方式综合效率最高 ,尤其适合大批量键值对遍历,生产环境优先推荐。
1、entrySet遍历(最优):一次性获取键值对实体Entry,只遍历一次集合,同时拿到Key与Value,无二次查询开销,大数据量性能碾压其他方式;底层直接复用Map内部Entry节点,减少IO查询次数;
2、keySet遍历:先遍历全部Key,再通过get()方法二次查询Value,需两次遍历;哈希表还要重复计算哈希下标,海量数据下性能损耗严重;
3、values遍历:仅单纯遍历全部Value,无法获取对应Key,适用只取值、不要键的极简业务;
4、迭代器/ForEach遍历:语法简洁,底层依旧依托entrySet实现,小数据量可读性高,大数据量无性能优势;
补充:开发规范:大数据量优先entrySet、只取value用values、禁止keySet大批量遍历;JDK8推荐forEach链式遍历,代码简洁且底层优化完善。
90.HashMap 线程安全替代方案
答:优先 ConcurrentHashMap,其次使用同步包装 Map。
第六部分 集合高阶面试题(91-100 题)
91.大数据量 HashMap 初始化优化
答:大数据量场景下HashMap核心优化手段为手动指定初始化容量 ,精准规避多次自动扩容,减少数组拷贝、哈希重计算带来的性能损耗。
1、计算公式:初始化容量 = 预估数据量 / 0.75 + 1;加一是为了预留冗余空间,防止临界值触发多余扩容;
2、底层原理:HashMap默认负载因子0.75,元素达到阈值就会二倍扩容;大数据量不指定容量,会频繁扩容、产生大量废弃数组、加重GC压力;
3、容量规整:若计算结果非2的幂次方,底层自动向上取最近2的幂,保证符合底层位运算存储逻辑;
4、开发规范:预估数据量大小时,强制手动初始化容量;杜绝默认空参构造,海量数据可适当微调负载因子降低哈希冲突;
补充:该优化是生产高频硬性规范,能大幅减少内存碎片、提升大数据量存取吞吐量,是最简单高效的HashMap调优手段。
92.HashMap 链表转红黑树条件
答:JDK1.8 HashMap 链表转红黑树必须同时满足两个硬性条件 ,缺一不可,目的是平衡查询性能与内存开销。
1、链表长度条件:当前链表节点数量 ≥ 8 ;链表过长、哈希冲突严重,线性查询效率退化,需要转为红黑树优化查询速度;
2、数组容量条件:底层哈希数组容量 ≥ 64 ;若数组容量过小,优先进行2倍扩容,不触发树化,避免小树结构频繁转换浪费性能;
3、底层设计原因:阈值8源自泊松分布统计,哈希冲突链表长度达到8概率极低,判定为恶意哈希碰撞;红黑树节点占用内存远大于普通链表节点,低容量树化性价比极低;
补充:仅满足链表长度≥8、数组容量不足64时,只会扩容不会树化;树化后满足降级条件(节点≤6)自动退回链表,中间7作为缓冲临界值,防止频繁树化抖动。
93.红黑树退化为链表条件
答:JDK1.8 HashMap 在扩容迁移 时触发红黑树降级,仅判断节点数量,阈值明确、机制严谨,与树化条件形成双向制衡。
1、降级触发条件:红黑树节点数量 ≤ 6 ,自动退化为普通单向链表;
2、触发时机:不会在普通删除时降级,仅在数组二倍扩容、拆分迁移树节点过程中校验并降级;
3、缓冲阈值设计:节点数量7作为中间缓冲区间,既不树化也不降级;防止节点数量在6~8之间频繁波动,造成反复树化、降级抖动,浪费CPU性能;
4、底层设计原因:红黑树节点结构复杂、占用内存更高,节点数量少时,链表遍历速度更快、维护成本更低,无需保留树结构;
补充:树化双条件、降级单条件,树化严苛、降级宽松;取舍目的为尽量少树化、尽量晚降级,减少结构转换开销,优化综合存取性能。
94.Java 实现 LRU 缓存淘汰算法
答:LRU(最近最少使用)是缓存淘汰策略 ,核心思想:优先淘汰**最久未被访问**的数据,日常最简实现方式为继承LinkedHashMap,重写判定移除方法,无需手动维护链表逻辑。
1、底层实现原理:依托LinkedHashMap哈希表+双向链表 结构,开启访问排序模式;每访问一个元素,自动将该节点移动至链表尾部,链表头部永远为最久未使用数据;
2、核心重写方法:重写removeEldestEntry()方法,自定义缓存最大容量;当集合元素超过设定容量,自动移除链表头部最老旧未访问节点;
3、手写实现步骤:①继承LinkedHashMap;②构造方法开启accessOrder访问排序;③定义最大缓存容量;④重写移除规则判定方法;
4、优缺点:实现极简、代码量少、底层由JDK优化;不适合海量高并发缓存,线程不安全,海量数据建议使用Redis的LRU淘汰机制;
补充:除原生实现外,可手动通过HashMap+双向链表手写LRU,自主维护插入、删除、移动节点逻辑,常用于面试手写算法考题。
95.集合引发内存泄漏常见原因
答:Java集合本身不会自动释放引用,若使用不当会造成对象常驻内存、GC无法回收 ,引发内存泄漏,生产环境高频出现四大核心原因。
1、静态集合长期持有引用:static修饰的List、Map等集合生命周期跟随虚拟机,全局常驻内存;存入对象后永不主动清空,对象一直被强引用指向,GC无法回收,不断堆积内存;
2、ThreadLocal集合未手动移除:ThreadLocal绑定线程,线程池线程长期存活;存放集合数据后不调用remove(),弱引用key被回收、value强引用滞留,造成内存泄漏;
3、集合扩容产生冗余废弃数组:ArrayList、HashMap频繁扩容,不断产生旧数组垃圾对象;若业务持续高频写入且不做优化,大量废弃数组堆积加重GC压力,形成隐性内存泄漏;
4、自定义对象未断引用:集合存入实体对象,对象关联外部资源(流、连接、监听器);仅清空集合不手动置空引用,资源对象无法回收,常驻堆内存;
补充:规避方案:非必要不使用静态集合、用完集合手动clear清空、ThreadLocal使用完毕强制remove、大批量数据提前指定初始化容量、及时断开无用对象引用。
96.多线程集合使用规范
答:多线程环境下禁止使用普通非线程安全集合,需根据读写比例、并发量级、业务场景 规范选用并发集合,规避并发异常、数据覆盖、内存溢出问题,生产通用规范如下。
1、List集合规范:读多写少场景优先CopyOnWriteArrayList ,写时复制保证线程安全,遍历无并发修改异常;高频频繁增删、读写均衡场景,手动加锁普通ArrayList,减少拷贝开销;坚决淘汰Vector、synchronizedList,全局锁并发性能极差。
2、Map集合规范:高并发键值存储强制使用ConcurrentHashMap ,细粒度锁+CAS适配高并发读写;少量低并发简单场景可使用Collections.synchronizedMap;彻底弃用HashTable,全局锁效率低下且不允许空键值。
3、Queue队列规范:线程池、生产者消费者模型使用阻塞队列 ;流量管控、防止OOM选用有界ArrayBlockingQueue;平稳持续任务选用LinkedBlockingQueue;瞬时无积压高并发选用SynchronousQueue;延时定时任务采用DelayQueue。
4、通用开发禁忌:多线程禁止并发修改普通集合,避免触发ConcurrentModificationException;并发集合迭代遍历无需手动加锁,普通同步集合遍历必须手动加锁;大批量并发写入优先设置有界容量,防止无限扩容内存溢出。
补充:核心选型口诀:读多写少用拷贝、高并发映射用Concurrent、线程通信用阻塞、老旧同步集合全淘汰。
97.Java8 Stream 集合常用操作
答:Stream是JDK8推出的流式处理机制 ,依托Lambda表达式简化集合代码,采用流水线思想处理数据,分为中间操作、终端操作,代码极简、链式编程、延迟执行,生产开发高频使用。
1、常用七大核心操作:
①过滤(filter):条件筛选元素,剔除不符合规则的数据,常用于数据清洗;
②映射(map):类型转换、字段提取,将A对象转为B对象,抽取实体指定字段;
③排序(sorted):支持自然排序、自定义比较器排序,灵活调整元素顺序;
④去重(distinct):底层重写hashCode+equals,实现集合快速去重;
⑤分组(groupingBy):按照指定字段分组,返回Map结构,适合分类统计;
⑥统计(collect/count):归集转换、计数求和、最值查找,实现数据汇总;
⑦遍历(forEach):简洁遍历集合,替代传统循环,代码可读性高。
2、操作分类特性:
①中间操作:filter、map、sorted、distinct,延迟执行、可链式拼接,不会立即触发执行;
②终端操作:forEach、collect、count、max/min,触发流水线执行,关闭流、不可重复使用。
3、开发规范:
Stream流只能一次性使用 ,终端执行后流自动关闭;并行流(parallelStream)适合无锁纯计算、无线程安全依赖场景;禁止在流中修改原集合数据。
补充:Stream彻底简化集合加工代码,替代冗余循环判断,是Java8最核心优化特性;日常开发优先使用Stream处理集合筛选、转换、排序、分组业务。
98.大数据量集合处理优化
答:大数据量集合处理核心优化思想:避免全量加载、减少内存占用、降低拷贝开销、控制GC压力 ,防止一次性加载海量数据引发OOM内存溢出、卡顿、频繁GC,生产通用优化方案如下。
1、数据加载优化:禁止一次性加载全量数据,采用分批读取、分页查询、流式读取 ;数据库分页、文件逐行读取,截断数据源流量,控制单次内存负载;
2、集合初始化优化:创建集合手动指定初始化容量 ,套用公式:初始容量=预估量/0.75+1;规避频繁扩容产生数组拷贝、内存碎片,减少GC次数;
3、遍历逻辑优化:海量数据优先迭代器、Stream流式遍历 ,不使用普通for、foreach一次性加载;遍历中及时释放无效引用,禁止遍历中扩容、新增元素;
4、数据去重与裁剪:提前过滤无效空数据、冗余重复数据;使用HashSet、Stream去重,减少集合存储压力;非必要字段不存入集合,精简对象内存;
5、内存回收优化:数据处理完毕手动调用clear()清空集合、置空引用;大集合及时断开对象引用,方便GC快速回收,杜绝内存累积泄漏;
6、并发场景优化:海量并发读写选用ConcurrentHashMap、CopyOnWriteArrayList ;禁止普通非线程安全集合;大批量写入采用有界队列,限制最大承载量防止无限膨胀;
补充:硬性开发规范:大数据量杜绝ArrayList无参构造、禁止内存一次性承载全量数据源、杜绝循环频繁创建集合对象;必要时使用第三方流式框架分片处理超大集合。
99.JDK8 集合新增核心特性
答:JDK8针对单列、双列集合进行大规模功能性升级,新增流式操作、便捷删除、键值合并、默认方法 等特性,简化代码、减少冗余循环、提升集合处理效率,五大高频核心特性如下。
1、Stream流式操作:新增java.util.stream流式包,依托Lambda实现筛选、映射、排序、分组、去重、归集链式操作,延迟执行、代码极简,替代传统for循环处理集合;
2、removeIf批量删除:Collection通用新增方法,根据自定义条件批量删除集合元素,底层迭代器安全删除,规避普通遍历删除并发异常,替代手写迭代器删除逻辑;
3、merge键值合并(Map专属):解决重复key覆盖问题,若key不存在直接存入;key存在自定义新旧value合并规则,适合统计累加、数据聚合场景;
4、forEach遍历方法:所有集合重写forEach方法,结合Lambda极简遍历,底层封装迭代器,代码简洁无冗余,禁止遍历中修改集合;
5、compute/putIfAbsent方法(Map专属):putIfAbsent不存在才存入,compute动态计算覆盖value,精准控制键值对修改逻辑,简化空值判断代码;
补充:底层优化:集合接口新增大量默认方法,不破坏原有实现类;Stream并行流可简易实现多线程批量处理,大数据量集合处理效率大幅提升,是开发日常高频使用特性。
100.Java 集合终极选型总结
答:日常开发集合选型遵循以性能优先、贴合业务、规避老旧集合、严控并发安全 四大原则,根据有序、去重、排序、并发、队列五大业务场景快速选型,通用完整选型规则如下。
1、普通单列List选型:查询遍历优先ArrayList ,底层数组随机访问极快;频繁头尾增删、极少查询选用LinkedList;坚决淘汰Vector、Stack老旧集合;
2、去重Set集合选型:单纯无顺序去重使用HashSet;保留插入顺序去重选用LinkedHashSet ;需要自动升降序排序强制使用TreeSet;枚举专用EnumSet性能最优;
3、双列Map集合选型:普通非并发存取优先HashMap;需要保留访问顺序、简易缓存选用LinkedHashMap;按键自动排序选用TreeMap;高并发生产环境强制使用ConcurrentHashMap;彻底弃用HashTable;
4、队列Queue集合选型:普通双端存取、模拟栈结构选用ArrayDeque;权重排序任务使用PriorityQueue;线程池、生产者消费者选用阻塞队列;延时任务采用DelayQueue;瞬时高并发无积压使用SynchronousQueue;
5、并发安全选型:读多写少采用CopyOnWriteArrayList;高并发键值映射采用ConcurrentHashMap;所有并发业务优先JUC并发集合,杜绝同步老旧集合;
6、特殊场景选型:临时内存缓存、自动回收无用对象使用WeakHashMap;固定不可修改数据使用不可变集合;大批量数据必须手动指定初始化容量;
补充:最简背诵选型口诀:查询用数组、增删用链表、去重用哈希、有序用链表映射、排序用红黑、并发用JUC、队列看阻塞、过期用弱引用。
附录:集合高频背诵总结(必背)
一、集合体系总览
Java 集合分为两大类:Collection 单列集合 、Map 双列集合 。
Collection 三大分支:List(有序可重复)、Set(无序不可重复)、Queue(队列);
Map 存储键值对:key 唯一,value 可重复。
二、五大底层数据结构(必考背诵)
Java集合全部依托数组、链表、红黑树、跳表、堆 五种基础数据结构实现,分工明确、各司其职,是集合底层核心骨架:
1、数组:内存连续、下标寻址 ,查询极快、中间增删慢;代表集合:ArrayList、ArrayDeque、普通哈希表;
2、链表:内存不连续、指针关联 ,增删快、查询慢;代表集合:LinkedList、LinkedHashMap、阻塞链表队列;
3、红黑树:自平衡二叉查找树 ,查询、增删时间复杂度稳定O(logN);代表集合:TreeMap、TreeSet、JDK1.8HashMap;
4、跳表:多层有序链表,空间换时间,查询效率媲美红黑树、实现简单;代表集合:ConcurrentSkipListMap;
5、堆:逻辑完全二叉树、物理数组存储,自动维护极值;代表集合:PriorityQueue、PriorityBlockingQueue。
补充:集合优化本质就是优化这五种结构,组合使用、取长补短;哈希表=数组+链表/红黑树,是最经典结构组合。
三、线程安全集合汇总
1、List 线程安全集合:
① Vector:古老同步集合,全部方法加synchronized全局锁,扩容2倍,并发性能极差,存在大量冗余方法,生产彻底淘汰;
② Collections.synchronizedList:装饰器模式包装普通List,加全局同步锁,遍历需手动加锁,读写性能一般,适合极低并发简单场景;
③ CopyOnWriteArrayList:JUC并发集合,写时复制+读写分离,无锁读取、加锁写入,遍历无并发修改异常,适合读多写少 业务。
2、Map 线程安全集合:
① HashTable:古老集合,全局锁锁住整张哈希表,并发吞吐量极低,不允许null键null值,现已淘汰;
② ConcurrentHashMap:生产高并发首选,JDK1.7分段锁、JDK1.8节点锁+CAS,锁粒度极小、并发性能强悍,不允许null键值。
3、Queue 线程安全集合:
七大阻塞队列(BlockingQueue),全部位于JUC包,内置锁机制,天然支持生产者消费者模型;包含ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue、DelayQueue、PriorityBlockingQueue等,线程池专用队列,自带阻塞、唤醒机制。
补充:线程安全集合选型优先级:JUC并发集合 > 同步装饰集合 > 老旧同步集合;生产禁止使用Vector、HashTable。
四、HashMap 必考参数(面试死记)
HashMap 全部固定源码常量,不可修改、面试高频默写,所有底层逻辑围绕这五大常量展开:
1、默认初始化容量:16 ,底层数组默认长度,强制为2的幂次方;
2、默认负载因子:0.75 ,扩容临界阈值,元素占用达到容量75%触发二倍扩容,时间空间最优平衡点;
3、链表树化阈值:8 ,单个链表节点大于等于8,且满足数组容量条件,触发红黑树转换;
4、红黑树降级阈值:6 ,扩容迁移时树节点小于等于6,自动退化为单向链表;
5、最小树化容量:64 ,链表长度达标但数组容量不足64,优先扩容、不树化;
补充扩展参数:
① 扩容倍数:2倍扩容 ,永远保持2的幂次方;
② 插入方式:JDK1.7头插法,JDK1.8改为尾插法 ;
③ 哈希扰动:一次高位异或运算,减少哈希冲突;
④ 最大容量:Integer.MAX_VALUE - 8,防止数组内存溢出;
背诵口诀:16容量、0.75因子、8树6降、64最低树化容量、永远二倍扩容。
五、面试高频坑点(必考避坑、最容易丢分)
本板块汇总Java集合所有高频易错、面试官高频挖坑、生产常出错知识点,全部硬性背诵,避开开发与面试陷阱:
1、遍历删除坑:foreach、普通for循环禁止增删元素 ,直接抛出ConcurrentModificationException;唯一安全删除方式:迭代器remove()、List.removeIf();
2、去重判断坑:自定义对象存入HashSet、HashMap必须重写hashCode()和equals() ,只重写一个会导致去重失效、内存重复堆积;
3、空值存储坑:HashMap允许一个null键,ConcurrentHashMap完全禁止null键、null值 ;TreeSet/TreeMap不允许null键,直接空指针;
4、数组转集合坑:Arrays.asList()返回原数组内部静态集合,长度固定无法增删 ;传入基本类型数组会被识别为单个对象,导致集合长度为1;
5、集合初始化坑:大数据量禁止空参构造HashMap、ArrayList;频繁扩容产生大量垃圾数组、加重GC,必须手动指定初始化容量;
6、并发使用坑:ArrayList、HashMap、HashSet全部非线程安全;多线程千万别直接使用,会出现数据覆盖、丢失、死循环、CPU飙升;
7、比较器坑:TreeSet、TreeMap排序优先级:外部比较器Comparator > 内部比较器Comparable ;只实现Comparable不写比较规则直接存对象抛类型转换异常;
8、LRU实现坑:LinkedHashMap默认插入顺序,必须开启accessOrder=true 才是访问顺序,才能实现缓存淘汰;不重写移除方法不会自动淘汰数据;
9、扩容坑:HashMap永远2倍扩容、ArrayList1.5倍扩容;HashMap容量强制2的幂,手动传入非2的幂底层自动向上规整;
10、链表树化坑:链表≥8且数组≥64 才树化;只满足链表长度只会扩容,不会转为红黑树,大量面试混淆该条件;
11、集合清空坑:list=null 无法释放内存,只是断开引用;大集合必须使用clear()清空+手动置空,方便GC回收防止内存泄漏;
12、equals坑:集合判断非空必须先判断集合引用,再判断size;isEmpty()效率高于size()==0,生产禁止手写size判断;
13、并行流坑:parallelStream并行流不保证顺序,多线程无锁操作会引发数据错乱,禁止业务随意滥用并行流;
14、阻塞队列坑:无界阻塞队列无限扩容,高并发会造成OOM;生产队列必须使用有界队列 ,设置最大容量保护内存;
补充:集合所有坑点核心总结:遍历不乱删、对象必重写、并发不用普通集合、空值分清规则、大数据必设容量。
六、集合使用选型口诀
查多改少 ArrayList;头尾增删 LinkedList;
普通去重 HashSet;有序去重 LinkedHashSet;
排序使用 Tree 系列;枚举专用 EnumSet;
映射取值用 HashMap;有序缓存 LinkedHashMap;
排序映射 TreeMap;弱存缓存 WeakHashMap;
并发映射 ConcurrentHashMap;淘汰哈希 HashTable;
栈结构用 ArrayDeque;阻塞队列做通信; 有界队列防OOM;无界队列存平稳;
延时任务 DelayQueue;权重优先堆队列;
读多写少拷贝集合;高并发下JUC优先;
静态集合谨慎使用;大数据量必设容量;
遍历删除慎用循环;自定义对象必重写;
空值存储分清规则;生产拒绝老旧集合。