ArrayList
和 LinkedList
是 Java 中 List
接口的两个常用实现类,它们在内部结构和性能上有显著的区别。
ArrayList
ArrayList
底层是基于动态数组实现的,因此它支持快速访问(即根据索引访问元素),但在添加或删除元素(尤其是在中间位置)时,性能会相对较低,因为这涉及到数组的移动和扩容。
优点:
- 根据索引访问元素(
get(int index)
)的时间复杂度是 O(1)。 - 在末尾添加元素(
add(E element)
)的性能通常较高(摊销时间复杂度为 O(1))。
缺点:
- 在中间或前面插入或删除元素的时间复杂度是 O(n)。
- 数组需要连续内存空间,扩容时会有一定的性能开销。
代码示例:
import java.util.ArrayList;
public class ArrayListExample {
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<>();
// 添加元素
arrayList.add("A");
arrayList.add("B");
arrayList.add("C");
// 访问元素
System.out.println("Element at index 1: " + arrayList.get(1)); // 输出 B
// 在末尾添加元素
arrayList.add("D");
// 在指定位置插入元素
arrayList.add(2, "E"); // 插入到 C 前面
// 删除元素
arrayList.remove("B");
// 遍历
for (String element : arrayList) {
System.out.println(element);
}
}
}
LinkedList
LinkedList
底层是基于双向链表实现的,因此它在添加或删除元素(尤其是在中间位置)时性能较好,但在根据索引访问元素时,性能会相对较低。
优点:
- 在任意位置插入或删除元素的时间复杂度是 O(1)(前提是你已经有了该位置的引用)。
- 不需要连续内存空间。
缺点:
- 根据索引访问元素(
get(int index)
)的时间复杂度是 O(n)。 - 相对于
ArrayList
,占用更多的内存(因为每个节点都存储了前后引用)。
代码示例:
import java.util.LinkedList;
public class LinkedListExample {
public static void main(String[] args) {
LinkedList<String> linkedList = new LinkedList<>();
// 添加元素
linkedList.add("A");
linkedList.add("B");
linkedList.add("C");
// 访问元素
System.out.println("Element at index 1: " + linkedList.get(1)); // 输出 B
// 在末尾添加元素
linkedList.add("D");
// 在指定位置插入元素
linkedList.add(2, "E"); // 插入到 C 前面
// 删除元素
linkedList.remove("B");
// 遍历
for (String element : linkedList) {
System.out.println(element);
}
}
}
比较总结
-
访问速度:
ArrayList
:基于索引的访问速度快,O(1)。LinkedList
:基于索引的访问速度慢,O(n)。
-
插入和删除性能:
ArrayList
:在末尾插入速度快,摊销 O(1);在中间或前面插入/删除速度慢,O(n)。LinkedList
:在任意位置插入/删除速度快,O(1)(前提是有该位置的引用)。
-
内存开销:
ArrayList
:较少内存开销,因为基于数组实现。LinkedList
:较高内存开销,因为每个元素都是一个节点,包含前后引用。
-
数据结构:
种类 | 数据结构 |
---|---|
ArrayList | 动态数组 |
LinkedList | 双向链表 |
ArrayList基于动态数组实现,意味着它在内部使用一个数组来存储元素。当数组容量不足时,ArrayList会自动扩容,通常是将容量增加到原来的1.5倍。LinkedList则基于双向链表实现,每个节点包含数据以及指向前一个和后一个节点的引用。
- 性能特点:
种类 | 插入/删除性能 | 访问性能 | 内存开销 |
---|---|---|---|
ArrayList | 中间或前面插入/删除较慢,O(n) 末尾插入较快,摊销O(1) | 基于索引的访问速度快,O(1) | 相对较低,因为基于数组实现 |
LinkedList | 任意位置插入/删除较快,O(1)(前提是有该位置的引用) | 基于索引的访问速度慢,O(n) | 相对较高,因为每个节点需要存储额外的前后引用 |
ArrayList在插入和删除元素时,如果操作位置不是末尾,则可能需要移动大量元素,导致性能下降。而LinkedList在插入和删除元素时,只需修改相邻节点的引用即可,效率较高。但在访问元素时,ArrayList可以通过索引直接定位,而LinkedList则需要从头或尾开始遍历链表,效率较低。
6.扩容机制:
种类 | 扩容机制 |
---|---|
ArrayList | 当数组容量不足时,自动扩容至原来的1.5倍 |
LinkedList | 基于链表实现,不需要扩容 |
ArrayList在添加元素时,如果当前数组容量不足以容纳新元素,则会自动扩容。扩容操作通常涉及内存分配和数据复制,可能会对性能产生一定影响。而LinkedList则不需要进行扩容操作,因为它基于链表实现,可以动态地添加和删除节点。
- 线程安全性
种类 | 线程安全性 |
---|---|
ArrayList | 非线程安全 |
LinkedList | 非线程安全 |
ArrayList和LinkedList都不是线程安全的。如果需要在多线程环境下使用它们,则需要外部同步机制来确保线程安全。Java还提供了Vector类作为线程安全的动态数组实现,但通常性能较低。
- 使用场景
种类 | 适用场景 |
---|---|
ArrayList | 适用于读多写少的场景,如缓存、查询较多的列表 |
LinkedList | 适用于写多读少的场景,如队列、双端队列等需要频繁插入/删除操作的场景 |
ArrayList由于其快速的随机访问性能,适合用于那些需要频繁访问元素但不经常修改列表的场景。而LinkedList则适合用于那些需要频繁插入和删除元素但不经常访问元素的场景。
综上所述,ArrayList和LinkedList在数据结构、性能特点、扩容机制、线程安全性和使用场景等方面存在显著差异。在选择使用哪种列表时,需要根据具体的应用场景和需求进行权衡和选择。