深入了解LinkedList。
环境:
- eclipse 2019-03 (4.11.0)
- jdk-12.0.1
eclipse中查看源码的方法:按住Ctrl键,鼠标左键单击代码(更详细请百度)。
容器:在Java中,“集合”有另外的用途,所以ArrayList、HashMap等皆称为容器类,其创建的一个对象就是一个容器。
简介
LinkedList是一个基于双向链表的List,除了能作为List使用之外,还可以用作队列或者栈。
LinkedList源码分析
1、class声明
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{...}
源码分析:
LinkedList继承了抽象类AbstractSequentialList,AbstractSequentialList类声明
public abstract class AbstractSequentialList<E> extends AbstractList<E> {...}
LinkedList实现了接口List,目的为了Class类的getInterfaces这个方法可以直接返回List接口。
LinkedList实现了接口Deque,使LinkedList可以实现队列的相关功能,Deque类声明
public interface Deque<E> extends Queue<E> {...}
Queue源码
public interface Queue<E> extends Collection<E> { boolean add(E e);//向队列添加数据 boolean offer(E e);//向队列添加数据 E remove();//移除队首 E poll();//移除队首 E element();//获取队首元素 E peek();//获取队首元素 }
LinkedList实现了接口Cloneable(标识接口),该接口不包含任何方法,实现它仅仅是为了指示可以使用Object类中的clone()方法来进行克隆。
LinkedList实现了Serializable接口(标识接口),表示该类可以进行序列化。该接口表示所有的非transient和非static字段都可以被序列化。如果要实现transient字段与static字段的序列化保存,必须声明writeObject和readObject方法
2、字段
transient int size = 0;
transient Node<E> first;
transient Node<E> last;
源码分析:
size:记录元素个数
first:第一个元素节点(链表首节点)
last:最后一个元素节点(链表尾节点)
3、构造方法
public LinkedList() {
}
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
源码分析:因为LinkedList数据结构是双向链表,不需要初始化大小,所以构造方法只有两个。
LinkedList():无参构造器。
LinkedList(Collection<? extends E> c):根据已有的Collection容器对象创建新的LinkedList对象
addAll()的目的是将一个Collection对象中的所有元素添加到当前容器中,来看看它的实现
public boolean addAll(Collection<? extends E> c) { return addAll(size, c);//调用addAll(int index, Collection<? extends E> c) } public boolean addAll(int index, Collection<? extends E> c) {//在指定位置index及其之后插入Collection中的所有元素 checkPositionIndex(index);//若index<0或index>size,抛出异常 Object[] a = c.toArray();//先将Collection中的所有元素保存在一个数组中 int numNew = a.length; if (numNew == 0)//若数组长度为0,即c中没有元素,返回false return false; Node<E> pred, succ;//pred指向前一个元素,succ指向当前元素 if (index == size) {//确定当前元素succ的位置 succ = null; pred = last; } else { succ = node(index); pred = succ.prev; } for (Object o : a) {//往链表中循环添加数组a中的元素 @SuppressWarnings("unchecked") E e = (E) o; Node<E> newNode = new Node<>(pred, e, null);//利用构造器封装数据 if (pred == null)//若前一个元素为null,即index为0,数组中的第一个元素应该是链首 first = newNode; else pred.next = newNode; pred = newNode; } if (succ == null) {//设置最后一个添加的元素的前后指针 last = pred; } else { pred.next = succ; succ.prev = pred; } size += numNew; modCount++; return true; }
4、数据结构
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;
}
}
源码分析:LinkedList存储的数据结构为静态内部类Node,双向链表。
5、添加元素add(),addFirst(),addLast()
public boolean add(E e) {//在链表末尾添加元素,添加成功返回true
linkLast(e);
return true;
}
public void add(int index, E element) {//指定位置插入元素
checkPositionIndex(index);//若index<0或index>size,抛出异常IndexOutOfBoundsException(outOfBoundsMsg(index))
if (index == size)
linkLast(element);//若要插入的位置刚好是链表尾部,调用linkLast()
else
linkBefore(element, node(index));//否则调用linkBefore()
}
public void addFirst(E e) {//队列首部添加元素
linkFirst(e);
}
public void addLast(E e) {//队列尾部添加元素
linkLast(e);
}
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;
}
public void push(E e) {//压入元素
addFirst(e);
}
源码分析:
- add(E e),offer(E e),offerFirst(E e),offerLast(E e)添加完元素后,返回true
源码中用到的linkLast(e)
void linkLast(E e) {//向链表尾部添加元素值e final Node<E> l = last;//首先获得链表最后一个元素的引用 final Node<E> newNode = new Node<>(l, e, null);//利用数据结构的构造器将元素值e封装为一个元素节点 last = newNode;//修改last为链表最新的结尾元素 if (l == null) first = newNode;//若原来的链表是null的,则设置first为刚插入的元素newNode else l.next = newNode;//否则将刚封装好的节点添加到链表尾部 size++;//元素计数加一 modCount++;//结构性变化,计数加一 }
源码中用到的linkBefore(element, node(index))
void linkBefore(E e, Node<E> succ) {//在元素succ的前面添加元素 // assert succ != null; final Node<E> pred = succ.prev;//获得succ的前一个元素的引用 final Node<E> newNode = new Node<>(pred, e, succ);//利用构造器将e封装为一个元素节点 succ.prev = newNode;//修改succ的prev指向新的元素 if (pred == null) first = newNode;//若succ是链表头的元素,修改first指向新的元素 else pred.next = newNode;//否则修改前一个元素的next指向新元素 size++; modCount++; }
源码中用到的node(index)
Node<E> node(int index) {//根据index获取链表中的元素 // assert isElementIndex(index); if (index < (size >> 1)) {//若index小于元素个数的一半,则从链表首开始遍历元素 Node<E> x = first; for (int i = 0; i < index; i++) x = x.next; return x; } else {//若index大于元素个数的一半,则从链表尾开始遍历元素 Node<E> x = last; for (int i = size - 1; i > index; i--) x = x.prev; return x; } }
源码中用到的linkFirst(e)
private void linkFirst(E e) {//在链表首插入元素 final Node<E> f = first;//首先获取第一个元素 final Node<E> newNode = new Node<>(null, e, f);//利用构造器将元素值e封装为一个元素节点 first = newNode;//修改first指向新的节点 if (f == null) last = newNode;//若原本链表为null,设置last指向新的节点 else f.prev = newNode;//否则修改原来的第一个节点的prev指向新的节点 size++; modCount++; }
6、删除元素remove(),removeFirst(),removeLast(),poll(),pollFirst(),pollLast(),pop()
public E remove() {//移除链表第一个元素
return removeFirst();
}
public E remove(int index) {//移除指定位置index的元素
checkElementIndex(index);//若index<0或index>=size,抛出异常IndexOutOfBoundsException(outOfBoundsMsg(index))
return unlink(node(index));
}
public boolean remove(Object o) {//移除指定元素
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {//这个循环还是有点意思的
if (x.item == null) {
unlink(x);///调用unlink()删除节点
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
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 E poll() {//移除队列头,并返回被移除的元素的值
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
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);
}
public E pop() {//弹出栈,并返回被弹出的元素的值
return removeFirst();
}
源码分析:
- remove(Object o)返回boolean值
- remove(int index)若index不符合要求,抛出异常
- remove(),removeFirst(),removeLast(),pop()移除元素时,若指定的元素为null,抛出异常
- poll(),pollFirst(),pollLast()移除元素时候,若指定的元素为null,返回null
源码中用到的unlink(node(index))
E unlink(Node<E> x) {//移除节点x,并返回被移除节点x的item值 // assert x != null; final E element = x.item;//首先获得要删除节点x的item,next以及prev final Node<E> next = x.next; final Node<E> prev = x.prev; if (prev == null) {//若prev为null,表示节点x是链表的第一个节点,直接修改first first = next; } else {//否则设置节点x的上一个节点的next指向节点x的下一个节点,并将节点x的prev修改为null,方便GC prev.next = next; x.prev = null; } if (next == null) {//若next为null,表示节点x是链表的最后一个节点,直接修改last last = prev; } else {//否则设置节点x的下一个节点的prev指向节点x的上一个节点,并将节点x的next修改为null,方便GC next.prev = prev; x.next = null; } x.item = null;//最后将节点的item修改为null,方便GC size--; modCount++; return element; }
源码中用的的unlinkFirst(f)
private E unlinkFirst(Node<E> f) {//链表中移除首节点,返回被移除的节点值 // assert f == first && f != null; final E element = f.item;//首先获取首节点的item和next final Node<E> next = f.next; f.item = null;//将item与next置空 f.next = null; // help GC first = next;//修改first指向原首节点的下一个节点 if (next == null)//如果原首节点的下一个节点为null,表示链表中只有一个节点,修改last为null last = null; else next.prev = null;//否则修改下一个节点的prev为null size--; modCount++; return element; }
源码中用到的unlinkLast(l)
private E unlinkLast(Node<E> l) {//链表中移除尾节点 // assert l == last && l != null; final E element = l.item;//首先获取尾节点的item和prev final Node<E> prev = l.prev; l.item = null;//将尾节点的item和prev置空 l.prev = null; // help GC last = prev;//修改last为原尾节点的前一个节点 if (prev == null)//若前一个节点为null,表示链表中只有一个节点,将first修改为null first = null; else prev.next = null;//否则将前一节点的next修改为null size--; modCount++; return element; }
7、修改元素set()
public E set(int index, E element) {//将指定位置index的元素值修改为element,返回原数据
checkElementIndex(index);//若index<0或index>=size,抛出异常IndexOutOfBoundsException(outOfBoundsMsg(index))
Node<E> x = node(index);//调用node(index)获得指定位置index的节点元素
E oldVal = x.item;//先获取节点原来的item值
x.item = element;//修改节点值
return oldVal;//返回原值
}
8、获取元素get(),getFirst(),getLast(),peek(),peekFirst(),peekLast(),element()
public E get(int index) {//获取指定位置index的元素值
checkElementIndex(index);//若index<0或index>=size,抛出异常IndexOutOfBoundsException(outOfBoundsMsg(index))
return node(index).item;//先调用node(index)获得目标节点,再获得item
}
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 peek() {//获得队头元素的值
final Node<E> f = first;
return (f == null) ? null : f.item;
}
public E peekFirst() {//获得队头元素的值,和peek()完全一样
final Node<E> f = first;
return (f == null) ? null : f.item;
}
public E peekLast() {//获得队尾元素的值
final Node<E> l = last;
return (l == null) ? null : l.item;
}
public E element() {//获得链表的第一个节点的值
return getFirst();
}
源码分析:
- get(int index),peek(),peekFirst(),peekLast()获取元素时,若目标为空,返回null
- getFirst(),getLast(),element()获取元素时,若目标为空,抛出异常
9、遍历元素iterator()
public ListIterator<E> listIterator(int index) {//返回一个迭代器对象
checkPositionIndex(index);//若index<0或index>size,抛出异常IndexOutOfBoundsException(outOfBoundsMsg(index))
return new ListItr(index);
}
private class ListItr implements ListIterator<E> {
private Node<E> lastReturned;//最后一个被返回的元素
private Node<E> next;//下一个元素
private int nextIndex;//下一个元素的索引
private int expectedModCount = modCount;
ListItr(int index) {//ListItr的构造方法,传入的index决定接下来遍历元素的开始位置
// assert isPositionIndex(index);
next = (index == size) ? null : node(index);//修改next指向index位置的节点
nextIndex = index;//保存节点位置index
}
public boolean hasNext() {//判断是否还有下一个元素
return nextIndex < size;
}
public E next() {//获取下一个元素的值,在调用next()之前,需要调用hasNext()判断下一个元素是否存在
checkForComodification();//检查modCount是否改变,即容器是否发生了结构性变化
if (!hasNext())
throw new NoSuchElementException();
lastReturned = next;//lastReturned指向next指向的元素
next = next.next;//next指向下一个元素
nextIndex++;
return lastReturned.item;//返回元素值item
}
public boolean hasPrevious() {//判断是否还有前一个元素
return nextIndex > 0;
}
public E previous() {//获取前一个元素的值
checkForComodification();//检查modCount是否改变,即容器是否发生了结构性变化
if (!hasPrevious())
throw new NoSuchElementException();
lastReturned = next = (next == null) ? last : next.prev;
nextIndex--;
return lastReturned.item;
}
public int nextIndex() {
return nextIndex;
}
public int previousIndex() {
return nextIndex - 1;
}
public void remove() {
checkForComodification();
if (lastReturned == null)
throw new IllegalStateException();
Node<E> lastNext = lastReturned.next;
unlink(lastReturned);
if (next == lastReturned)
next = lastNext;
else
nextIndex--;
lastReturned = null;
expectedModCount++;
}
public void set(E e) {
if (lastReturned == null)
throw new IllegalStateException();
checkForComodification();
lastReturned.item = e;
}
public void add(E e) {
checkForComodification();
lastReturned = null;
if (next == null)
linkLast(e);
else
linkBefore(e, next);
nextIndex++;
expectedModCount++;
}
public void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (modCount == expectedModCount && nextIndex < size) {
action.accept(next.item);
lastReturned = next;
next = next.next;
nextIndex++;
}
checkForComodification();
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
源码分析:因为LinkedList是一个双向链表,所以它的迭代类与ArrayList有些不同。单独来看ListItr类,我们最常用的就是其中的hasNext(),next(),hasPrevious(),previous()四个方法
10、链表转数组toArray()
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;
}
实例
1、LinkedList作为栈的实现
public static void main(String[] args) {
LinkedList<String> stack=new LinkedList<String>();
stack.push("abc");//压栈
stack.peek();//获取最后一个压入栈的元素
stack.pop();//弹出最后一个压入栈的元素
}
2、LinkedList作为单向队列的实现
public static void main(String[] args) {
Queue<String> queue=new LinkedList<String>();
queue.offer("abc");//队尾添加元素
queue.peek();//队头获取元素
queue.poll();//队头删除元素
}
3、LinkedList作为双向队列的实现
public static void main(String[] args) {
Deque<String> deque=new LinkedList<String>();
deque.addFirst("abc");//队头添加元素
deque.addLast("bcd");//队尾添加元素
deque.getFirst();//队头获取元素
deque.getLast();//队尾获取元素
deque.removeFirst();//队头删除元素
deque.removeLast();//队尾删除元素
}
4、如果仅仅只是希望将LinkedList作为一个链表来使用
public static void main(String[] args) {
LinkedList<String> list=new LinkedList<String>();
list.addFirst("abc");//链首添加
list.addLast("bcd");//链尾添加
list.add(0, "aaa");//指定插入
list.getFirst();//链首获取
list.getLast();//链尾获取
list.get(0);//指定获取
list.removeFirst();//链首删除
list.removeLast();//链尾删除
list.remove(0);//指定删除
list.remove("bcd");//指定删除
list.set(0, "bbb");//修改
}
总结
- LinkedList是一个基于双向链表的List,且还是一个双端队列,具有队列,栈的特性
- LinkedList可以根据“索引”来 添加/获取/删除/修改 元素,但这种做法效率比较低