2.List集合
2.1List集合的概述和特点【记忆】
-
List集合的概述
-
有序集合,这里的有序指的是存取顺序
-
用户可以精确控制列表中每个元素的插入位置,用户可以通过整数索引访问元素,并搜索列表中的元素
-
与Set集合不同,列表通常允许重复的元素
-
-
List集合的特点
-
存取有序
-
可以重复
-
有索引
-
🧠 理论理解
List 集合是 Collection 接口的重要子接口,它的核心特征是“有序、可重复”。所谓有序,是指元素的存储顺序与取出顺序保持一致,集合内部使用索引(从 0 开始)定位每个元素。与 Set 不同,List 允许添加重复元素,是典型的“顺序容器”。此外,List 是实现随机访问的基础数据结构,支持按索引增删改查,是日常开发中最常用的集合类型之一。
🏢 企业实战理解
-
字节跳动:在抖音推荐系统中,List 被用来保存用户历史点击序列,确保推荐逻辑按时间顺序回溯。
-
阿里巴巴:天猫购物车系统使用 List 保存当前用户已添加的商品项,确保显示顺序与添加顺序一致。
-
Google:Gmail 邮件列表分页缓存中利用 List 存储预加载的邮件对象,提高 UI 响应速度。
-
OpenAI:在 ChatGPT 的上下文窗口内,用户输入历史和 AI 回复历史按 List 顺序组合,作为大模型输入。
-
美团:美团骑手接单的队列是通过 List 实现的,以便骑手可以看到顺序任务列表。
面试题 1️⃣:请详细说一下 List 集合的特点,并和 Set 做对比。
参考答案:
List 是 Collection 接口的重要分支,具有以下几个特点:
-
有序性:List 存储元素的顺序和取出顺序一致,基于索引定位数据。
-
可重复性:允许存储重复元素。
-
支持索引访问:提供 get、set、add(index, element)、remove(index) 等基于位置的操作。
Set 不允许元素重复,内部实现常见如 HashSet 是无序的,LinkedHashSet 保持插入顺序,TreeSet 有序但基于自然顺序或 Comparator 排序。
大厂扩展:
字节跳动的抖音评论功能,采用 List 存储展示顺序,而“黑名单”去重列表则用 Set。实际开发中两者往往组合使用,保证既有顺序又有去重机制。
场景题 1️⃣:你在字节跳动做抖音短视频评论系统,产品要求评论展示要保持用户输入的顺序,同时允许重复评论内容。此时该用什么集合?为什么?
场景答案:
这种场景需要满足两个要求:
1️⃣ 顺序性 → 显示顺序必须和存储顺序一致;
2️⃣ 可重复性 → 不限制相同评论(比如用户连续打“666”)。
因此应选择 List 集合。相比 Set,List 允许重复元素,且保证插入顺序不变,完全符合评论列表的业务特性。字节跳动内部也采用了 ArrayList
封装数据展示层,并在服务端通过分页控制列表大小,提升加载速度。
2.2List集合的特有方法【应用】
-
方法介绍
方法名 描述 void add(int index,E element) 在此集合中的指定位置插入指定的元素 E remove(int index) 删除指定索引处的元素,返回被删除的元素 E set(int index,E element) 修改指定索引处的元素,返回被修改的元素 E get(int index) 返回指定索引处的元素 -
示例代码
public class MyListDemo { public static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("aaa"); list.add("bbb"); list.add("ccc"); //method1(list); //method2(list); //method3(list); //method4(list); } private static void method4(List<String> list) { // E get(int index) 返回指定索引处的元素 String s = list.get(0); System.out.println(s); } private static void method3(List<String> list) { // E set(int index,E element) 修改指定索引处的元素,返回被修改的元素 //被替换的那个元素,在集合中就不存在了. String result = list.set(0, "qqq"); System.out.println(result); System.out.println(list); } private static void method2(List<String> list) { // E remove(int index) 删除指定索引处的元素,返回被删除的元素 //在List集合中有两个删除的方法 //第一个 删除指定的元素,返回值表示当前元素是否删除成功 //第二个 删除指定索引的元素,返回值表示实际删除的元素 String s = list.remove(0); System.out.println(s); System.out.println(list); } private static void method1(List<String> list) { // void add(int index,E element) 在此集合中的指定位置插入指定的元素 //原来位置上的元素往后挪一个索引. list.add(0,"qqq"); System.out.println(list); } }
🧠 理论理解
List 接口在 Collection 的基础上扩展了四个关键方法,允许基于索引直接操作元素:
-
add(index, element)
:插入元素到指定位置,后续元素右移。 -
remove(index)
:删除指定索引处的元素,并返回被删除的对象。 -
set(index, element)
:替换指定索引的元素,返回原来的元素。 -
get(index)
:按索引获取元素。
这些方法提供了 List 的随机访问能力,使其比 Set 更适合有序数据场景。
🏢 企业实战理解
-
字节跳动:今日头条 APP 内部评论列表在前端缓存时,通过
set()
方法局部刷新点赞状态,避免全量更新。 -
阿里巴巴:支付宝账单列表在查询后,通过
add()
方法插入“广告位”或“提示卡片”,位置是动态可控的。 -
英伟达:GPU 分布式任务队列用
remove()
方法动态摘除已完成的任务,保证高并发下任务分发的有序性。 -
Google Drive:文件夹内的文件顺序重排功能用
set()
实现,支持拖拽式的 UI 排序。 -
滴滴出行:司机端的待处理乘客订单列表用 List 存储,当用户取消订单时用
remove()
精确删除。
面试题 2️⃣:add(index, element)
在 ArrayList 中底层是怎么实现的?
参考答案:
在 ArrayList 中,add(index, element)
需要先确保容量足够(如果容量不足会触发扩容机制),然后调用 System.arraycopy()
将 index 及其后面的元素整体向右平移一位,空出位置插入新元素。时间复杂度为 O(n)。
大厂思路:
阿里巴巴在购物车功能中,通过提前预估列表容量(如常买清单默认长度),减少 arraycopy
触发的次数,提高性能。
场景题 2️⃣:在美团开发“我的收藏”功能时,用户可以手动调整收藏列表的顺序。如何用 List 实现“在第 2 个位置插入新收藏”功能?涉及哪个方法?
场景答案:
这个功能对应的核心就是指定位置插入元素,add(int index, E element)
方法可以做到。例如:
favorites.add(1, "新收藏");
在美团中,这类“自定义顺序”的收藏数据会存储用户自定义顺序值,插入时不仅在内存 List 操作,还会同步更新数据库里的排序字段,确保前后端一致性。
2.3List集合的五种遍历方式【应用】
-
迭代器
-
列表迭代器
-
增强for
-
Lambda表达式
-
普通for循环
代码示例:
//创建集合并添加元素
List<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
//1.迭代器
/*Iterator<String> it = list.iterator();
while(it.hasNext()){
String str = it.next();
System.out.println(str);
}*/
//2.增强for
//下面的变量s,其实就是一个第三方的变量而已。
//在循环的过程中,依次表示集合中的每一个元素
/* for (String s : list) {
System.out.println(s);
}*/
//3.Lambda表达式
//forEach方法的底层其实就是一个循环遍历,依次得到集合中的每一个元素
//并把每一个元素传递给下面的accept方法
//accept方法的形参s,依次表示集合中的每一个元素
//list.forEach(s->System.out.println(s) );
//4.普通for循环
//size方法跟get方法还有循环结合的方式,利用索引获取到集合中的每一个元素
/*for (int i = 0; i < list.size(); i++) {
//i:依次表示集合中的每一个索引
String s = list.get(i);
System.out.println(s);
}*/
// 5.列表迭代器
//获取一个列表迭代器的对象,里面的指针默认也是指向0索引的
//额外添加了一个方法:在遍历的过程中,可以添加元素
ListIterator<String> it = list.listIterator();
while(it.hasNext()){
String str = it.next();
if("bbb".equals(str)){
//qqq
it.add("qqq");
}
}
System.out.println(list);
🧠 理论理解
List 支持 5 种遍历方式:
1️⃣ Iterator
2️⃣ 增强 for
3️⃣ Lambda 表达式(forEach)
4️⃣ 普通 for 循环(通过索引)
5️⃣ ListIterator(支持遍历时添加元素)
不同遍历方式适用于不同场景,例如:
-
普通 for 提供高效的索引访问。
-
Iterator 支持安全删除元素。
-
ListIterator 提供双向遍历和遍历中插入的功能。
🏢 企业实战理解
-
京东:商品列表页用普通 for 循环遍历商品数据,按索引映射到 UI 元素,提升性能。
-
字节跳动:西瓜视频播放器缓存历史记录时,使用 Lambda + forEach 快速遍历播放列表进行统计。
-
腾讯:QQ 聊天消息回放时用 ListIterator 实现“向上翻页”功能,支持向前检索消息记录。
-
美团外卖:骑手任务队列的遍历通过 Iterator 实现,支持任务超时后自动移除。
-
OpenAI:在生成多轮对话时,用增强 for 快速遍历对话历史,实现上下文整合。
面试题 3️⃣:ListIterator 和 Iterator 有什么区别?使用场景是?
参考答案:
-
Iterator:只能单向遍历,只支持删除元素。
-
ListIterator:双向遍历(有
hasPrevious()
、previous()
方法),支持在遍历中动态添加、修改元素,还能获取当前索引。
使用场景:
-
如果只是简单遍历+删除,用 Iterator;
-
如果有需求在遍历中插入/修改,或需要反向遍历,就要用 ListIterator。
大厂补充:
腾讯视频评论翻页功能利用 ListIterator 的反向遍历高效加载历史数据,避免重新构造列表。
场景题 3️⃣:你在 Google Photos 团队开发“相册时间线”功能,展示用户照片时需要反向遍历最近 1 年的照片列表。哪种遍历方式最合适?为什么?
场景答案:
这里推荐使用 ListIterator,它支持双向遍历,通过 hasPrevious()
和 previous()
方法可以反向读取数据,不需要再手动倒序或重组数据,非常高效。
代码示例:
ListIterator<Photo> iterator = photoList.listIterator(photoList.size());
while(iterator.hasPrevious()) {
Photo p = iterator.previous();
// 处理照片
}
在 Google Photos,时间线按时间倒序显示,ListIterator 非常适合这种“最新数据优先展示”的场景。
2.4 细节点注意:
List系列集合中的两个删除的方法
1.直接删除元素 2.通过索引进行删除
代码示例:
//List系列集合中的两个删除的方法
//1.直接删除元素
//2.通过索引进行删除
//1.创建集合并添加元素
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
//2.删除元素
//请问:此时删除的是1这个元素,还是1索引上的元素?
//为什么?
//因为在调用方法的时候,如果方法出现了重载现象
//优先调用,实参跟形参类型一致的那个方法。
//list.remove(1);
//手动装箱,手动把基本数据类型的1,变成Integer类型
Integer i = Integer.valueOf(1);
list.remove(i);
System.out.println(list);
🧠 理论理解
List 集合中 remove()
方法重载了两种形式:
1️⃣ remove(Object o)
删除指定对象(按值删除)。
2️⃣ remove(int index)
删除指定索引(按位置删除)。
⚠️ 注意:当传入的是数字时,Java 会优先匹配 remove(int index)
,这在删除 Integer 类型时容易出错,常见的做法是用 Integer.valueOf()
强制将数字装箱为对象类型。
🏢 企业实战理解
-
支付宝:删除收藏夹的数字 ID 时,开发团队在早期曾踩坑误用
remove(1)
删除索引,后用Integer.valueOf()
修复。 -
滴滴:司机端订单删除时,为区分订单序号和订单对象,严格封装了 remove 方法防止误删索引。
-
Google:安卓通讯录应用中,用
remove(Object o)
精确删除联系人对象,避免索引错误导致的数据错位问题。
面试题 4️⃣:如何在 List<Integer> 中删除指定值 1,而不是索引 1 位置的元素?解释原因。
参考答案:
因为 List 的 remove()
方法存在重载:
-
remove(int index)
:按索引删除 -
remove(Object o)
:按对象删除
如果写 list.remove(1)
,会被解析为删除索引 1 处的元素。为了删除值为 1 的元素,应该强制装箱:
list.remove(Integer.valueOf(1));
这样会匹配 remove(Object o)
。
大厂思路:
字节跳动在后台大数据清洗时,团队踩过这个坑,在删除 Integer 列表中指定值时出错,后来封装工具类明确类型,防止误删索引。
场景题 4️⃣:你在腾讯视频开发收藏夹功能,遇到一个 Bug:删除 List<Integer>
中的 1 时,删除的是索引 1 处的元素,而不是值为 1 的元素。请问你如何修改代码?
场景答案:
问题出现的原因是 remove(int index)
和 remove(Object o)
方法重载了。如果你直接写:
list.remove(1);
它会认为是删除索引 1的元素。
解决方案:要删除值为 1 的元素,必须手动装箱,写成:
list.remove(Integer.valueOf(1));
在腾讯内部,这种问题常出现在整型列表处理上,团队通过工具方法封装 removeByValue()
来隐藏细节,防止这种 Bug 重复发生。
3.数据结构
3.1数据结构之栈和队列【记忆】
-
栈结构
先进后出
-
队列结构
先进先出
🧠 理论理解
-
栈(Stack):后进先出(LIFO),常见于撤销/回退功能。
-
队列(Queue):先进先出(FIFO),适用于排队处理任务,如消息队列。
🏢 企业实战理解
-
字节跳动:抖音短视频撤销功能用栈实现回退,任务分发队列用队列结构高效处理视频上传。
-
阿里巴巴:蚂蚁金服风控队列使用高并发的阻塞队列保障实时性。
-
OpenAI:API 请求接入层用任务队列排队执行,缓冲高峰请求。
面试题 5️⃣:什么场景下优先使用栈,什么场景下优先使用队列?请举实际项目案例。
参考答案:
-
栈(LIFO):撤销、递归、回溯问题。
-
队列(FIFO):消息排队、限流、任务调度。
实际案例:
-
栈:美团点评 App 实现“返回上一步”功能时,使用栈记录操作路径。
-
队列:字节跳动内容审核系统中,用队列管理待审核视频任务,保证顺序执行。
场景题 5️⃣:你在 OpenAI 的 GPT-4 编辑器中实现“撤销/重做”功能,打算用 List 实现栈结构,具体怎么操作?
场景答案:
撤销/重做是栈结构典型场景,先进后出(LIFO)。可以用 LinkedList
来模拟栈:
-
撤销操作:用
addFirst()
存储每次编辑; -
重做操作:用
removeFirst()
弹出上一步。
示例代码:
LinkedList<EditAction> undoStack = new LinkedList<>();
undoStack.addFirst(newAction); // 记录操作
EditAction last = undoStack.removeFirst(); // 撤销
OpenAI 的实际产品中,这种“多步回退”功能都是基于栈实现,并且在多线程编辑场景中配合锁机制保证并发安全。
3.2数据结构之数组和链表【记忆】
-
数组结构
查询快、增删慢
-
队列结构
查询慢、增删快
🧠 理论理解
-
数组:连续内存,支持随机访问,增删慢。
-
链表:节点指针连接,增删快,随机访问慢。
🏢 企业实战理解
-
美团外卖:用户界面展示订单列表用数组存储,方便随机访问。
-
腾讯会议:实时消息队列链表实现,插入删除效率高。
-
英伟达:GPU 作业链表存储任务依赖,支持动态插入任务。
面试题 6️⃣:什么时候用数组?什么时候用链表?请结合性能分析。
参考答案:
-
数组:支持 O(1) 随机访问,适合读多写少、数据固定长度场景。
-
链表:支持 O(1) 插入/删除,适合频繁增删的场景,但访问性能差。
性能分析:
-
数组:增删需要整体移动,O(n)。
-
链表:随机访问 O(n)。
实际案例:
Google Docs 实现多人协作时,用链表维护操作日志,方便实时追加和回滚;而展示数据则用数组缓存提高访问速度。
场景题 6️⃣:你在英伟达实现 GPU 任务调度列表,高并发场景中任务频繁插入/删除,如何选择数据结构,为什么?
场景答案:
此时应选择 链表(LinkedList),因为链表的插入/删除是 O(1) 的,不需要像数组那样移动大量数据。虽然查找是 O(n),但 GPU 任务调度往往只需头尾插入和删除,不需要随机访问,非常适合链表。
英伟达的 GPU 任务池内部会将任务挂在一个链表中,用锁和条件变量确保线程安全。
4.List集合的实现类
4.1List集合子类的特点【记忆】
-
ArrayList集合
底层是数组结构实现,查询快、增删慢
-
LinkedList集合
底层是链表结构实现,查询慢、增删快
🧠 理论理解
-
ArrayList:基于数组,查询快,增删慢。
-
LinkedList:基于链表,增删快,查询慢。
-
Vector:线程安全版 ArrayList(已较少使用)。
🏢 企业实战理解
-
字节跳动:短视频缓存列表使用 ArrayList 提高读取速度。
-
京东:活动排期任务用 LinkedList 快速动态插入/删除。
-
腾讯:早期微信群聊列表用 Vector 线程安全实现。
面试题 7️⃣:简述 ArrayList 和 LinkedList 的主要区别?什么时候选用 LinkedList?
参考答案:
-
ArrayList:基于数组实现,查询快(O(1)),插入/删除慢(O(n))。
-
LinkedList:基于双向链表,查询慢(O(n)),插入/删除快(O(1))。
使用时机:
-
如果场景主要是随机访问/查找 → ArrayList
-
如果场景频繁插入/删除 → LinkedList
大厂补充:
阿里巴巴秒杀系统中,库存展示页用 ArrayList;后台限流队列,用 LinkedList 提高实时性。
场景题 7️⃣:你在阿里巴巴开发“双 11 秒杀系统”时,数据存储层该选用 ArrayList 还是 LinkedList?为什么?
场景答案:
选择 ArrayList,因为秒杀系统的页面展示需求是高频读取,几乎不涉及增删操作。ArrayList 基于数组结构,支持 O(1) 读取,性能更优。
阿里巴巴也在 Double 11 期间优化了秒杀系统,把订单队列用 LinkedList 实现(插入/删除多),展示列表则用 ArrayList。
4.2LinkedList集合的特有功能【应用】
-
特有方法
方法名 说明 public void addFirst(E e) 在该列表开头插入指定的元素 public void addLast(E e) 将指定的元素追加到此列表的末尾 public E getFirst() 返回此列表中的第一个元素 public E getLast() 返回此列表中的最后一个元素 public E removeFirst() 从此列表中删除并返回第一个元素 public E removeLast() 从此列表中删除并返回最后一个元素 -
示例代码
public class MyLinkedListDemo4 { public static void main(String[] args) { LinkedList<String> list = new LinkedList<>(); list.add("aaa"); list.add("bbb"); list.add("ccc"); // public void addFirst(E e) 在该列表开头插入指定的元素 //method1(list); // public void addLast(E e) 将指定的元素追加到此列表的末尾 //method2(list); // public E getFirst() 返回此列表中的第一个元素 // public E getLast() 返回此列表中的最后一个元素 //method3(list); // public E removeFirst() 从此列表中删除并返回第一个元素 // public E removeLast() 从此列表中删除并返回最后一个元素 //method4(list); } private static void method4(LinkedList<String> list) { String first = list.removeFirst(); System.out.println(first); String last = list.removeLast(); System.out.println(last); System.out.println(list); } private static void method3(LinkedList<String> list) { String first = list.getFirst(); String last = list.getLast(); System.out.println(first); System.out.println(last); } private static void method2(LinkedList<String> list) { list.addLast("www"); System.out.println(list); } private static void method1(LinkedList<String> list) { list.addFirst("qqq"); System.out.println(list); } }
🧠 理论理解
LinkedList 独有的双端操作:
-
addFirst()
/addLast()
-
getFirst()
/getLast()
-
removeFirst()
/removeLast()
适合实现队列、栈结构等场景。
🏢 企业实战理解
-
美团:骑手抢单列表用
addLast()
插入订单,队首订单最先处理。 -
字节跳动:直播弹幕队列通过
removeFirst()
实现实时展示。 -
Google Docs:协作编辑历史版本栈用
addFirst()
实现撤销功能。
面试题 8️⃣:LinkedList 如何实现队列和栈?请简述底层逻辑。
参考答案:
-
队列:用
addLast()
入队,removeFirst()
出队。 -
栈:用
addFirst()
入栈,removeFirst()
出栈。
底层逻辑:
LinkedList 通过双向链表实现,first
/last
节点快速操作头尾,无需移动其他节点,时间复杂度 O(1)。
企业案例:
美团骑手抢单列表实现时,用 LinkedList 的 addLast()
高效添加新任务,并用 removeFirst()
提供订单给骑手。
场景题 8️⃣:字节跳动飞书的“最近联系人”功能希望在最前面快速插入联系人,并能从末尾删除超时联系人,LinkedList 如何实现?
场景答案:
利用 addFirst()
和 removeLast()
:
-
新联系人:
list.addFirst(contact);
-
删除超时联系人:
list.removeLast();
双向链表的特性可以保证头尾插入删除都是 O(1) 操作,性能极佳。飞书类似的 IM 逻辑采用 LinkedList 实现这种滑动窗口式数据管理。
5. 源码分析
5.1 ArrayList源码分析:
核心步骤:
-
创建ArrayList对象的时候,他在底层先创建了一个长度为0的数组。
数组名字:elementDate,定义变量size。
size这个变量有两层含义: ①:元素的个数,也就是集合的长度 ②:下一个元素的存入位置
-
添加元素,添加完毕后,size++
扩容时机一:
-
当存满时候,会创建一个新的数组,新数组的长度,是原来的1.5倍,也就是长度为15.再把所有的元素,全拷贝到新数组中。如果继续添加数据,这个长度为15的数组也满了,那么下次还会继续扩容,还是1.5倍。
扩容时机二:
-
一次性添加多个数据,扩容1.5倍不够,怎么办呀?
如果一次添加多个元素,1.5倍放不下,那么新创建数组的长度以实际为准。
举个例子: 在一开始,如果默认的长度为10的数组已经装满了,在装满的情况下,我一次性要添加100个数据很显然,10扩容1.5倍,变成15,还是不够,
怎么办?
此时新数组的长度,就以实际情况为准,就是110
具体分析过程可以参见视频讲解。
添加一个元素时的扩容:
添加多个元素时的扩容:
🧠 理论理解
ArrayList 内部是数组结构,默认容量为 10,扩容机制:
-
单元素添加:容量不足时扩容为原容量 1.5 倍。
-
批量添加:一次性扩容满足新数据所需容量。
🏢 企业实战理解
-
字节跳动:列表预加载场景需关注扩容性能,防止频繁 resize 影响响应速度。
-
阿里巴巴:购物车系统优化时,曾将 ArrayList 改为初始化指定容量,避免多次扩容。
-
OpenAI:大模型 token 窗口数据管理,初始化 ArrayList 时直接分配大容量,减少内存重分配开销。
面试题 9️⃣:ArrayList 的扩容机制是怎样的?为什么 1.5 倍扩容?
参考答案:
-
默认容量:10
-
单个元素添加时,容量不足会扩容为
原容量 * 1.5
-
大批量添加时,直接扩到“所需容量”。
扩容原因:
1.5 倍扩容是权衡空间与时间复杂度的结果,防止频繁扩容的性能损耗,同时也不浪费太多内存。
大厂补充:
字节跳动的推荐服务在缓存列表初始化时,直接初始化容量(如 new ArrayList<>(1000)
),避免系统高峰期频繁扩容。
场景题 9️⃣:美团外卖首页推荐用了 ArrayList,发现高峰期频繁触发扩容导致卡顿。请问如何优化?
场景答案:
优化建议:提前设置合理容量。通过 new ArrayList<>(预计容量)
直接初始化,避免反复扩容带来的性能问题。
List<Goods> list = new ArrayList<>(5000);
美团在外卖首页中通过预估数据规模,初始化容量后大大减少了卡顿现象。
5.2 LinkedList源码分析:
底层是双向链表结构
核心步骤如下:
-
刚开始创建的时候,底层创建了两个变量:一个记录头结点first,一个记录尾结点last,默认为null
-
添加第一个元素时,底层创建一个结点对象,first和last都记录这个结点的地址值
-
添加第二个元素时,底层创建一个结点对象,第一个结点会记录第二个结点的地址值,last会记录新结点的地址值
具体分析过程可以参见视频讲解。
🧠 理论理解
底层是双向链表,节点包含:
-
上一个节点(prev)
-
元素值(item)
-
下一个节点(next)
适合频繁增删的场景。
🏢 企业实战理解
-
美团:餐厅排队功能用链表实现,顾客进出队高效。
-
滴滴:司机实时任务链基于 LinkedList 架构,支持动态增删。
面试题 🔟:LinkedList 为什么不支持高效随机访问?
参考答案:
因为 LinkedList 是链表结构,存储位置是离散的,访问某个索引的元素需要从头/尾遍历到指定位置,时间复杂度 O(n)。
企业补充:
美团外卖订单列表之前因误用 LinkedList 导致查询性能下降,后来改为 ArrayList 提高检索速度。
场景题 🔟:你在 Google Drive 实现“上传任务队列”,如何利用 LinkedList 实现生产者-消费者模型?
场景答案:
LinkedList 自带双端队列能力,适合作为任务队列。生产者 addLast()
添加任务,消费者 removeFirst()
取任务处理。
示例:
uploadQueue.addLast(new UploadTask());
UploadTask task = uploadQueue.removeFirst();
Google Drive 在多线程上传中用这种结构结合锁和条件队列,实现高效任务调度。
5.3 迭代器源码分析:
迭代器遍历相关的三个方法:
-
Iterator<E> iterator() :获取一个迭代器对象
-
boolean hasNext() :判断当前指向的位置是否有元素
-
E next() :获取当前指向的元素并移动指针
🧠 理论理解
-
hasNext()
检查是否还有元素。 -
next()
获取下一个元素并移动指针。 -
remove()
安全删除当前元素。
⚠️ 内部通过游标实现状态跟踪。
🏢 企业实战理解
-
字节跳动:自研工具链中基于 Iterator 封装安全遍历库,避免并发修改异常。
-
腾讯:QQ群成员批量管理时封装 Iterator 支持删除操作。
-
OpenAI:数据处理模块遍历大文件数据时通过迭代器封装延迟加载机制。
面试题 1️⃣1️⃣:为什么迭代器遍历时不能用集合的 remove 方法删除元素?会抛什么异常?
参考答案:
因为迭代器遍历时有“快速失败机制”(fail-fast),如果在遍历时用集合本身的方法修改,会抛出 ConcurrentModificationException
。应该使用迭代器自身的 remove()
方法安全删除。
企业案例:
腾讯 QQ 群批量踢人功能曾经遇到过 ConcurrentModificationException,后重构为 Iterator 迭代时删除,解决线程安全问题。
场景题 1️⃣1️⃣:你在 OpenAI 的 API 平台需要实现实时 IP 黑名单过滤,用迭代器遍历大列表时如何安全删除命中 IP?
场景答案:
必须用 Iterator 的 remove() 方法 删除,否则会触发 ConcurrentModificationException
。
示例:
Iterator<String> it = blacklist.iterator();
while (it.hasNext()) {
String ip = it.next();
if (shouldRemove(ip)) {
it.remove(); // 安全删除
}
}
OpenAI 在 API 限流模块就利用这种方式做实时黑名单扫描,确保删除安全且不中断服务。