一文让你清楚ArrayList和LinkedList有什么区别 以及 他们的底层原理、源码解析
😀大家好!我是向阳🌞,一个想成为优秀全栈开发工程师的有志青年!
📔今天来说一说如何使用AOP实现异步记录操作日志。
1.前言
在Java开发中,ArrayList和LinkedList是最比较的两种集合类型。它们虽然都实现了List接口,但底层实现和性能特点却大相径庭。了解它们的底层原理和适用场景,能够帮助我们在实际开发中做出更优的选择。本文将通过源码解析和性能对比,带你彻底理解二者的核心差异😎。
2.源码分析
这里的源码分析都是jdk1.8下的ArrayList和LinkedList。
2.1.ArrayList
ArrayList的底层数据结构是动态数组,我们可以看一下ArrayList的源码,其中最主要的就是elementData这个属性,这个Object就是我们要维护的数组。
当我们初始化一个ArrayList数组时,我们不指定数组容量的大小,这个时候默认是创建一个空数组,不会给我们分配容量,size大小也为0。
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
这里的 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 就是ArrayList的属性之一,是一个空数组。
第一次添加元素
当我们第一次添加元素时,也就是使用 add() 操作,我们才会分配空间,我们来看一下他的源码。
当我们添加元素的时候,首先我们会执行 ensureCapacityInternal() 方法。
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
我们进到方法里面,看下面的执行步骤:
第一步: 我们先计算需要的最小容量大小,我们是第一次添加元素,所以会进到if里面,返回初始化的默认数组大小,也就是 10。
第二步:记录修改次数加一并且判断是否需要扩容,将我们第一步计算出来的需要的最小容量与当前数组的长度相比,如果大于0,说明数组长度不够用了,则进行扩容。
我们来看看扩容代码干了些什么事情:
- 首先计算旧数组的容量大小和需要扩容的数组长度大小。
- 接下来判断扩容后的容量大小和需要的最小容量,如果扩容后的容量大小 小于 需要的最小容量,则新容量大小等于需要的最小容量。
- 还有一个判断是判断新容量的大小如果太大,则进行调整。
- 最后进行数组拷贝。
添加第 2 ~ 10 个元素
添加第2 ~ 9 个元素时,我们还是执行上面那些方法,只不过有些方法的执行路径发生改变,比如下图,我们直接返回的是需要的最小容量大小,而不是返回默认值10了。
这部分添加都是不需要扩容的,因为我们第一步初始化了数组的大小为10,所以 minCapacity 减去 elementData.length 的大小都是小于0的。
添加第十一个元素
当我们添加第11个元素的时候,这个时候 minCapacity 减去 elementData.length 大于0了,我们进入扩容方法。
我们添加第十一个元素的时候,经过debug发现,最新扩容的数组大小为15,说明我们进行了扩容。
2.2.LinkedList
LinkedList的底层采用的是双向链表的数据结构,我们可以看到他的属性,一个first节点,一个last节点,他们都是Node对象。
我们来看一下Node的结构,一共有三个属性,当前的元素,指向下一个节点和指向上一个节点。
我们这里来用个图来解释一下这个双向链表的结构:
双向链表的添加操作这里就不多赘述了,这个很简单。
3.对比
特性 | ArrayList | LinkedList |
---|---|---|
底层结构 | 动态数组 | 双向链表 |
随机访问 | O(1)(通过索引直接访问) | O(n)(需要遍历节点) |
头部插入/删除 | O(n)(需要移动元素) | O(1)(修改头节点引用) |
尾部插入/删除 | O(1)(均摊时间,可能触发扩容) | O(1)(修改尾节点引用) |
中间插入/删除 | O(n)(需要移动元素) | O(n)(遍历到指定位置+修改引用) |
内存占用 | 较小(仅存储数据+数组预留空间) | 较大(每个节点存储前后引用) |
扩容机制 | 自动扩容(1.5倍),可能浪费空间 | 无扩容,按需分配节点 |
线程安全 | 非线程安全 | 非线程安全 |
适用场景 | 高频查询、尾部操作 | 频繁插入/删除、队列或栈的实现 |
4.结语
ArrayList和LinkedList各有优劣,没有绝对的“更好”,只有“更适合”。理解它们的底层原理和性能特点,能够帮助我们在实际开发中根据具体需求做出合理选择。例如,实现一个需要快速随机访问的只读列表时,ArrayList是更优解;而需要频繁在头部插入数据的场景(如实现队列),LinkedList则更具优势。
——👦[作者]:向阳256
——⏳[更新]:2025.4.29
——🥰本人技术有限,如果有不对指正需要更改或者有更好的方法,欢迎到评论区留言。