上文,我们分析了Collection单列集合接口下List接口的实现类ArrayList,本文就接着来看看他的好兄弟LinkedList
一、宏观关系
LinkedList和ArrayList一样,都是List接口的实现类。但是和ArrayList不同,LinkedList底层数据结构是双向链表
二、数据结构
//容量
transient int size = 0;
//头节点
transient Node<E> first;
//尾节点
transient Node<E> last;
//内部类Node
private static class Node<E> {
E item;
//记录下一个节点
Node<E> next;
//记录上一个节点
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
三、构造方法
//无参构造
public LinkedList() {
}
//有参构造(传入集合)
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
四、add相关
public boolean add(E e) {
//尾插法插入
linkLast(e);
return true;
}
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
LinkedList提供了双向链表的所有标准操作,包括在列表头部、尾部、指定位置插入和删除元素。默认的插入操作是尾插。
五、remove相关
public boolean remove(Object o) {
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
由上述remove方法可见,LinkedList也是可以存储null值的
六、ArrayList与LinkedList的区别
- 数据结构:
ArrayList底层数据结构是数组,而LinkedList底层数据结构是双向链表。 - 随机访问:
ArrayList直接通过索引(因为数组的连续存储特性)时间复杂度O(1);
LinkedList需要从头或者从尾遍历链表,时间复杂度为O(n) - 插入和删除操作:
ArrayList尾部插入和删除时间复杂度为O(1),只需要调整数组长度。但是指定位置插入删除需要移动其他元素,时间复杂度为O(n)。
LinkedList 开始或者结尾位置插入和删除都是O(1),因为只需要调整节点的指针,中间O(n) - 内存占用:
ArrayList:内存占用相对较小,因为它存储的是数组,数组中的每个元素都是紧密排列的。
LinkedList:内存占用相对较大,因为每个元素都是一个节点对象,需要额外的内存来存储前后元素的引用。 - 使用场景:
ArrayList:适合于频繁的查找操作,或者在列表的末尾添加和删除元素的场景。
LinkedList:适合于频繁的插入和删除操作,尤其是在列表的开头或中间,或者需要作为队列
两者都不是线程安全的。如果需要线程安全,可以使用 Vector(类似 ArrayList),或者使用
Collections.synchronizedList 方法包装 ArrayList 或 LinkedList。