目录
2,插入单个元素到指定位置add(int index, E element)
3,在表头插入数据addFirst(),push()在表尾插入数据addLast(),offer()
3,删除链表首个为o的元素removeFirstOccurrence(Object o)
4,删除链表最后为o的元素removeLastOccurrence(Object o)
8,移除多个元素removeAll(Collection c)
9,移除集合c中不存在的元素retainAll(Collection c)
1,LinkedList简介
LinkedList ,基于节点实现的双向链表的 List ,每个节点都指向前一个和后一个节点从而形成链表。相比 ArrayList 来说,我们日常开发使用 LinkedList 相对比较少。
2,类图
LinkedList 实现的接口、继承的抽象类,如下图所示:
如下 3 个接口是 ArrayList 一致的:
- List接口
- Serializable接口
- Cloneable接口
如下1个接口是少于ArrayList的:
RandomAccess接口,LinkedList不同于ArrayList的很大一点,不支持随机访问
如下一个接口是多于ArrayList的:
Deque接口,提供双端队列的功能,LinkedList 支持快速的在头尾添加元素和读取元素,所以很容易实现该特性。
继承了 AbstractSequentialList 抽象类,它是 AbstractList 的子类,实现了只能连续访问“数据存储”(例如说链表)的 #get(int index)
、#add(int index, E element)
等等随机操作的方法。可能这样表述有点抽象,点到AbstractSequentialList 抽象类中看看这几个方法,基于迭代器顺序遍历后,从而实现后续的操作。
- 但是呢,LinkedList 和 ArrayList 多是一个有点“脾气”的小伙子,都为了结合自身的特性,更加高效的实现,都选择了重写了 AbstractSequentialList 的方法,嘿嘿。
- 不过一般情况下,对于支持随机访问数据的继承 AbstractList 抽象类,不支持的继承 AbstractSequentialList 抽象类。
3,属性
LinkedList 一共有 3 个属性。如下图所示:
-
transient int size = 0;
-
transient Node<E> first;
-
transient Node<E> last;
代码如下所示:
transient int size = 0;
/**
* Pointer to first node.
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node<E> first;
/**
* Pointer to last node.
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node<E> last;
first和last属性:链表的头尾指针.
- 在初始的时候,first和last指向null,因为此时没有Node节点,在添加完首个节点后,创建对应的Node节点node1,前后指向null , 此时first和last指向Node节点
- 若继续添加一个元素后,创建对应的Node节点node2,其
prev = node1
和next = null
,而node1
的prev = null
和next = node2
。此时,first
保持不变,指向node1
,last
发生改变,指向node2
。 size
属性:链表的节点数量。通过它进行计数,避免每次需要 List 大小时,需要从头到尾的遍历。
对应代码如下:
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
/**
* 链表大小
*/
transient int size = 0;
/**
头节点
*/
transient Node<E> first;
/**
尾节点
*/
transient Node<E> last;
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;
}
}
}
4,构造方法
LinkedList 一共有两个构造方法,我们分别来看看。代码如下:
public LinkedList() {
}
public LinkedList(Collection<? extends E> c) {
this();
// 添加 c 到链表中
addAll(c);
}
相比 ArrayList 来说,因为没有容量一说,所以不需要提供 #ArrayList(int initialCapacity)
这样的构造方法。
5,添加方法
1,添加单个元素add(E e)
add(E e)
方法,顺序添加单个元素到链表。代码如下:
相比 ArrayList 来说,无需考虑容量不够时的扩容
public boolean add(E e) {
// <X> 添加末尾
linkLast(e);
return true;
}
void linkLast(E e) {
// <1> 记录原 last 节点
final Node<E> l = last;
// 第一个参数表示,newNode 的前一个节点为 l 。
// 第二个参数表示,e 为元素。
// 第三个参数表示,newNode 的后一个节点为 null 。
final Node<E> newNode = new Node<>(l, e, null);
// <3> last 指向新节点
last = newNode;
// <4.1> 如果原 last 为 null ,说明 first 也为空,则 first 也指向新节点
if (l == null)
// <4.2> 如果原 last 非 null ,说明 first 也非空,则原 last 的 next 指向新节点。
first = newNode;
else
l.next = newNode;
size++;
// <6> 增加数组修改次数
modCount++;
}
2,插入单个元素到指定位置add(int index, E element)
插入单个元素到指定位置add(int index, E element)
方法, 。代码如下:
public void add(int index, E element) {
checkPositionIndex(index); // 校验不要超过范围
// 如果刚好等于链表大小,直接添加到尾部即可
if (index == size)
linkLast(element);
else
//添加到第 index 的节点的前面
linkBefore(element, node(index));
}
linkBefore(E e, Node<E> succ)
方法,添加元素 e
到 succ
节点的前面。代码如下
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
// 获得 succ 的前一个节点
final Node<E> pred = succ.prev;
// 创建新的节点 newNode
final Node<E> newNode = new Node<>(pred, e, succ);
//设置 succ 的前一个节点为新节点
succ.prev = newNode;
// 如果 pred 为 null ,说明 first 也为空,则 first 也指向新节点
if (pred == null)
first = newNode;
// 如果 pred 非 null ,说明 first 也为空,则 pred 也指向新节点
else
pred.next = newNode;
// 增加链表大小
size++;
// 增加数组修改次数
modCount++;
}
node(int index)
方法,获得第 index
个 Node 节点,会去判断是不是超过链表一半去决定是否使用倒序还是先序遍历
Node<E> node(int index) {
// assert isElementIndex(index);
// 如果 index 小于 size 的一半,就正序遍历,获得第 index 个节点
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--) 如果 index 大于 size 的一半,就倒序遍历,获得第 index 个节点
x = x.prev;
return x;
}
}
3,在表头插入数据addFirst(),push()
在表尾插入数据addLast(),offer()
因为 LinkedList 实现了 Deque 接口,所以它实现了 addFirst(E e)
和 addLast(E e)
方法,分别添加元素到链表的头尾。代码如下:
public void addFirst(E e) {
linkFirst(e);
}
public void addLast(E e) {
linkLast(e);
}
linkLast(E e)
方法,和 add(E e)
方法是一致的
addFirst(E e)
方法,调用 linkFirst(E e)
方法,添加元素到队头。代码如下
因为 LinkedList 实现了 Queue 接口,所以它实现了 push(E e)
和 offer(E e)
方法,添加元素到链表的头尾。就是上面2个方法代码如下:
public void push(E e) {
addFirst(e);
}
public boolean offer(E e) {
return add(e);
}
4,添加多个元素addAll(Collection<? extends E> c)
addAll(Collection<? extends E> c)
方法,批量添加多个元素。代码如下:
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index);
//将 c 转成 a 数组
Object[] a = c.toArray();
int numNew = a.length;
if (numNew == 0) // 如果无添加元素,直接返回 false 数组未变更
return false;
// 获得第 index 位置的节点 succ ,和其前一个节点 pred
Node<E> pred, succ;
if (index == size) { // 如果 index 就是链表大小,那说明插入队尾,所以 succ 为 null ,pred 为 last
succ = null;
pred = last;
} else {
succ = node(index);
pred = succ.prev;
}
// 遍历 a 数组,添加到 pred 的后面
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o; // 创建新节点
Node<E> newNode = new Node<>(pred, e, null);
if (pred == null) // 如果 pred 为 null ,说明 first 也为 null ,则直接将 first 指向新节点
first = newNode;
else // pred 下一个指向新节点
pred.next = newNode;
pred = newNode; // 修改 pred 指向新节点
}
//修改 succ 和 pred 的指向
if (succ == null) { // 如果 succ 为 null ,说明插入队尾,则直接修改 last 指向最后一个 pred
last = pred;
} else { // 如果 succ 非 null ,说明插入到 succ 的前面
pred.next = succ; //下一个指向 succ
succ.prev = pred; // succes 前一个指向 pred
}
//增加链表大小
size += numNew;
modCount++; //增加数组修改次数
return true; //返回 true 数组有变更
}
6,链表扩容
Linked没有扩容,因为添加元素都是通过Node的前后指定
7,删除元素
1. 移除单个元素remove(int index)
remove(int index)
方法,移除指定位置的元素,并返回该位置的原元素。代码如下:
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index)); // 获得第 index 的 Node 节点,然后进行移除
}
- 首先,调用
node(int index)
方法,获得第index
的 Node 节点。然后调用unlink(Node<E> x)
方法,移除该节点。
unlink(Node<E> x)
方法,代码如下:
E unlink(Node<E> x) {
// assert x != null;获得 x 的前后节点 prev、next
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
//将 prev 的 next 指向下一个节点
if (prev == null) {
first = next; //如果 prev 为空,说明 first 被移除,则直接将 first 指向 next
} else { //如果 prev 非空
prev.next = next; // prev 的 next 指向 next
x.prev = null; // x 的 pre 指向 null
}
//将 next 的 prev 指向上一个节点
if (next == null) {//如果 next 为空,说明 last 被移除,则直接将 last 指向 prev
last = prev;
} else { //如果 next 非空
next.prev = prev; // next 的 prev 指向 prev
x.next = null; // x 的 next 指向 null
}
//将 x 的 item 设置为 null ,帮助 GC
x.item = null;
//减少链表大小
size--;
// 增加数组的修改次数
modCount++;
return element;
}
2,删除首个为o的元素remove(Object o)
remove(Object o)
方法,移除首个为 o
的元素,并返回是否移除到。代码如下:
public boolean remove(Object o) {
if (o == null) { // o 为 null 的情况
for (Node<E> x = first; x != null; x = x.next) { // 顺序遍历,找到 null 的元素后,进行移除
if (x.item == null) {
unlink(x);
return true;
}
}
} else {// 顺序遍历,找到等于 o 的元素后,进行移除
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
3,删除链表首个为o的元素removeFirstOccurrence(Object o)
removeFirstOccurrence(Object o)删除链表首个为o代码如下:
就是去调用remove(Object o)
public boolean removeFirstOccurrence(Object o) {
return remove(o);
}
4,删除链表最后为o的元素removeLastOccurrence(Object o)
removeLastOccurrence(Object o)删除链表最后为o的元素代码如下:
public boolean removeLastOccurrence(Object o) { // 移除首个
if (o == null) {
for (Node<E> x = last; x != null; x = x.prev) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = last; x != null; x = x.prev) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
5,删除首个节点remove()
remove()
方法,移除链表首个节点。代码如下:
public E remove() {
return removeFirst();
}
public E removeFirst() {
final Node<E> f = first;
if (f == null) //如果链表为空,抛出 NoSuchElementException 异常
throw new NoSuchElementException();
return unlinkFirst(f); //移除链表时首个元素
}
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;
final Node<E> next = f.next; // 获得 f 的下一个节点
f.item = null; /// 设置 f 的 item 为 null ,帮助 GC
f.next = null; // help GC 设置 f 的 next 为 null ,帮助 GC
first = next; // 修改 fisrt 指向 next
if (next == null) // 如果链表只有一个元素,说明被移除后,队列就是空的,则 last 设置为 null
last = null;
else
next.prev = null;
size--; // 链表大小减一
modCount++; // 增加数组修改次数
return element;
}
6,删除尾结点removeLast()
removeLast()
方法,移除链表最后一个节点。代码如下:
public E removeLast() {
final Node<E> l = last;
if (l == null) // 如果链表为空,则抛出 NoSuchElementException 移除
throw new NoSuchElementException();
return unlinkLast(l); // 移除链表的最后一个元素
}
private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
final E element = l.item;
final Node<E> prev = l.prev; // 获得 f 的上一个节点
l.item = null; // 设置 l 的 item 为 null ,帮助 GC
l.prev = null; // help GC设置 l 的 prev 为 null ,帮助 GC
last = prev; // 修改 last 指向 prev
if (prev == null) // 如果链表只有一个元素,说明被移除后,队列就是空的,则 first 设置为 null
first = null;
else
prev.next = null;
size--; // 链表大小减一
modCount++; // 增加数组修改次数
return element;
}
7,pool方法里面有移除头尾操作
public E poll() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
public E pop() {
return removeFirst();
}
public E pollFirst() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
public E pollLast() {
final Node<E> l = last;
return (l == null) ? null : unlinkLast(l);
}
8,移除多个元素removeAll(Collection<?> c)
removeAll(Collection<?> c)
方法,批量移除指定的多个元素。代码如下:
该方法是通过父类AbstractCollection来实现的通过迭代器遍历LinkedList判断c包含就删除
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
boolean modified = false;
Iterator<?> it = iterator(); // 获得迭代器
while (it.hasNext()) { // 通过迭代器遍历
if (c.contains(it.next())) { // 如果 c 中存在该元素,则进行移除
it.remove();
modified = true; // 标记修改
}
}
return modified;
}
9,移除集合c中不存在的元素retainAll(Collection<?> c)
和removeAll(Collection<?> c)
方法相反代码如下
public boolean retainAll(Collection<?> c) {
Objects.requireNonNull(c);
boolean modified = false;
Iterator<E> it = iterator();
while (it.hasNext()) {
if (!c.contains(it.next())) {
it.remove();
modified = true;
}
}
return modified;
}