目录
一.LinkedList简介:
前面说到ArrayList类的操作方式,底层是基于数组实现的,那么同样作为List接口实现类的LinkedList,则是通过"链表"的方式实现了List接口。
二.LinkedList源代码分析:
2.1 LinkedList的定义:
我们首先来看它的定义:
- 继承自AbstractSequentialList类: 强调基于迭代器的方式实现连续性,因为LinkedList基于链表实现,所以该类更使用于它。
- 实现了List接口: 具有有序,允许重复值的特点。
- 实现了Deque接口: 同时作为双向队列的实现。
- 实现了Cloneable接口: 可使用Clone()函数,能克隆。
- 实现了Serializable接口: 支持序列化,可通过序列化去传输。
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
//元素的个数,默认为0
transient int size = 0;
/**
* Pointer to first node.
*/
transient Node<E> first; //头结点
/**
* Pointer to last node.
*/
transient Node<E> last; //尾结点
}
2.2 构造方法:
//无参构造方法
public LinkedList() {
}
//构造一个包含指定集合元素的列表,按照他们迭代器返回的顺序排列
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
2.3 添加元素:
- addFirst(): 将元素添加至首部
- addLast(): 将元素添加至尾部
- add(): 默认将元素添加至尾部,操作成功返回true
- add(int index,E element): 将元素添加至指定位置
- addAll(Collection<? extends E> c): 将集合添加至尾部
- addAll(int index, Collection<? extends E> c): 在指定位置添加集合
// 添加到头位置
public void addFirst(E e) {
linkFirst(e);
}
// 添加到末尾
public void addLast(E e) {
linkLast(e);
}
// 添加元素(默认添加到尾部,操作成功返回true)
public boolean add(E e) {
linkLast(e);
return true;
}
// 在指定位置添加元素
public void add(int index, E element) {
checkPositionIndex(index); //检查索引是否合法
if (index == size)
linkLast(element); //如果索引值与元素个数相等,也就等同于在链表末尾添加结点
else
linkBefore(element, node(index)); //否则查找该索引对应的元素,将要添加的结点插入到该节点之前,使其成为该索引对应的元素
}
// 添加指定集合元素
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c); //等同于将集合添加至链表末尾
}
// 在指定位置添加一个集合
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index); //检查索引是否合法
Object[] a = c.toArray();
int numNew = a.length;
if (numNew == 0)
return false; //判断该集合是否为空,为空则返回false
Node<E> pred, succ;
if (index == size) {
succ = null;
pred = last; //如果要添加到末尾,则后继结点为null,前驱结点为当前尾结点
} else {
succ = node(index);
pred = succ.prev;
} //否则后继结点为索引对应的结点,前驱节点为node(index)的前驱结点
//遍历集合元素,依次添加进链表
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
Node<E> newNode = new Node<>(pred, e, null);
if (pred == null)
first = newNode; //如果前驱节点为null,那么让头结点指向新创建的第一个结点
else
pred.next = newNode; //如果不为头结点,则在pred之后添加进新结点
pred = newNode; //添加成功后,为下一次添加做准备,下一次添加时的前驱结点为刚刚的newNode
}
//如果后继结点为null,说明这是尾结点
if (succ == null) {
last = pred; //则newNode为尾结点(上面pred = newNode)
} else {
pred.next = succ;
succ.prev = pred;
} //链接newNode和node(index)
size += numNew; //更新size
modCount++; //结构性改变次数加1
return true; //操作成功共返回true
}
2.4 查找元素:
- getFirst(): 返回头结点
- getLast(): 返回尾结点
- get(int index): 查找指定索引的结点
//查找头结点
public E getFirst() {
final Node<E> f = first; //将头结点赋给变量f
if (f == null)
throw new NoSuchElementException(); //如果为null,抛出无此元素异常
return f.item; //返回头结点的内容
}
//查找尾结点
public E getLast() {
final Node<E> l = last; //将尾结点赋给变量l
if (l == null)
throw new NoSuchElementException(); //如果为null,抛出无此元素异常
return l.item; //返回尾结点的内容
}
//查找指定索引的结点
public E get(int index) {
checkElementIndex(index); //判断索引是否合法
return node(index).item; //返回指定索引的的结点的内容
}
2.5 删除元素:
- removeFirst(): 删除当前头结点
- removeLast(): 删除当前尾结点
- remove(Object o): 根据内容删除结点
- remove(int index): 根据索引删除结点
- clear(): 清空链表
- removeFirstOccurrence(Object o): 删除第一次出现指定内容的结点
- removeLastOccurrence(Object o): 删除最后一次出现指定内容的结点
//删除当前头结点
public E removeFirst() {
final Node<E> f = first; //保存头结点
if (f == null)
throw new NoSuchElementException(); //如果为null,则抛出异常
return unlinkFirst(f); //删除头结点
}
//删除当前尾结点
public E removeLast() {
final Node<E> l = last; //保存尾结点
if (l == null)
throw new NoSuchElementException(); //如果为null,则抛出异常
return unlinkLast(l); //删除尾结点
}
//根据内容删除
public boolean remove(Object o) {
if (o == null) { //如果内容为null
for (Node<E> x = first; x != null; x = x.next) { //依次遍历链表
if (x.item == null) { //如果当前结点的内容域为null
unlink(x); //取消链接当前结点(删除)
return true; //操作成功返回true
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
} //不为null,则遍历链表依次调用equals()方法检查内容
return false; //操作失败返回false
}
//根据索引删除
public E remove(int index) {
checkElementIndex(index); //检查索引是否合法
return unlink(node(index)); //先调用node()方法找到索引处的结点,再删除
}
//清空
public void clear() {
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++;
}
//删除第一次出现指定元素的结点
public boolean removeFirstOccurrence(Object o) {
return remove(o);
}
//删除最后一次出现指定元素的结点(从后往前遍历)
public boolean removeLastOccurrence(Object o) {
if (o == null) { //如果为null,则从后往前遍历,找到后删除并返回true
for (Node<E> x = last; x != null; x = x.prev) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else { //不为null则从后往前依次调用equals()方法检查,找到则删除并返回true
for (Node<E> x = last; x != null; x = x.prev) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false; //操作失败返回false
}
2.6 修改元素:
- set(int index,E element): 修改指定索引结点的并返回修改前的值
public E set(int index, E element) {
checkElementIndex(index); //检查索引是否合法
Node<E> x = node(index); //查找指定索引的结点赋给变量x
E oldVal = x.item; //保存修改前的值
x.item = element; //修改值
return oldVal; //返回修改前的值
}
2.7 使用到的核心方法:
node(): 根据索引返回对应的结点
//根据索引返回对应的结点
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) { //判断index的位置,查询一半长度,减少系统开销
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next; //遍历到index-1时,将x.next赋给x并返回
return x;
} else { //如果在后半部分,则从后往前遍历
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
linkFirst(): 使成为头结点
linkLast(): 使成为尾结点
linkBefore(E e, Node<E> succ): 使成为结点succ之前的结点(succ结点不为null)
private void linkFirst(E e) {
final Node<E> f = first;
final Node<E> newNode = new Node<>(null, e, f);
first = newNode;
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}
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++;
}
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
2.8 核心内部类Node:
在上面的代码中,我们频繁操作的每一个结点都是一个Node<E>类型,下面是该类的源代码:
- 该类为静态内部类,范围只能在此类中使用。
- 分为三个区域,分别是: item(数据域,用于存放该结点的数据),next(用于存放此结点的前驱结点,所以为Node类型),prev(用于存放此结点的后继结点,所以也为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;
}
}
2.9 队列操作:
前面说到LinkedList实现了Deque接口,那么就意味着它有着队列的特性以及相关操作:
下列的方法我们分为throw Exception和返回false或null两种方法:
- throw Exception:
- add(): 添加元素到队尾
- remove(): 拿到队首并删除
- element(): 拿到队首不删除
- 返回false或null:
- offer(): 添加元素到队尾
- poll(): 拿到队首并删除
- peek(): 拿到队首不删除
public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
public E element() {
return getFirst();
}
public E poll() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
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;
}
public E peekFirst() {
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 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是以双向链表为数据结构实现的List集合。
- 它没有扩容动作,每次都是添加新的结点在链表中并分配空间。
- 缺点: 由于它是基于链表的集合,所以在数据的遍历,数据的查询,数据的读取方面会消耗更多的性能。
- 优点: 添加,删除操作快捷。
- 适用场景: 适合数据频繁的添加删除操作,即写多读少。而ArrayList更适合读多写少的场景。
- 实现了Deque接口,同时具有队列的特性,可在队列场景中使用LinkedList。