LinkedList概述
LinkedList 是 Java 集合中比较常用的数据结构,与 ArrayList 一样,实现了 List 接口,只不过 ArrayList 是基于数组实现的,而 LinkedList 是基于链表实现的。LinkedList 底层是一个双链表。是一个直线型的链表结构。所以 LinkedList 插入和删除方面要优于 ArrayList,而随机访问上则 ArrayList 性能更好。除了 List 接口之外,LinkedList 还实现了 Deque,Cloneable,Serializable 三个接口。这说明该数据结构支持队列,克隆和序列化操作的。与 ArrayList 一样,允许 null 元素的存在,且是不支持多线程的。
LinkedList图解
package java.util;
import java.util.function.Consumer;
/**
* LinkedList底层使用一个Node数据结构,有前后两个指针,双向链表实现的。相对数组,链表插入效率较高,只需要更改前后两个指针即可;
* 另外链表不存在扩容问题,因为链表不要求存储空间连续,每次插入数据都只是改变last指针;另外,链表所需要的内存比数组要多,
* 因为他要维护前后两个指针;它适合删除,插入较多的场景。另外,LinkedList还实现了Deque接口。
*/
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
/**
* 记录当前容器中有多少元素
* LinkedList的size是int类型,但是后面会看到LinkedList大小实际只受内存大小的限制
* 也就是LinkedList的size大小可能发生溢出,返回负数
*/
transient int size = 0;
/**
* LinkedList的头节点。
*/
transient Node<E> first;
/**
* LinkedList的尾节点。
*/
transient Node<E> last;
/**
* 无参构造
*/
public LinkedList() {
}
/**
* 有参构造,传入集合。
*/
public LinkedList(Collection<? extends E> c) {
//调用无参构造
this();
//将集合添加至链表
addAll(c);
}
/**
* 把参数中的元素作为链表的第一个元素。
* 私有方法,我们只能通过addFirst()方法调用
*/
private void linkFirst(E e) {
final Node<E> f = first;
//然后新建一个节点,把next指向f,然后自身设置为头结点。
final Node<E> newNode = new Node<>(null, e, f);
//新节点赋值给头结点
first = newNode;
//判断f是否为空,如果为空的话,说明原来的LinkedList为空,所以同时也需要把新节点设置为尾节点
if (f == null)
last = newNode;
else
//否则将f放置头节点的下一个节点
f.prev = newNode;
//size和modCount自增。
size++;
modCount++;//用于判断是否当前访问的链表是否是最新的(快速失败)
}
/**
* 把参数中的元素作为链表的最后一个元素。
*/
void linkLast(E e) {
//把尾节点存储起来。
final Node<E> l = last;
//新建一个节点,把last指向l,然后自身设置为尾结点。
final Node<E> newNode = new Node<>(l, e, null);
//将新节点赋值给尾节点
last = newNode;
//判断l是否为空,如果为空说明原来的LinkedList为空,所以同时也需要把新节点设置为头节点
if (l == null)
first = newNode;
else
//否则直接将新节点新增在最后作为尾节点
l.next = newNode;
//size和modCount自增。
size++;
modCount++;
}
/**
* 在非空节点succ之前插入元素e。
*/
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
//将succ的前节点存储起来
final Node<E> pred = succ.prev;
//新建一个节点,将节点插入到succ前面,则新节点的前节点为succ原来的前节点,next为succ节点
final Node<E> newNode = new Node<>(pred, e, succ);
//succ的前节点置为新节点(因为是双向链表)
succ.prev = newNode;
//如果pred==null,说明succ节点为头节点
if (pred == null)
first = newNode;
else
//否则将succ原头节点的next节点置为newNode(因为是双向链表)
pred.next = newNode;
//size和modCount自增。
size++;
modCount++;
}
/**
* 删除LinkedList中第一个节点。(该节点不为空)(并且返回删除的节点的值)
* 使用该方法的前提是参数f是头节点,而且f不能为空。
* 私有方法,我们没有权限使用。我们只能通过removeFirst()方法删除第一个元素
*/
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
//将f节点元素存储起来
final E element = f.item;
//将f节点的下一个节点存储起来(因为我们需要设置f节点的下一个节点为头结点,而且需要把f节点的值设置为空)
final Node<E> next = f.next;
//把f的值和它的next设置为空,把它的下一个节点设置为头节点。
f.item = null;
f.next = null; // help GC
first = next;
//判断一个它的下一个节点是否为空,如果为空的话,则需要把last设置为空
if (next == null)
last = null;
else
//否则需要把next的prev设置为空,因为next现在指代头节点。
next.prev = null;
//size减一和modCount自增。
size--;
modCount++;
return element;//返回删除的元素
}
/**
* 和unlinkFirst()方法差不多
* 删除LinkedList的最后一个节点。(该节点不为空)(并且返回删除节点对应的值)
*/
private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
//将l节点元素存储起来
final E element = l.item;
//将l节点的前一个节点存储起来(因为我们需要设置l节点的前一个节点为尾结点,而且需要把l节点的值设置为空)
final Node<E> prev = l.prev;
//把l的值和它的prev设置为空,把它的前一个节点设置为尾节点。
l.item = null;
l.prev = null; // help GC
last = prev;
//判断一个它的前一个节点是否为空,如果为空的话,则需要把first设置为空
if (prev == null)
first = null;
else
//否则需要把next的prev设置为空,因为next现在指代头节点。
prev.next = null;
//size减一和modCount自增。
size--;
modCount++;
return element;
}
/**
* 删除一个节点(该节点不为空)
*/
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;
//如果删除节点的前节点为空,说明x为头节点。删除后将x的next节点作为头节点
if (prev == null) {
first = next;
} else {
//否则将它的前节点的下一个节点置为它的下一个节点(有点绕,意思就是:把该节点的前、后节点重新链接起来)
prev.next = next;
x.prev = null;
}
//如果删除节点的后节点为空,说明x为尾节点。删除后将x的prev节点作为尾节点
if (next == null) {
last = prev;
} else {
//否则将它的后节点的前一个节点置为它的前一个节点(有点绕,意思就是:把该节点的前、后节点重新链接起来)
next.prev = prev;
x.next = null;
}
//删除节点的值设置为空
x.item = null;
//size减一和modCount自增。
size--;
modCount++;
return element;//返回删除的节点
}
/**
* 获取链表的第一个元素(头节点)
*/
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;
}
/**
* 删除链表的第一个元素(头节点)
*/
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
//删除头节点
return unlinkFirst(f);
}
/**
* 删除链表最后一个元素(尾节点)
*/
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
//删除尾节点
return unlinkLast(l);
}
/**
* 在链表的头部添加一个新的元素
*/
public void addFirst(E e) {
linkFirst(e);
}
/**
* 在链表的尾部添加一个新的元素
*/
public void addLast(E e) {
linkLast(e);
}
/**
* 判断LinkedList是否包含某一个元素。
*/
public boolean contains(Object o) {
return indexOf(o) != -1;
}
/**
* 获取当前LinkedList中元素的个数
*/
public int size() {
return size;
}
/**
* 添加一个新元素。
*/
public boolean add(E e) {
//把最新的元素添加至链表的尾部,调用了linkLast()方法。
linkLast(e);
return true;
}
/**
* 从LinkedList中删除指定元素。(且只删除第一次出现的指定的元素)(如果指定的元素在集合中不存在,则返回false,否则返回true)
*/
public boolean remove(Object o) {
//判断是否为null,如果为null则不能用equals方法
if (o == null) {
//从头节点开始遍历LinkedList,判断是否有与object相等的元素,如果有,则删除第一次出现的指定的元素,如果没找不到,则返回false。
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
//从头节点开始遍历LinkedList,判断是否有与object相等的元素,如果有,则删除第一次出现的指定的元素,如果没找不到,则返回false。
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
/**
* 将整个集合添加到链表中
*/
public boolean addAll(Collection<? extends E> c) {
//通过调用addAll(int index, Collection<? extends E> c) 完成集合的添加。
return addAll(size, c);
}
/**
* 将指定集合添加至集合最后
*/
public boolean addAll(int index, Collection<? extends E> c) {
//校验参数是否合法
checkPositionIndex(index);
//将集合转为object[]数组
Object[] a = c.toArray();
int numNew = a.length;
//如果集合参数为空则直接返回false
if (numNew == 0)
return false;
//pred暂存index前一个节点用于赋值新节点的pred,succ是暂存当前index位置的节点
Node<E> pred, succ;
if (index == size) {
//如果index==size说明直接在链表最后添加,succ=null,pred = last.
succ = null;
pred = last;
} else {
//否则在链表中间添加,获取index位置的节点
succ = node(index);
pred = succ.prev;
}
//循环添加集合中的节点
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
//创建新的节点添加到当前位置遍历依次添加
Node<E> newNode = new Node<>(pred, e, null);
//如果当前节点的前一个节点为null说明当前节点为头节点
if (pred == null)
first = newNode;
else
//否则设置前一个节点的next为当前节点(双向链表)
pred.next = newNode;
//然后将当前节点赋于pred继续遍历
pred = newNode;
}
//如果当前节点等于null,(也就是新添加的节点位于LinkedList集合的最后一个元素的后面)
//则说明遍历完后pred指向的是LinkedList中的最后一个元素,所以把last指向pred指向的节点。
if (succ == null) {
last = pred;
} else {
//当succ不为空的时候,表明在LinkedList集合中间添加的元素。需要把pred的next指向succ,succ的prev指向pred。
pred.next = succ;
succ.prev = pred;
}
//最后把集合的大小设置为新的大小(size + numNew)。
size += numNew;
//modCount自增
modCount++;
return true;
}
/**
* 清空LinkedList中的所有元素
*/
public void clear() {
//直接遍历整个LinkedList,然后把每个节点都置空
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也设置为空
size = 0;
//但是modCount仍然自增
modCount++;
}
/**
* 获取对应index的节点的值。
*/
public E get(int index) {
//校验参数是否合法
checkElementIndex(index);
//返回index节点元素
return node(index).item;
}
/**
* 设置对应index的节点的值。
*/
public E set(int index, E element) {
//校验参数是否合法
checkElementIndex(index);
//获取index位置的节点
Node<E> x = node(index);
//修改节点的值
E oldVal = x.item;
x.item = element;
//并返回旧值
return oldVal;
}
add
/**
* 在指定的位置上添加新的元素。
*/
public void add(int index, E element) {
//校验参数是否合法
checkPositionIndex(index);
//判断新添加的元素是否位于LinkedList的最后,是,则直接调用linkLast()方法添加即可
if (index == size)
linkLast(element);
else
//否则的话,调用linkBefore()添加
linkBefore(element, node(index));
}
/**
* 移除指定位置上的元素
*/
public E remove(int index) {
//校验参数是否合法
checkElementIndex(index);
//直接调用node(index)获取位置元素,然后调用unlink删除元素
return unlink(node(index));
}
/**
* 判断参数index是否是元素的索引,它用来判断index是否在LinkedList索引范围内
*/
private boolean isElementIndex(int index) {
return index >= 0 && index < size;
}
/**
* 判断当添加元素的时候,传进来的index是否合法,而且我们新添加的元素可能在LinkedList最后一个元素的后面,
* 所以这里允许index<=size。
*/
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
}
/**
* 返回越界的信息。当前index值和size对比信息
*/
private String outOfBoundsMsg(int index) {
return "Index: "+index+", Size: "+size;
}
/**
* 判断参数index是否是元素的索引(如果不是则抛出异常)
*/
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
/**
* 判断当添加元素的时候,传进来的index是否合法,而且我们新添加的元素可能在LinkedList最后一个元素的后面,
* 所以这里允许index<=size。如果不合法,则抛出异常。
*/
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
/**
* 获取指定索引上的节点(返回Node)
* LinkedList还对整个做了优化,不是盲目地直接从头进行遍历,而是先比较一下index更靠近链表(LinkedList)的头节点
* 还是尾节点。然后进行遍历,获取相应的节点。
*/
Node<E> node(int index) {
// assert isElementIndex(index);
//size >> 1相当于size除2,如果index<(size >> 1)说明index更靠近头节点,则从头节点开始遍历知道index位置
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
//否则index更靠近尾节点,则从尾开始遍历至index
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
/**
* 返回指定元素第一次出现的下标(从0开始)
*/
public int indexOf(Object o) {
int index = 0;
//判断是否为null,如果为null则不能用equals方法
if (o == null) {
//从头节点开始遍历LinkedList,判断是否有与object相等的元素,如果有,则返回对应的位置index,如果找不到,则返回-1。
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null)
return index;
index++;
}
} else {
//从头节点开始遍历LinkedList,判断是否有与object相等的元素,如果有,则返回对应的位置index,如果找不到,则返回-1。
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))
return index;
index++;
}
}
return -1;
}
/**
* 在LinkedList中查找object在LinkedList中的位置。从后向前遍历,返回第一出线的元素的索引,如果没找到,则返回-1
*/
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;
}
// 后面的方法都是队列的一些操作。
/**
* 获取队头元素不删除,返回头结点的值,为null则返回null。
*/
public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
/**
* 获取队头元素不删除。返回头节点的值。和peek()方法不同
* 如果头节点为空,则抛出NoSuchElementException。
*/
public E element() {
return getFirst();
}
/**
* 出队列,移除头结点,并返回移除的头结点的值。如果头结点为空,则返回null
*/
public E poll() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
/**
* 出队列,移除头结点的值,并返回移除的头结点的值。如果头结点为空,则抛出NoSuchElementException异常。
*/
public E remove() {
return removeFirst();
}
/**
* 入队列,在链表的尾部添加一个元素
*/
public boolean offer(E e) {
return add(e);
}
// 双端队列
/**
* 在队列的头部添加一个新的元素
*/
public boolean offerFirst(E e) {
addFirst(e);
return true;
}
/**
* 在队列的尾部添加一个元素
*/
public boolean offerLast(E e) {
addLast(e);
return true;
}
/**
* 获取队列的第一个元素(不删除),如果为空,则返回null
*/
public E peekFirst() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
/**
* 获取队列的第后一个元素(不删除),弹出队列尾节点的元素,如果为空,则返回null
*/
public E peekLast() {
final Node<E> l = last;
return (l == null) ? null : l.item;
}
/**
* 出队列(删除节点),删除头节点并返回头节点
*/
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);
}
/**
* 入队列,新添加的元素位于LinkedList的头结点
*/
public void push(E e) {
addFirst(e);
}
/**
* 出队列,弹出头结点的元素,删除头结点的值,并返回删除的值
*/
public E pop() {
return removeFirst();
}
/**
* 移除第一次出现的元素。(从前向后遍历集合)
* 底层调用remove()方法,通过从前向后遍历集合。
*/
public boolean removeFirstOccurrence(Object o) {
return remove(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;
}
/**
* LinkedList提供了两种迭代器,一种是返回Iterator,另一种返回ListIterator。
*/
public ListIterator<E> listIterator(int index) {
//校验参数是否合法
checkPositionIndex(index);
//返回ListIterator迭代器
return new ListItr(index);
}
//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;
}
}
/**
* 返回Iterator迭代器
*/
public Iterator<E> descendingIterator() {
return new DescendingIterator();
}
/**
* 克隆的辅助方法
*/
private LinkedList<E> superClone() {
try {
//直接调用超类的clone()方法,然后把得到的Object对象转换为LinkedList类型。
return (LinkedList<E>) super.clone();
} catch (CloneNotSupportedException e) {
throw new InternalError(e);
}
}
/**
* 克隆方法
*/
public Object clone() {
//首先获取从辅助方法返回的LinkedList对象
LinkedList<E> clone = superClone();
// 初始化(初始化零值)
clone.first = clone.last = null;
clone.size = 0;
clone.modCount = 0;
// 循环复制链表
for (Node<E> x = first; x != null; x = x.next)
clone.add(x.item);
return clone;
}
/**
* 转化为数组:(没有参数)
* 通过遍历LinkedList中的每个节点,把值添加到数组中。
*/
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;
}
/**
* 转化为数组
* 泛型方法
* 在方法内部,如果a的长度小于集合的大小的话,
* 通过反射创建一个和集合同样大小的数组,
* 接着把集合中的所有元素添加到数组中。
* 如果数组的元素的个数大于集合中元素的个数,则把a[size]设置
* 为空。
* 我估计代码设计者这样设计代码的目的是为了能够通过返回值观察到
* LinkedList集合中原来的元素有哪些。通过null把集合中的元素凸显出来。
* ArrayList中也有同样的考虑和设计。
*/
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;
if (a.length > size)
a[size] = null;
return a;
}
private static final long serialVersionUID = 876323262645176354L;
/**
* 将LinkedList写入到流中。(也就是把LinkedList状态保存到流中)(序列化)
*/
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
// Write out any hidden serialization magic
s.defaultWriteObject();
// Write out size
s.writeInt(size);
// Write out all elements in the proper order.
for (Node<E> x = first; x != null; x = x.next)
s.writeObject(x.item);
}
/**
* 从流中把LinkedList读取出来(读取流,拼装成LinkedList)(反序列化)
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in any hidden serialization magic
s.defaultReadObject();
// Read in size
int size = s.readInt();
// Read in all elements in the proper order.
for (int i = 0; i < size; i++)
linkLast((E)s.readObject());
}
}