ArrayList本质上是对Java原生数组的封装升级。普通数组一旦初始化,长度就固定不变,这在实际开发中极不灵活。ArrayList通过动态扩容机制完美解决了这个问题。
具体实现上,ArrayList内部维护了一个Object[] elementData数组。当我们执行new ArrayList()时,默认会创建一个长度为10的空数组(JDK8及以后版本是懒加载,第一次add时才初始化)。这个设计非常巧妙:
扩容触发条件很简单:当size + 1 > elementData.length时就会触发扩容。扩容过程本质上是创建一个新数组(通常按原容量1.5倍增长),然后将原数组内容拷贝到新数组。这里有个优化细节:在JDK8中,Arrays.copyOf()底层调用System.arraycopy(),这是一个native方法,执行效率极高。
重要提示:如果能预估数据规模,建议通过ArrayList(int initialCapacity)指定初始容量,避免频繁扩容带来的性能损耗。
LinkedList的实现比ArrayList复杂得多。它采用双向链表结构,每个节点包含三个部分:
在JDK实现中,LinkedList还维护了first和last指针分别指向首尾节点,这使得头尾操作都能达到O(1)时间复杂度。特别值得注意的是,Java的LinkedList是双向循环链表——头节点的prev指向尾节点,尾节点的next指向头节点。
链表结构的优势在于:
但实际开发中要注意:LinkedList的每个元素都有两个额外的引用开销(next和prev),当存储小型对象时,这个开销可能比数据本身还大。
我们用JOL工具实测一个包含100万个Integer的ArrayList:
code复制ArrayList实例占用: 24字节(对象头)
elementData数组: 4,000,016字节(100万元素+默认扩容空间)
可见ArrayList内存使用非常紧凑,每个元素只占4字节(引用大小)。但要注意:
同样存储100万个Integer的LinkedList:
code复制LinkedList实例占用: 24字节
每个Node节点: 24字节(对象头+3个引用)
总节点开销: 100万 × 24 = 24,000,000字节
惊人的内存差异!LinkedList的内存占用是ArrayList的6倍。这是因为:
实战建议:在内存敏感场景(如Android开发)中,应谨慎使用LinkedList。
我们通过JMH基准测试(单位:ns/op):
code复制Benchmark (size) Mode Cnt Score
ArrayList.get 10000 thrpt 10 2365.891
LinkedList.get 10000 thrpt 10 89124.677
ArrayList的随机访问比LinkedList快约37倍!这是因为:
code复制ArrayList.add(末尾) 10000 thrpt 10 4123.456
LinkedList.add(末尾) 10000 thrpt 10 3987.123
两者性能相当,因为:
code复制ArrayList.add(中间) 10000 thrpt 10 124578.33
LinkedList.add(中间) 10000 thrpt 10 98745.67
虽然LinkedList仍需遍历到中间位置,但省去了数组元素移动的开销。当数据量>1000时,LinkedList优势会更明显。
由于实现了Deque接口,LinkedList可以完美扮演以下角色:
这些操作的时间复杂度都是O(1),这是ArrayList无法比拟的优势。例如实现LRU缓存时,LinkedList就是理想选择。
最后分享一个真实案例:在电商系统中,购物车适合用ArrayList(频繁查询展示),而订单处理队列适合用LinkedList(先进先出)。