予枫个人主页 个人专栏: 《Java 从入门到起飞》《读研码农的干货日常》《Java 面试刷题指南》 Debug 这个世界Return 更好的自己引言作为Java开发者ArrayList和LinkedList是日常开发中最常用的两个List实现类也是面试高频考点。很多人只知道“ArrayList查得快、LinkedList删得快”却不懂底层原理和真正的性能差异导致开发中用错集合、面试时被追问到哑口无言。本文从源码拆解入手结合性能实测讲清两者核心区别、适用场景再补充面试官高频追问帮你吃透这两个集合面试不踩坑、开发不踩雷文章目录引言一、ARRAYLIST与LINKEDLIST核心底层原理源码拆解1.1 ArrayList底层动态数组1.2 LinkedList底层双向链表1.3 底层结构对比二、核心性能对比附测试案例2.1 核心操作性能对比表2.2 关键性能误区澄清2.3 简单测试代码可直接复制运行三、适用场景精准匹配开发不踩坑3.1 ArrayList适用场景优先选择3.2 LinkedList适用场景谨慎选择3.3 避坑提醒四、面试官高频追问实战必备4.1 追问1ArrayList的扩容机制JDK7和JDK8有什么区别4.2 追问2LinkedList为什么不适合随机查询4.3 追问3ArrayList和LinkedList都是线程安全的吗如果不是怎么保证线程安全4.4 追问4ArrayList的remove(int index)和remove(Object o)有什么区别底层怎么实现的五、全文总结一、ARRAYLIST与LINKEDLIST核心底层原理源码拆解要搞懂两者的区别首先得明白它们的底层存储结构——这是所有差异的根源。建议点赞收藏跟着源码一步步拆解理解更透彻 ✅1.1 ArrayList底层动态数组ArrayList的底层是Object类型的动态数组核心是通过数组扩容实现元素的动态存储JDK8源码核心逻辑如下// ArrayList核心成员变量JDK8privatestaticfinalintDEFAULT_CAPACITY10;// 默认初始容量privateObject[]elementData;// 存储元素的核心数组privateintsize;// 实际存储的元素个数初始容量默认是10当我们new ArrayList()时并不会直接初始化数组而是在第一次添加元素时add方法才创建容量为10的数组懒加载优化。扩容机制当元素个数达到数组容量size elementData.length时会触发扩容扩容公式为「新容量 旧容量 旧容量 1」也就是扩容1.5倍。核心特点数组是连续的内存空间通过索引index可以直接定位元素这也是它查询快的核心原因。1.2 LinkedList底层双向链表LinkedList的底层是双向链表JDK8取消了循环链表设计每个元素都是一个Node节点节点之间通过指针关联源码核心逻辑如下// LinkedList核心内部类Node节点privatestaticclassNodeE{Eitem;// 节点存储的元素NodeEnext;// 指向后一个节点的指针NodeEprev;// 指向前一个节点的指针Node(NodeEprev,Eelement,NodeEnext){this.prevprev;this.itemelement;this.nextnext;}}// LinkedList核心成员变量transientNodeEfirst;// 头节点transientNodeElast;// 尾节点transientintsize;// 元素个数存储特点链表的节点在内存中是分散存储的不需要连续空间每个节点只需要记录前后节点的指针即可。核心优势添加、删除元素时只需要修改节点的指针指向不需要移动大量元素这是它增删快的核心原因。1.3 底层结构对比ArrayList动态数组连续内存空间通过索引定位元素扩容机制1.5倍扩容LinkedList双向链表分散内存空间通过指针关联节点无扩容机制按需创建节点二、核心性能对比附测试案例很多人误以为“ArrayList查得快LinkedList删得快”是绝对的其实这是误区——性能差异和操作场景增删的位置、数据量大小密切相关。以下是基于JDK8的实测对比测试数据量10万条元素。2.1 核心操作性能对比表操作场景ArrayListLinkedList性能结论随机查询get(index)快O(1)慢O(n)ArrayList完胜直接通过索引定位无需遍历头部插入add(0, e)慢O(n)快O(1)LinkedList完胜只需修改头节点指针尾部插入add(e)快O(1)无扩容时快O(1)两者差距不大ArrayList无扩容时略快中间插入add(index, e)慢O(n)慢O(n)都慢ArrayList要移动元素LinkedList要遍历找位置头部删除remove(0)慢O(n)快O(1)LinkedList完胜尾部删除remove(size-1)快O(1)快O(1)两者差距不大中间删除remove(index)慢O(n)慢O(n)都慢原因同中间插入2.2 关键性能误区澄清✅ 误区1LinkedList增删一定比ArrayList快错只有“头部/尾部”的增删操作LinkedList才占优势中间增删时两者都需要遍历/移动元素性能差距不大甚至ArrayList在数据量小时更优。✅ 误区2ArrayList扩容会严重影响性能不一定ArrayList扩容是1.5倍扩容扩容频率不高且JDK8做了懒加载优化避免了初始化时的内存浪费日常开发中无需过度担心。2.3 简单测试代码可直接复制运行importjava.util.ArrayList;importjava.util.LinkedList;importjava.util.List;publicclassListPerformanceTest{publicstaticvoidmain(String[]args){intsize100000;Listlt;Integergt;arrayListnewArrayList();Listlt;Integergt;linkedListnewLinkedList();// 测试尾部插入性能longstart1System.currentTimeMillis();for(inti0;isize;i){arrayList.add(i);}longend1System.currentTimeMillis();System.out.println(ArrayList尾部插入10万条数据(end1-start1)ms);longstart2System.currentTimeMillis();for(inti0;isize;i){linkedList.add(i);}longend2System.currentTimeMillis();System.out.println(LinkedList尾部插入10万条数据(end2-start2)ms);// 测试随机查询性能longstart3System.currentTimeMillis();for(inti0;isize;i){arrayList.get(i);}longend3System.currentTimeMillis();System.out.println(ArrayList随机查询10万条数据(end3-start3)ms);longstart4System.currentTimeMillis();for(inti0;isize;i){linkedList.get(i);}longend4System.currentTimeMillis();System.out.println(LinkedList随机查询10万条数据(end4-start4)ms);}}运行结果仅供参考ArrayList尾部插入10万条数据8msLinkedList尾部插入10万条数据12msArrayList随机查询10万条数据1msLinkedList随机查询10万条数据1236ms从结果能明显看出随机查询时ArrayList的性能碾压LinkedList尾部插入两者差距不大。三、适用场景精准匹配开发不踩坑结合上面的底层原理和性能对比直接给出精准的适用场景收藏起来开发时直接对照用 ✅3.1 ArrayList适用场景优先选择日常开发中查询操作远多于增删操作的场景如展示列表、数据查询缓存。增删操作主要集中在尾部的场景如日志收集、数据批量添加。需要频繁通过索引访问元素的场景如根据下标获取列表元素。3.2 LinkedList适用场景谨慎选择增删操作主要集中在头部或尾部的场景如队列实现、栈实现、消息队列的入队出队。数据量不大且需要频繁在头部/尾部操作的场景如简单的任务队列。3.3 避坑提醒⚠️ 避坑不要在需要频繁随机查询、中间增删的场景中使用LinkedList否则会严重影响系统性能。⚠️ 避坑ArrayList初始化时若已知数据量建议指定初始容量如new ArrayList(1000)避免频繁扩容。四、面试官高频追问实战必备这部分是核心亮点结合面试场景补充面试官常问的延伸问题帮你提前准备面试不慌 4.1 追问1ArrayList的扩容机制JDK7和JDK8有什么区别JDK7new ArrayList()时直接初始化容量为10的数组扩容时先计算新容量旧容量*1.5再复制数组。JDK8new ArrayList()时不初始化数组懒加载第一次add元素时才创建容量为10的数组扩容逻辑和JDK7一致但优化了空数组的复用EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA。4.2 追问2LinkedList为什么不适合随机查询因为LinkedList的底层是双向链表没有索引要获取指定index的元素必须从头部或尾部开始遍历直到找到目标节点时间复杂度是O(n)而ArrayList是数组通过索引直接定位时间复杂度是O(1)。4.3 追问3ArrayList和LinkedList都是线程安全的吗如果不是怎么保证线程安全都不是线程安全的在多线程环境下若同时进行增删操作会出现ConcurrentModificationException并发修改异常。解决方案使用Collections.synchronizedList()包装简单但性能一般使用线程安全的集合类如CopyOnWriteArrayList适合读多写少场景手动加锁如synchronized。4.4 追问4ArrayList的remove(int index)和remove(Object o)有什么区别底层怎么实现的remove(int index)根据索引删除先检查索引合法性再通过System.arraycopy()移动数组元素覆盖要删除的元素时间复杂度O(n)。remove(Object o)根据元素内容删除先遍历数组找到元素的索引再调用remove(int index)删除时间复杂度O(n)若元素为null会特殊处理避免空指针。五、全文总结本文从底层源码出发拆解了ArrayList动态数组和LinkedList双向链表的核心结构通过性能实测对比了两者在不同操作场景下的差异最后给出了精准的适用场景和面试官高频追问核心总结如下底层结构决定性能数组→查询快链表→头尾增删快无绝对优劣按需选择查询多、尾部增删→ArrayList头尾增删多→LinkedList面试重点扩容机制、线程安全、性能差异、适用场景。