时隔175天我终于想起了整理集合的第二篇:LinkedList
LinkedList的底层实现是双向链表,继承AbstractSequentialList,实现了List, Deque, Cloneable, java.io.Serializable,我们发现没有RandomAccess类,因为数组可以通过角标索引实现随机访问,而LinkedList只支持次序访问,而不支持随机访问,因为它的 get(int index)
,set(int index, E element)
, add(int index, E element)
, remove(int index)
都是基于迭代器实现的。
因为LinkedList实现了Deque 接口,所以可以将LinkedList当作双端队列使用。
在介绍ArrayList 的时候我们说:
add,delete 元素的时间复杂度为 O(n),size, isEmpty, get, set, iterator, and listIterator 等操作的复杂度为 O(1)。
而在LinkedList它的增删效率高,它的实现是链表,只需要修改链表节点指针,所以效率较高。
而改和查,都需要先迭代遍历定位到目标节点,所以效率较低。
同样,它是线程不安全的!允许元素为 null。
成员变量
//链表中元素的个数,transient 关键字修饰,序列化时该值不会被带上
transient int size = 0;
/**
* 头节点指针
* 指向第一个 Node
* 恒成立: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node<E> first;
/**
* 尾节点指针
* 指向最后一个 Node
* 恒成: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node<E> last;
节点Node结构
双向链表,不了解双向链表的可以看:https://juejin.im/post/6844903648154271757#heading-6
简单理解就是一个节点记录了它的前一个节点和后一个节点。
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);
}
添加元素
boolean addAll(Collection<? extends E> c)
//往链表末尾添加所有元素
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
boolean addAll(int index, Collection<? extends E> c)
//在指定位置插入集合c中所有元素
public boolean addAll(int index, Collection<? extends E> c) {
// 检查index位置是否超出链表范围, [0,size] 闭区间
checkPositionIndex(index);
//将集合c转变成Object数组 同时计算数组长度
Object[] a = c.toArray();
int numNew = a.length;
//如果新增元素数量为0,则不增加,并返回false
if (numNew == 0)
return false;
//index节点的前置节点,后置节点
Node<E> pred, succ;
//如果插入位置为尾部
if (index == size) {
//size节点(队尾)的后置节点一定是null,见上面的成员变量的恒等式
succ = null;
//前置节点是当前链表的最后一个节点(last)
pred = last;
} else {
//取出index节点,作为后置节点;node方法实际是在遍历
succ = node(index);
//前置节点是index节点的前一个节点
pred = succ.prev;
}
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
Node<E> newNode = new Node<>(pred, e, null);
//如果前置节点是空,说明是头结点
if (pred == null)
//first 要指向第一个插入元素封装的新 node
first = newNode;
else
//否则 前置节点的后置节点设置为新节点
pred.next = newNode;
//步进,当前的节点为前置节点了,为下次添加节点做准备
pred = newNode;
}
//循环结束后,判断,如果后置节点是null。 说明此时是在队尾append的
if (succ == null) {
//则设置尾节点
last = pred;
} else {
// 否则是在队中插入的节点 ,更新前置节点 后置节点
pred.next = succ;
//更新后置节点的前置节点
succ.prev = pred;
}
//修改链表长度
size += numNew;
//记录修改次数
modCount++;
return true;
}
根据index 查询出Node
Node<E> node(int index) {
//通过下标获取某个node 的时候(增、查),判断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--)
x = x.prev;
return x;
}
}
add(E e) /add(int index, E element)
public boolean add(E e) {
linkLast(e);
return true;
}
public void add(int index, E element) {
//校验index是否越界
checkPositionIndex(index);
//判断是否在末尾添加
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
末尾添加
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++;//修改次数+1
}
在节点前添加
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
//保存后置节点的前置节点
final Node<E> pred = succ.prev;
//以前置和后置节点和元素值e 构建一个新节点
final Node<E> newNode = new Node<>(pred, e, succ);
//新节点newNode是原节点succ的前置节点
succ.prev = newNode;
//如果之前的前置节点是空,说明succ是原头结点。所以新节点是现在的头结点
if (pred == null)
first = newNode;
else
//否则构建前置节点的后置节点为newNode
pred.next = newNode;
size++;//修改数量
modCount++;//修改次数+1
}
删除元素
E remove()
虽然没有指定index,但是这个方法其实只是移除第一个结点。
public E remove() {
return removeFirst();
}
public E removeFirst() {
final Node<E> f = first;
if (f == null)
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.item = null;
f.next = null; // help GC
first = next;
//如果下一个节点是null代表,当前链表只有一个节点,所以要更新last
if (next == null)
last = null;
else
//否则第二个节点就是新的头结点,需要把prev置为null
next.prev = null;
size--;//链表长度-1
modCount++;//修改次数+1
return element;
}
E remove(int index)
移除指定位置的值
public E remove(int index) {
//校验index合法性
checkElementIndex(index);
return unlink(node(index));
}
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;//记录元素值
final Node<E> next = x.next;//保存当前节点的后置节点
final Node<E> prev = x.prev;//保存当前节点的前置节点
//前置节点为null
if (prev == null) {
//则首节点为原本节点的next
first = next;
} else {
//否则 更新前置节点的后置节点
prev.next = next;
//将要删除节点的前置节点置null,方便GC
x.prev = null;
}
//如果后置节点为null,说明是尾节点
if (next == null) {
last = prev;
} else {
//否则更新 后置节点的前置节点
next.prev = prev;
//删除节点的后置节点为null,方便GC
x.next = null;
}
//将删除节点的元素值置null,方便GC
x.item = null;
size--;
modCount++;
return element;
}
boolean remove(Object o)
删除指定值,分为null和不为null的两种方式遍历查找
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;
}
E removeLast()
移除最后一个节点
public E removeLast() {
final Node<E> l = last;
if (l == null)
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;
l.item = null;
l.prev = null; // help GC
last = prev;
//判断是否只有一个节点的链表,删除后链表为空
if (prev == null)
// 首节点置null
first = null;
else
//前置节点成了最后一个节点
prev.next = null;
size--;
modCount++;
return element;
}
修改
一个小小值得注意的地方,set不修改modCount!
public E set(int index, E element) {
//检查越界[0,size)
checkElementIndex(index);
//取出节点
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
查询
get(int index)/getFirst()/getLast()
public E get(int index) {
checkElementIndex(index);//检查越界[0,size)
return node(index).item;//调用node()方法 取出 Node节点
}
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}
int indexOf(Object o)/lastIndexOf(Object o)
根据对象查所在的位置,和删除类似
public int indexOf(Object o) {
int index = 0;
//如果目标对象是null
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null)
return index;
index++;
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))
return index;
index++;
}
}
return -1;
}
从尾至头遍历链表,找到目标元素值为o的节点
public int lastIndexOf(Object o) {
int index = size;
if (o == null) {
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (x.item == null)
return index;
}
} else {
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (o.equals(x.item))
return index;
}
}
return -1;
}
转数组
Object[] toArray()
new 一个新数组然后遍历链表,将每个元素存在数组里,返回
public Object[] toArray() {
Object[] result = new Object[size];
int i = 0;
for (Node<E> x = first; x != null; x = x.next)
result[i++] = x.item;
return result;
}
T[] toArray(T[] a)
可以将节点的值存入传入的数组中,但是如果传入的数组太小则会重新生成size大小的新数组,存储链表的所有数据,否则数组的前size长度会被覆盖成链表的值,但是传入数组的size后的数据不会受到影响。
public <T> T[] toArray(T[] a) {
//如果数组长度不够,生成新的数组
if (a.length < size)
a = (T[])java.lang.reflect.Array.newInstance(
a.getClass().getComponentType(), size);
int i = 0;
Object[] result = a;
for (Node<E> x = first; x != null; x = x.next)
//覆盖传入数组的值
result[i++] = x.item;
//覆盖角标为size的数组值
if (a.length > size)
a[size] = null;
return a;
}
清空
遍历所有的节点把所有节点对象的属性置为null,便于GC
public void clear() {
// Clearing all of the links between nodes is "unnecessary", but:
// - helps a generational GC if the discarded nodes inhabit
// more than one generation
// - is sure to free memory even if there is a reachable Iterator
for (Node<E> x = first; x != null; ) {
Node<E> next = x.next;
x.item = null;
x.next = null;
x.prev = null;
x = next;
}
first = last = null;
size = 0;
modCount++;
}
ListIterator 迭代器
istIterator是一个更加强大的Iterator迭代器的子类型,它只能用于各种List类的访问。尽管Iterator只能向前移动,但是ListIterator可以双向移动。它还可以产生相对于迭代器在列表中指向的当前位置的前一个和后一个元素的索引,可以用set()
方法替换它访问过得最后一个元素。
private Node<E> lastReturned;
private Node<E> next;//用于记录当前节点
private int nextIndex;//用于记录当前节点所在索引
private int expectedModCount = modCount;
ListItr(int index) {
// assert isPositionIndex(index);
next = (index == size) ? null : node(index);
nextIndex = index;
}
LinkedList 和 ArrayList 的区别
LinkedList 是基于双向链表实现的,ArrayList 是基于数组实现的。
LinkedList 添加、插入、删除元素速度更快,而 ArrayList 查询速度更快。
LinkedList 使用 Node 来存储数据,每个 Node 中不仅存储元素的值,还存储了前一个 Node 的引用和后一个 Node 的引用,占用内存更多,而 ArrayList 使用 Object 数组来存储数据,占用内存相比会更少。
LinkedList 和 ArrayList 都是线程不安全的。
最后
其实LinkedList还有一些其他的方法,如peek/peek等等,这些和上述的方法差不多,不再多做介绍。
参考
https://zhangxutong.blog.csdn.net/article/details/77341098
https://juejin.im/post/6844903521431584782
https://wenshixin.gitee.io/blog/2018/12/16/LinkedList%E4%B8%AA%E4%BA%BA%E7%90%86%E8%A7%A3/