1.Collection集合
1.1数组和集合的区别【理解】
-
相同点
都是容器,可以存储多个数据
-
不同点
-
数组的长度是不可变的,集合的长度是可变的
-
数组可以存基本数据类型和引用数据类型
集合只能存引用数据类型,如果要存基本数据类型,需要存对应的包装类
-
新增补充:
-
从底层机制来看:
-
数组分配的内存是连续的,查询效率高(O(1)),但插入/删除时需要整体移动数据,效率低。
-
集合底层是基于链表/动态数组/哈希表等结构,扩容机制会自动调整容量,插入/删除灵活,但查询效率视具体实现而定(如
ArrayList
查询快、LinkedList
插入快)。
-
-
实战应用场景对比:
-
固定容量、大批量数据场景优先考虑数组(如高性能计算、内存池)。
-
数据规模不确定、频繁增删操作时优先选择集合。
-
🧠 理论理解
数组和集合本质上都是数据容器,但设计初衷不同:
-
数组是静态数据结构,长度固定,适合存储已知数量的元素。它可以存储基本数据类型(如
int[]
)或引用数据类型(如String[]
),但一旦初始化,容量不可变。 -
集合是动态数据结构,容量可动态扩展,仅支持存储对象(引用类型),存储基本类型时需要使用包装类(如
Integer
替代int
)。集合提供了丰富的API,支持动态增删查改。
此外,数组在内存中是连续存储,而集合的底层实现(如链表、哈希表)可能是非连续存储,导致在查找、插入、删除上的性能特点也不同。
🏢 企业实战理解
-
阿里巴巴:在支付系统的订单号生成中,为了极致性能使用数组批量缓存预生成的流水号,但在订单历史存储、查询中使用集合(如
List
、Set
)。 -
字节跳动:推荐系统中,算法层返回的初步推荐结果以数组存储(快速索引),但后续过滤、打分流程中基于
ArrayList
动态操作数据。 -
Google:在大规模分布式计算(如 MapReduce)中,内部中间数据采用集合结构管理任务列表,实现灵活扩展和动态调度。
面试题 1:
阿里巴巴面试:请详细说说数组和集合的区别?实际开发中如何选择使用?
参考答案:
数组是 Java 中一种基础的数据结构,特点是长度固定、支持基本类型和引用类型,其优势是存取速度快(内存连续)。适用于数据量已知且结构简单的场景,比如频繁进行索引访问的场景(如缓存池)。
集合是 JDK 提供的高级数据结构,容量可动态扩展,主要操作对象是对象引用。集合只能存储引用类型,若要存储基本类型(如 int
),需要使用包装类(如 Integer
)。集合内置了丰富的增删查改 API,灵活性远大于数组。
如何选择:
-
如果数据规模固定、操作简单,数组优先,比如处理大量数字的算法题。
-
如果数据量不确定、需要动态变化、频繁插入删除,集合优先。比如用户在线列表、评论区动态加载等。
大厂加分点:
字节跳动在高性能推荐场景中,常用数组处理打分排序(追求极致性能),而在业务数据流转(如推荐列表)中用 ArrayList
灵活应对数据变动。
场景题 1:
你在字节跳动负责短视频推荐系统优化,有一个模块需要存储推荐池里的 500 个视频对象,推荐逻辑实时刷新池子(添加/删除)。技术负责人问你:这里是用数组好,还是用集合好?为什么?
参考答案:
在推荐池中,虽然 500 条视频听起来是定量的,但因为实时刷新涉及频繁增删,所以集合更合适。
数组的特点是长度固定,一旦初始化 500 长度就无法扩容或缩减。如果采用数组,后续的增删都要手动维护新数组,复制旧数据,非常低效。
集合(如 ArrayList
)的优势在于动态扩容和提供完善的 add()
、remove()
等操作方法,能高效应对池子数据的增删变化。
实战中,字节跳动推荐系统使用 ArrayList
动态更新推荐池,而在短时间快照生成时,用数组复制集合(list.toArray()
),加速内存访问。
1.2集合类体系结构【理解】
现有结构图很好,这里补上几点你未提到的知识:
-
Collection顶层接口 是单列集合的根接口(存储单个元素),Map则是双列集合的根接口(存储键值对)。
-
主要子接口:
-
List:有序、可重复(如
ArrayList
、LinkedList
) -
Set:无序、不重复(如
HashSet
、TreeSet
) -
Queue/Deque:队列/双端队列(如
LinkedList
实现了Deque
)
-
-
补充说明:
-
JDK 1.5 之后引入了泛型机制,使集合在编译时就能检查类型安全。
-
JDK 1.8 之后
Collection
接口增加了default
方法,如forEach
等,支持函数式编程。
-
🧠 理论理解
Java 集合体系基于 Collection
和 Map
两大顶层接口:
-
Collection
→ 存储单列数据(元素):-
List
:有序、可重复(如ArrayList
、LinkedList
) -
Set
:无序、不重复(如HashSet
、TreeSet
) -
Queue
:队列/双端队列(如LinkedList
、PriorityQueue
)
-
-
Map
→ 存储双列数据(键值对):-
HashMap
、TreeMap
等。
-
设计采用了接口+实现类分层思想,提供多样化选择,同时鼓励面向接口编程。
🏢 企业实战理解
-
美团:在外卖派单系统中,订单对象存储在
PriorityQueue
中,实现优先级调度。 -
字节跳动:微服务网关将在线用户会话用
ConcurrentHashMap
存储,实现高并发读写的线程安全。 -
英伟达:GPU任务调度中,通过
TreeSet
管理任务优先级,实现任务的有序执行。
面试题 2:
腾讯面试:Java 的集合体系结构是如何设计的?为什么这么设计?
参考答案:
Java 的集合体系结构基于两个大接口:
1️⃣ Collection 接口(单列数据):
-
List
(有序可重复,如ArrayList
、LinkedList
) -
Set
(无序不可重复,如HashSet
、TreeSet
) -
Queue
(队列/优先队列,如LinkedList
、PriorityQueue
)
2️⃣ Map 接口(双列数据):
-
HashMap
、TreeMap
等
这种设计是典型的接口+实现类分层:
-
接口定义标准操作(增、删、查、遍历)
-
实现类提供不同的底层实现,适应不同场景(数组结构、链表结构、哈希结构、红黑树等)
设计好处:
-
符合面向接口编程,解耦性强。
-
不同实现类可以自由切换,提升灵活性。
-
开闭原则(对修改关闭、对扩展开放),方便维护。
实战场景补充:
Google 在大规模数据调度中,会根据性能需求切换 HashSet
(哈希结构)和 TreeSet
(有序红黑树结构),保证数据唯一性同时满足有序性要求。
场景题 2:
腾讯 QQ 群功能中,有“群成员列表”和“已读消息列表”这两部分。你负责优化底层数据结构,请问分别该用什么集合?为什么?
参考答案:
-
群成员列表:需要保证成员不重复,且支持快速查找,适合使用
HashSet
或LinkedHashSet
。如果还需要保证列表顺序(按加入顺序排列),推荐LinkedHashSet
。 -
已读消息列表:涉及消息的发送时间顺序、重复允许(有些消息可能重复),适合使用
ArrayList
,它支持顺序访问、快速遍历。
腾讯在 IM 消息模块中,大规模使用 Set
确保群成员的唯一性,并利用 List
记录聊天记录流,保持顺序处理。
1.3Collection 集合概述和使用【应用】
-
Collection集合概述
-
是单例集合的顶层接口,它表示一组对象,这些对象也称为Collection的元素
-
JDK 不提供此接口的任何直接实现.它提供更具体的子接口(如Set和List)实现
-
-
创建Collection集合的对象
-
多态的方式
-
具体的实现类ArrayList
-
-
Collection集合常用方法
方法名 说明 boolean add(E e) 添加元素 boolean remove(Object o) 从集合中移除指定的元素 boolean removeIf(Object o) 根据条件进行移除 void clear() 清空集合中的元素 boolean contains(Object o) 判断集合中是否存在指定的元素 boolean isEmpty() 判断集合是否为空 int size() 集合的长度,也就是集合中元素的个数
除了已有的 add、remove 方法,这里我补充几个 实际开发中常用且易忽略的点:
-
containsAll(Collection c):判断当前集合是否完全包含另一个集合的所有元素。
-
addAll(Collection c):一次性向集合中添加另一个集合的所有元素。
-
retainAll(Collection c):取两个集合的交集。
-
removeAll(Collection c):移除当前集合中与另一个集合相同的元素。
扩展:
-
线程安全问题:
Collection
及其子类如ArrayList
默认是非线程安全的,若多线程环境中使用需要:-
使用
Collections.synchronizedCollection
包装; -
或直接用
CopyOnWriteArrayList
、Vector
等线程安全实现。
-
-
性能对比:
-
ArrayList
:随机访问快,插入删除慢(尤其中间位置)。 -
LinkedList
:插入删除快,随机访问慢。
-
🧠 理论理解
Collection
是所有单列集合的根接口,核心设计理念是抽象出集合的基本操作:
-
增(
add
) -
删(
remove
) -
查(
contains
) -
遍历(
iterator
、forEach
)
ArrayList
是最常用的实现类,它基于动态数组实现,可随机访问,扩容时会重新分配更大的数组并拷贝数据。
🏢 企业实战理解
-
腾讯:直播弹幕的缓存列表使用
ArrayList
存储,当弹幕量达到上限时清空或持久化。 -
OpenAI:在对话历史管理中,将每轮对话记录动态存储到
ArrayList
中,便于序列化成上下文传递给大模型。
面试题 3:
字节跳动面试:谈谈 Collection 集合的核心方法,并结合实际举例说明。
参考答案:
Collection 接口核心方法包括:
-
add(E e)
:添加元素 -
remove(Object o)
:移除元素 -
clear()
:清空集合 -
contains(Object o)
:判断是否存在 -
size()
:获取集合长度 -
isEmpty()
:判断是否为空
比如,在抖音短视频推荐中,一个推荐列表(ArrayList
)会先 add
用户匹配的视频,再 remove
违规或重复的视频。上线前,会用 clear()
清空缓存,避免历史数据干扰。
面试细节:
-
remove(Object o)
是按对象值删除,不是按索引删除。 -
contains()
的底层依赖equals()
方法,若自定义对象,务必重写equals()
和hashCode()
保证判断正确。
实战中,腾讯的 IM 消息系统在处理用户会话列表时,频繁使用 Collection
方法动态增删在线好友。
场景题 3:
你在阿里巴巴负责电商促销系统,项目中有个“用户限购名单”功能,要求实时更新违规名单(比如下单过多的用户),后台还需要快速判断某用户是否已在限购名单中。你选什么数据结构?如何实现?
参考答案:
“限购名单”需要:
1️⃣ 实时更新(增删)
2️⃣ 快速判断是否存在
推荐使用 HashSet
:
-
add()
和remove()
操作时间复杂度 O(1) -
contains()
判断是否存在的速度极快
实战中,阿里在“双 11”场景下实时更新违规名单,通过 HashSet
存储黑名单,再结合 contains()
快速校验下单用户是否合法,大幅降低数据库压力。
1.4Collection集合的遍历
1.4.1 迭代器遍历
-
迭代器介绍
-
迭代器,集合的专用遍历方式
-
Iterator<E> iterator(): 返回此集合中元素的迭代器,通过集合对象的iterator()方法得到
-
-
Iterator中的常用方法
boolean hasNext(): 判断当前位置是否有元素可以被取出 E next(): 获取当前位置的元素,将迭代器对象移向下一个索引位置
-
Collection集合的遍历
public class IteratorDemo1 { public static void main(String[] args) { //创建集合对象 Collection<String> c = new ArrayList<>(); //添加元素 c.add("hello"); c.add("world"); c.add("java"); c.add("javaee"); //Iterator<E> iterator():返回此集合中元素的迭代器,通过集合的iterator()方法得到 Iterator<String> it = c.iterator(); //用while循环改进元素的判断和获取 while (it.hasNext()) { String s = it.next(); System.out.println(s); } } }
-
迭代器中删除的方法
void remove(): 删除迭代器对象当前指向的元素
public class IteratorDemo2 { public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(); list.add("a"); list.add("b"); list.add("b"); list.add("c"); list.add("d"); Iterator<String> it = list.iterator(); while(it.hasNext()){ String s = it.next(); if("b".equals(s)){ //指向谁,那么此时就删除谁. it.remove(); } } System.out.println(list); } }
🧠 理论理解
Iterator
提供了通用遍历接口,实现了“游标式”遍历。核心方法:
-
hasNext()
:判断是否有下一个元素。 -
next()
:返回下一个元素并移动游标。 -
remove()
:删除当前元素(迭代器安全删除)。
fail-fast机制:如果在迭代过程中直接使用集合修改结构,会抛出 ConcurrentModificationException
。
🏢 企业实战理解
-
字节跳动:在推荐系统数据过滤中,采用迭代器遍历大集合数据并删除不符合条件的元素,避免
ConcurrentModificationException
。 -
美团:骑手位置实时更新时,用迭代器安全遍历并剔除已超时的位置信息,保持数据准确性。
面试题 4:
阿里巴巴面试:迭代器的底层工作原理是什么?如何保证遍历时删除元素的安全性?
参考答案:
Iterator
遍历底层是基于游标的,三步走:
1️⃣ hasNext()
判断是否有下一个元素;
2️⃣ next()
获取元素并把游标往后移动;
3️⃣ 内部维护一个 expectedModCount
防止并发修改异常(fail-fast)。
当遍历时如果直接调用集合的 remove()
方法修改集合,modCount
改变,而 expectedModCount
没更新,触发 ConcurrentModificationException
。
正确做法: 使用 Iterator.remove()
方法删除当前元素,它会同步更新 expectedModCount
,确保安全。
实战中,美团的骑手列表动态剔除离线骑手时,必须用 Iterator.remove()
,避免线程间意外报错。
场景题 4:
你在 Google 参与翻译产品开发,翻译历史记录是一个 ArrayList
存储的。当你需要遍历所有历史记录,并删除含有敏感词的记录,如何做才能保证安全不报错?
参考答案:
因为涉及遍历中删除,必须使用 Iterator
提供的 remove()
方法。传统用 list.remove()
会导致 ConcurrentModificationException
。
做法:
Iterator<String> it = historyList.iterator();
while (it.hasNext()) {
String record = it.next();
if (record.contains("敏感词")) {
it.remove();
}
}
Google 翻译服务中,历史记录同步更新模块采用 Iterator.remove()
安全删除违规内容,确保不会触发异常影响体验。
1.4.2 增强for
-
介绍
-
它是JDK5之后出现的,其内部原理是一个Iterator迭代器
-
实现Iterable接口的类才可以使用迭代器和增强for
-
简化数组和Collection集合的遍历
-
-
格式
for(集合/数组中元素的数据类型 变量名 : 集合/数组名) { // 已经将当前遍历到的元素封装到变量中了,直接使用变量即可 }
-
代码
public class MyCollectonDemo1 { public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(); list.add("a"); list.add("b"); list.add("c"); list.add("d"); list.add("e"); list.add("f"); //1,数据类型一定是集合或者数组中元素的类型 //2,str仅仅是一个变量名而已,在循环的过程中,依次表示集合或者数组中的每一个元素 //3,list就是要遍历的集合或者数组 for(String str : list){ System.out.println(str); } } }
-
细节点注意:
1.报错NoSuchElementException
2.迭代器遍历完毕,指针不会复位
3.循环中只能用一次next方法
4.迭代器遍历时,不能用集合的方法进行增加或者删除
🧠 理论理解
增强for是 Iterator
的语法糖,简化了遍历逻辑,但无法在循环中安全修改集合结构。
本质:
for (String s : list) {
// 底层其实是使用了Iterator
}
🏢 企业实战理解
-
阿里巴巴:日志系统定时遍历缓存区日志列表,采用增强for直接输出所有日志数据。
-
谷歌:在Android系统中,增强for广泛用于遍历配置参数列表,提高代码可读性。
面试题 5:
字节跳动面试:增强 for 和 Iterator 有什么关系?是否能在增强 for 里删除元素?
参考答案:
增强 for 是 Iterator
的语法糖,底层其实调用 iterator()
方法遍历集合。增强 for 简化了代码,但限制较多:
-
不能安全删除元素,因为它没有暴露
remove()
方法。 -
只能顺序单向遍历,不能像
ListIterator
支持双向遍历。
如果想删除,必须用显式 Iterator
。
字节跳动在用户黑名单数据更新中,禁止用增强 for 删除数据,统一用迭代器实现安全修改。
public class A04_CollectionDemo4 {
public static void main(String[] args) {
/*
迭代器的细节注意点:
1.报错NoSuchElementException
2.迭代器遍历完毕,指针不会复位
3.循环中只能用一次next方法
4.迭代器遍历时,不能用集合的方法进行增加或者删除
暂时当做一个结论先行记忆,在今天我们会讲解源码详细的再来分析。
如果我实在要删除:那么可以用迭代器提供的remove方法进行删除。
如果我要添加,暂时没有办法。(只是暂时)
*/
//1.创建集合并添加元素
Collection<String> coll = new ArrayList<>();
coll.add("aaa");
coll.add("bbb");
coll.add("ccc");
coll.add("ddd");
//2.获取迭代器对象
//迭代器就好比是一个箭头,默认指向集合的0索引处
Iterator<String> it = coll.iterator();
//3.利用循环不断的去获取集合中的每一个元素
while(it.hasNext()){
//4.next方法的两件事情:获取元素并移动指针
String str = it.next();
System.out.println(str);
}
//当上面循环结束之后,迭代器的指针已经指向了最后没有元素的位置
//System.out.println(it.next());//NoSuchElementException
//迭代器遍历完毕,指针不会复位
System.out.println(it.hasNext());
//如果我们要继续第二次遍历集合,只能再次获取一个新的迭代器对象
Iterator<String> it2 = coll.iterator();
while(it2.hasNext()){
String str = it2.next();
System.out.println(str);
}
}
}
-
fail-fast机制:
-
当使用
Iterator
遍历时,如果在遍历过程中直接用集合自身的add
或remove
修改集合,会触发ConcurrentModificationException
。 -
原因:迭代器维护了一个
modCount
版本号,一旦检测到集合结构被修改且不是通过iterator.remove()
,就会报错。
-
-
源码核心:
-
ArrayList
内部的Itr
类,hasNext()
判断是否越界,next()
返回元素并移动游标。
-
补充细节:
-
增强for是
Iterator
的语法糖,不能在遍历中修改集合结构,否则同样会触发ConcurrentModificationException
。 -
增强for适用于读取场景,不适用于修改/删除场景。
场景题 5:
美团外卖系统中有个“骑手列表”,你需要遍历所有骑手并输出状态日志。因为仅是遍历输出,不涉及删除,你选用什么方式?为什么?
参考答案:
因为只是单纯遍历输出,推荐使用增强 for:
for (Rider rider : riderList) {
System.out.println(rider.getName() + " 状态:" + rider.getStatus());
}
增强 for 简洁、可读性好,底层使用迭代器实现,非常适合“只读”场景。美团外卖系统中实时监控日志模块,广泛使用增强 for 进行状态遍历。
1.4.3 lambda表达式
利用forEach方法,再结合lambda表达式的方式进行遍历
public class A07_CollectionDemo7 {
public static void main(String[] args) {
/*
lambda表达式遍历:
default void forEach(Consumer<? super T> action):
*/
//1.创建集合并添加元素
Collection<String> coll = new ArrayList<>();
coll.add("zhangsan");
coll.add("lisi");
coll.add("wangwu");
//2.利用匿名内部类的形式
//底层原理:
//其实也会自己遍历集合,依次得到每一个元素
//把得到的每一个元素,传递给下面的accept方法
//s依次表示集合中的每一个数据
/* coll.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});*/
//lambda表达式
coll.forEach(s -> System.out.println(s));
}
}
🧠 理论理解
JDK 1.8 开始,Collection
提供 forEach
默认方法,结合 lambda 表达式,实现函数式遍历,提高了代码简洁性:
list.forEach(s -> System.out.println(s));
底层本质是内部迭代,可配合 Stream 提供链式操作。
🏢 企业实战理解
-
OpenAI:在对话接口中,批量处理用户输入数据,结合
forEach
实现异步流式处理,提升响应效率。 -
字节跳动:推荐系统中用 lambda + Stream 链式处理多级过滤、打分操作,实现高效并行计算。
面试题 6:
OpenAI 面试:你如何看待 lambda 表达式结合 forEach 的使用?它适合什么场景?是否存在限制?
参考答案:
Lambda+forEach 提供了更现代化的遍历方式,代码简洁、可读性强:
list.forEach(s -> System.out.println(s));
适合场景:
-
对集合进行只读操作(如打印、统计)。
-
配合 Stream 使用,实现链式处理(如过滤、映射)。
限制:
-
不能像
Iterator
那样删除元素(UnsupportedOperationException
)。 -
内部迭代不可中断,无法像传统 for 循环中使用
break
、continue
控制流程。
OpenAI 在模型批量训练任务中,用 lambda+forEach 快速分发数据包,实现并行分片。
场景题 6:
你在英伟达 GPU 云平台开发日志收集模块,需要高效遍历 GPU 节点列表,并实时打印心跳包状态。如何简化代码?
参考答案:
推荐使用 lambda + forEach:
gpuNodeList.forEach(node -> System.out.println(
node.getId() + " 心跳状态:" + node.getHeartbeat()));
这种写法简洁明了,特别适合日志场景。英伟达在 GPU 云调度模块中,使用 lambda 结合 forEach 高效实现节点状态监控和日志收集。