理论认知
ArrayList
和 LinkedList
都是 Java 集合框架(Java Collections Framework)中的一部分,它们用于存储动态大小的元素列表。然而,它们在内部实现、性能特性以及用途上有一些显著的差异。以下是它们之间的主要区别:
-
内部实现:
ArrayList
:基于动态数组实现。在内存中,它使用连续的空间来存储元素。当元素被添加到列表中时,如果数组的大小不足以容纳新元素,则创建一个更大的数组,并将旧数组的内容复制到新数组中。LinkedList
:基于双向链表实现。每个元素(称为节点)都包含数据和指向其前一个节点和后一个节点的引用。因此,它不需要在内存中占用连续的空间。
-
性能特性:
- 访问元素:由于
ArrayList
使用连续的内存空间,因此可以通过索引直接访问任何位置的元素,时间复杂度为 O(1)。而LinkedList
需要从头或尾节点开始遍历链表以访问特定位置的元素,时间复杂度为 O(n)。 - 添加/删除元素:在
ArrayList
的末尾添加或删除元素是高效的(O(1)),但在列表的开头或中间添加或删除元素则可能需要移动大量元素(O(n))。相反,LinkedList
在列表的开头或结尾添加或删除元素是高效的(O(1)),因为只需修改相邻节点的引用即可。在列表的中间添加或删除元素需要遍历链表以找到正确的位置(O(n)),但不需要移动大量元素。
- 访问元素:由于
-
空间占用:
ArrayList
在内存中占用连续的空间,因此空间利用率较高。但是,当数组大小不足以容纳新元素时,它可能会创建更大的数组,这可能会导致空间浪费。LinkedList
由于使用链表结构,不需要连续的内存空间,因此更灵活。但是,由于每个节点都需要额外的空间来存储引用,因此其空间占用可能会稍大一些。
-
线程安全:
ArrayList
和LinkedList
都不是线程安全的。如果在多线程环境中使用它们,并且多个线程同时修改列表,可能会导致数据不一致或其他并发问题。如果需要线程安全的列表,可以使用Collections.synchronizedList()
方法或CopyOnWriteArrayList
和ConcurrentLinkedQueue
等并发集合类。
-
用途:
ArrayList
通常用于需要频繁访问元素但不经常添加或删除元素的情况。由于其高效的索引访问和较低的空间占用,它是许多应用程序的首选数据结构。LinkedList
通常用于需要频繁在列表的开头或结尾添加或删除元素的情况。由于其双向链表结构,它在这些操作上具有优势。此外,它还可以用作栈(使用push()
和pop()
方法)或队列(使用offer()
、poll()
或peek()
方法)。
下面我将使用Java代码来展示ArrayList
和LinkedList
的一些基本操作,并突出它们之间的区别。
ArrayList 示例
import java.util.ArrayList;
public class ArrayListExample {
public static void main(String[] args) {
// 创建一个 ArrayList
ArrayList<String> arrayList = new ArrayList<>();
// 添加元素到 ArrayList 的末尾
arrayList.add("Element 1");
arrayList.add("Element 2");
arrayList.add("Element 3");
// 访问 ArrayList 中的元素(通过索引)
System.out.println("Element at index 1: " + arrayList.get(1));
// 在 ArrayList 的开头添加元素
// 注意:这可能需要移动所有现有的元素,因此效率较低
arrayList.add(0, "Element 0");
// 删除 ArrayList 中的元素(通过索引)
// 注意:这可能需要移动被删除元素之后的所有元素,因此效率较低
arrayList.remove(2);
// 遍历 ArrayList
for (String element : arrayList) {
System.out.println(element);
}
}
}
LinkedList 示例
下滑查看解决方法
import java.util.LinkedList;
public class LinkedListExample {
public static void main(String[] args) {
// 创建一个 LinkedList
LinkedList<String> linkedList = new LinkedList<>();
// 添加元素到 LinkedList 的末尾
linkedList.add("Element 1");
linkedList.add("Element 2");
linkedList.add("Element 3");
// 访问 LinkedList 中的元素(通常不这样做,因为效率较低)
// 但可以通过迭代器或从头/尾节点访问
// 这里为了演示仍然使用 get(index)
System.out.println("Element at index 1: " + linkedList.get(1));
// 在 LinkedList 的开头添加元素
// 这是 LinkedList 的优势之一,因为它不需要移动任何元素
linkedList.addFirst("Element 0");
// 在 LinkedList 的结尾删除元素
// 这是另一个优势,因为它也不需要移动任何元素
linkedList.removeLast();
// 遍历 LinkedList(通常使用迭代器,但为了简单这里使用 for-each)
for (String element : linkedList) {
System.out.println(element);
}
// 使用 LinkedList 作为栈(使用 push 和 pop)
linkedList.push("New Element as Stack");
System.out.println(linkedList.pop()); // 删除并返回栈顶元素
// 使用 LinkedList 作为队列(使用 offer, poll, peek)
linkedList.offer("New Element as Queue");
System.out.println(linkedList.peek()); // 查看队列头元素但不删除
System.out.println(linkedList.poll()); // 删除并返回队列头元素
}
}
在上面的示例中,你可以看到ArrayList
和LinkedList
在添加、删除和访问元素时的不同。特别是,当在列表的开头或中间添加/删除元素时,LinkedList
的效率更高,因为它不需要移动大量元素。而ArrayList
在访问元素时效率更高,因为它可以通过索引直接访问。同时,LinkedList
还提供了作为栈和队列使用的额外方法。