LinkedList概述
LinkedList是一个双向链表,链表数据结构的特点是每个元素分配的空间不必连续、插入和删除元素时速度非常快、但访问元素的速度较慢。本源码版本:1.8.0_172
LinkedList结构
- Deque接口:双端队列(double ended queue)接口,支持从两个端点检索和插入数据,即可支持LIFO也可支持FIFO
- Serializable接口:标记接口,java提供的序列化接口,为对象提供标准的序列化和反序列化操作
- Cloneable接口:标记接口,只有实现了这个接口,然后才能调用类的clone()方法,否则抛出CloneNotSupportedException异常
LinkedList的源码分析
LinkedList属性
/**
* 链表长度
*/
transient int size = 0;
/**
* 链表的头节点
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node first;
/**
* 链表的尾部节点
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node last;
LinkedList的节点Node介绍
Node节点主要包括:
- item:当前节点元素信息
- next:后继节点,即下一个节点Node的信息,节点是尾部节点时,next == null
- prev:前驱节点,即上一个节点Node的信息,节点是头节点时,prev == null
LinkedList的节点Node代码如下:
private static class Node {
E item;
Node next;
Node prev;
Node(Node prev, E element, Node next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
LinkedList的主要实现方法
LinkedList的所有操作主要是围绕如下七个方法实现的:
linkFirst(链表头部插入节点)
linkLast(链表尾部插入)
linkBefore(某一个节点前插入)
unlinkFirst(删除链表头部节点),unlinkLast(删除链表尾部节点),unlink(删除指定节点),
node(查找指定索引节点),掌握上面几个方法的实现,基本就掌握了LinkedList。
/**
* 链表头部插入节点
*
* 1. 将头节点first缓存到临时节点f
* 2. 创建需要插入的元素的节点信息,同时将新增节点的后驱指向原头节点
* 3. 将新增节点设置为头节点
* 4. 判断原头节点是否为空
* 如果为空,链表为空,将新增节点设置为尾部节点
* 如果不为空,则将原头节点的前驱指向新增节点
* 5. 链表长度加1
* 6. 修改记录数加1
*/
private void linkFirst(E e) {
final Node f = first;
final Node newNode = new Node<>(null, e, f);
first = newNode;
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}
/**
* 链表尾部插入节点
*
* 1. 将尾部节点last缓存到临时节点f
* 2. 创建需要插入的元素的节点信息,同时将新增节点的前驱指向原尾部节点
* 3. 将新增节点设置为尾部节点
* 4. 判断原尾部节点是否为空
* 如果为空,链表为空,需要将头节点设置为新增节点
* 如果不为空,则将原尾部节点的后继指向新增节点
* 5. 链表长度加1
* 6. 修改记录数加1
*/
void linkLast(E e) {
final Node l = last;
final Node newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
/**
* 在指定节点前添加节点
*
* 1. 缓存指定节点succ的前驱节点pred
* 2. 创建需要插入元素的节点信息newNode,同时将前驱设置为指定节点的前驱pred, 后继设置为指定节点succ
* 3. 将指定节点的前驱设置为新增节点newNode
* 4. 判断指定节点的前驱节点pred是否为空
* 如果为空,说明指定节点是头节点,也是尾部节点,需要将新增节点设置为尾部节点
* 如果不为空,需要将指定节点的前驱节点的pred的后继节点执行新增节点newNode
* 5. 链表长度加1
* 6. 修改记录数加1
*/
void linkBefore(E e, Node succ) {
// assert succ != null;
final Node pred = succ.prev;
final Node newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
/**
* 移除首节点(首节点不为空)
*
* 1. 缓存首节点f元素信息element
* 2. 缓存首节点f的后继节点next
* 3. 将首节点的元素信息设置为空(为了gc释放资源)
* 4. 将首节点的后继节点设置为空(为了gc释放资源)
* 5. 将首节点重新设置为原首节点的后继节点next
* 6. 判断后继节点next是否为空,
* 如果为空,则原首节点无后继节点,将尾部节点设置为null
* 如果不为空,则将后继节点next(也就是现在的首节点)的前驱节点设置为null
* 7. 链表长度减一
* 8. 修改记录数加一
* 9. 返回移除的首节点元素
*/
private E unlinkFirst(Node f) {
// assert f == first && f != null;
final E element = f.item;
final Node next = f.next;
f.item = null;
f.next = null; // help GC
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
/**
* 移除尾部节点(尾部节点不为空)
*
* 1. 缓存尾部节点f元素信息element
* 2. 缓存尾部节点f的前驱节点prev
* 3. 将尾部节点的元素信息设置为空(为了gc释放资源)
* 4. 将尾部节点的前驱节点设置为空(为了gc释放资源)
* 5. 将尾部节点重新设置为原尾部节点的前驱节点prev
* 6. 判断前驱节点prev是否为空,
* 如果为空,则原尾部节点无前驱节点,将首节点设置为null
* 如果不为空,则将前驱节点prev(也就是现在的尾部节点)的后继节点设置为null
* 7. 链表长度减一
* 8. 修改记录数加一
* 9. 返回移除的首节点元素
*/
private E unlinkLast(Node l) {
// assert l == last && l != null;
final E element = l.item;
final Node prev = l.prev;
l.item = null;
l.prev = null; // help GC
last = prev;
if (prev == null)
first = null;
else
prev.next = null;
size--;
modCount++;
return element;
}
/**
* 移除指定节点(节点不允许为空)
*
* 1. 缓存指定节点x的元素信息element
* 2. 缓存指定节点的后继节点next
* 3. 缓存指定节点的前驱节点prev
* 4. 判断前驱节点prev是否为空
* 如果为空,说明指定节点为首节点,重新将首节点设置为指定节点的后继节点next
* 如果不为空,将前驱节点prev的后继节点重新设置为指定节点的后继节点next,指定节点的前驱节点prev设置为空(释放引用)
* 5. 判断后继节点next是否为空
* 如果为空,说明指定节点为尾部节点,重新将尾部节点设置为指定节点的前驱节点prev
* 如果不为空,将后继节点next的前驱节点重新设置为指定节点的前驱节点prev,指定节点的后继节点next设置为空(释放引用)
* 6. 将指定节点的元素设置为空
* 7. 链表长度减一
* 8. 修改记录数加一
* 9. 返回移除的首节点元素
*/
E unlink(Node x) {
// assert x != null;
final E element = x.item;
final Node next = x.next;
final Node prev = x.prev;
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
/**
* 通过索引index获取链表中节点信息
*
* 思考:为什么需要判断 index < (size >> 1)呢?
* 答:因为LinkedList是一个双向链表结构,可以通过从头节点向后遍历到尾部节点
* 也可以从最后节点遍历到头部节点, index < (size >> 1) 实际的作用是判断
* 需要从头节点开始遍历还是需要从尾部节点开始遍历,size >> 1,相当于size除以2,
* index < (size >> 1) 则离头部节点更近,从头部节点遍历更快获取索引index位置节点信息,
* 否则从尾部节点遍历更快获取到索引index位置的节点信息
*
*/
Node node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {
Node x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
LinkedList构造器
LinkedList主要有两个构造器,一个是空构造器,一个是将集合中所有元素加入到LinkedList里面,具体实现将在LinkedList添加元素中讲解
/**
* Constructs an empty list.
*/
public LinkedList() {
}
/**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
*
* @param c the collection whose elements are to be placed into this list
* @throws NullPointerException if the specified collection is null
*/
public LinkedList(Collection extends E> c) {
this();
addAll(c);
}
LinkedList新增相关详解
/**
* LinkedList的新增方法,实际上调用的是linkLast方法
*
*/
public boolean add(E e) {
linkLast(e);
return true;
}
/**
* 在链表尾部添加元素e,实际调用add.
*
* @param e the element to add
* @return {@code true} (as specified by {@link Queue#offer})
* @since 1.5
*/
public boolean offer(E e) {
return add(e);
}
/**
* 在指定索引位置插入元素element
*
* 1、校验索引位置是否越界 index > size 则抛出IndexOutOfBoundsException异常
* 2、判断索引是否等于LinkedList长度
* 等于则表示元素element需要插入到LinkedList的尾部,直接调用linkedLast方法
* 不等于,则通过node(index)方法找到索引index的节点信息,然后通过linkBefore方法
* 将元素element插入到当前索引index节点之前
* @param index 指定索引位置
* @param element 需要插入的元素
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
/**
* 将集合c中所有元素添加到LinkedList尾部
*
* @param c 集合
* @return {@code true} if this list changed as a result of the call
* @throws NullPointerException if the specified collection is null
*/
public boolean addAll(Collection extends E> c) {
return addAll(size, c);
}
/**
* 将集合c中所有元素添加到LinkedList指定索引index位置前,如果index == size,则添加到尾部
*
* @param index 索引位置
* @param c 指定集合
* @return {@code true} if this list changed as a result of the call
* @throws IndexOutOfBoundsException {@inheritDoc}
* @throws NullPointerException if the specified collection is null
*/
public boolean addAll(int index, Collection extends E> c) {
//校验索引位置是否越界 index > size 则抛出IndexOutOfBoundsException异常
checkPositionIndex(index);
//将集合转换成数组
Object[] a = c.toArray();
//获取数组长度,用于判断数组元素个数
int numNew = a.length;
//如果数组长度等于0,返回false,没有元素添加
if (numNew == 0)
return false;
//succ-索引index位置的节点信息
//pred-索引index位置的前驱节点信息
Node pred, succ;
//判断是否添加到链表尾部,即 index == size
if (index == size) { //添加到尾部
//当前节点信息为空
succ = null;
//前驱节点为最后尾部节点last
pred = last;
} else { //添加到索引index位置
succ = node(index);
pred = succ.prev;
}
//循环遍历数组a,通过Node构造器,建立链表前驱后继关系
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
Node newNode = new Node<>(pred, e, null);
//如果前驱节点为空,说明没有前驱节点,需要将新节点设置为头部节点
if (pred == null)
first = newNode;
else
pred.next = newNode;
//更新前驱节点为新节点
pred = newNode;
}
//对数组a中最后一个元素节点pred建立后继管理
//如果索引index节点succ节点为空,需要将数组a最后一个节点pred设置为尾部节点
if (succ == null) {
last = pred;
} else {
//将数组a最后一个节点pred后继执行索引index位置的节点
pred.next = succ;
//加索引index位置的节点前驱节点指向数组a最后一个节点pred
succ.prev = pred;
}
//修改LinkedList长度为原来长度size + 数组长度 numNew
size += numNew;
modCount++;
return true;
}
LinkedList移除相关操作
/**
* 移除指定索引位置的节点
*
* @param index the index of the element to be removed
* @return the element previously at the specified position
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E remove(int index) {
//校验索引位置是否越界 index > size 则抛出IndexOutOfBoundsException异常
checkElementIndex(index);
//先通过node方法查找节点信息,然后通过unlink方法移除节点信息
return unlink(node(index));
}
/**
* 移除第一个匹配元素o的节点信息
* @param o element to be removed from this list, if present
* @return {@code true} if this list contained the specified element
*/
public boolean remove(Object o) {
//判断元素o是否为空
if (o == null) {
//如果元素为空,从头节点开始遍历节点
for (Node x = first; x != null; x = x.next) {
//获取节点元素信息判断是否等于null
if (x.item == null) {
//移除节点
unlink(x);
//返回链表改动标识
return true;
}
}
} else { //元素不为空
//从头节点开始遍历节点
for (Node x = first; x != null; x = x.next) {
//获取节点元素信息判断是否等于元素o
if (o.equals(x.item)) {
//移除节点
unlink(x);
//返回链表改动标识
return true;
}
}
}
//返回链表没有改动标识
return false;
}
LinkedList修改相关操作
/**
* 将索引index位置元素换成element
*
* @param index index of the element to replace
* @param element element to be stored at the specified position
* @return the element previously at the specified position
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E set(int index, E element) {
//校验索引位置是否越界 index > size 则抛出IndexOutOfBoundsException异常
checkElementIndex(index);
//通过node方法查询节点信息
Node x = node(index);
//换成节点元素信息
E oldVal = x.item;
//将节点元素信息换成element
x.item = element;
//返回被替换元素信息
return oldVal;
}
/**
* 获取第一个匹配元素o的节点索引,没有匹配返回-1
* 基本思想和上续remove一样,不重复了
* @param o element to search for
* @return the index of the first occurrence of the specified element in
* this list, or -1 if this list does not contain the element
*/
public int indexOf(Object o) {
int index = 0;
if (o == null) {
for (Node x = first; x != null; x = x.next) {
if (x.item == null)
return index;
index++;
}
} else {
for (Node x = first; x != null; x = x.next) {
if (o.equals(x.item))
return index;
index++;
}
}
return -1;
}
/**
* 获取最后一个匹配元素o的节点索引,没有匹配返回-1
* 基本思想和上续remove一样,只不过遍历是从尾部向头部遍历,不重复了
*
* @param o element to search for
* @return the index of the last occurrence of the specified element in
* this list, or -1 if this list does not contain the element
*/
public int lastIndexOf(Object o) {
int index = size;
if (o == null) {
for (Node x = last; x != null; x = x.prev) {
index--;
if (x.item == null)
return index;
}
} else {
for (Node x = last; x != null; x = x.prev) {
index--;
if (o.equals(x.item))
return index;
}
}
return -1;
}
LinkedList对双向队列的支持
/**
* 在链表头部添加元素e,实际调用linkFirst.
*
* @param e the element to add
*/
public void addFirst(E e) {
linkFirst(e);
}
/**
* 在链表头部添加元素e,实际调用addFirst.
*
* 与addFirst不同是返回true
*
* @param e the element to insert
* @return {@code true} (as specified by {@link Deque#offerFirst})
* @since 1.6
*/
public boolean offerFirst(E e) {
addFirst(e);
return true;
}
/**
* 在链表尾部添加元素e,实际调用linkLast.
*
* @param e the element to add
*/
public void addLast(E e) {
linkLast(e);
}
/**
* 在链表尾部添加元素e,实际调用addLast.
*
* 与addLast不同是返回true
* @param e the element to insert
* @return {@code true} (as specified by {@link Deque#offerLast})
* @since 1.6
*/
public boolean offerLast(E e) {
addLast(e);
return true;
}
/**
* 如果头节点不存在,返回null,
* 思考:比较removeFirst()不同
* 否则移除链表头节点,并返回头节点元素信息,
* 核心实现unlinkFirst
*/
public E pollFirst() {
final Node f = first;
return (f == null) ? null : unlinkFirst(f);
}
/**
* 如果尾部节点不存在,返回null,
* 思考:比较removeLast()不同
* 否则移除尾部节点,并返回节点元素信息
* 核心实现unlinkFirst
*/
public E pollLast() {
final Node l = last;
return (l == null) ? null : unlinkLast(l);
}
/**
* 如果头节点不存在,抛出异常NoSuchElementException,
* 思考:比较pollFirst()不同
* 否则移除链表头节点,并返回头节点元素信息,
* 核心实现unlinkFirst
*/
public E removeFirst() {
final Node f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
/**
* 如果尾部节点不存在,抛出异常NoSuchElementException,
* 思考:比较pollLast()不同
* 否则移除尾部节点,并返回节点元素信息
* 核心实现unlinkFirst
*/
public E removeLast() {
final Node l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
/**
* 获取链表头节点信息
* 如果链表头节点不存在,返回null
* 否则,返回头节点元素信息
* 思考:peekFirst和getFirst的不同点
*/
public E peekFirst() {
final Node f = first;
return (f == null) ? null : f.item;
}
/**
* 获取链表尾部节点信息
* 如果链表尾部节点不存在,返回null
* 否则,返回头节点元素信息
* 思考:peekLast和getLast的不同点
*/
public E peekLast() {
final Node l = last;
return (l == null) ? null : l.item;
}
/**
* 获取链表头节点信息
* 如果链表头节点不存在,抛出异常NoSuchElementException
* 否则,返回头节点元素信息
* 思考:peekFirst和getFirst的不同点
*/
public E getFirst() {
final Node f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
/**
* 获取链表尾部节点信息
* 如果链表尾部节点不存在,抛出异常NoSuchElementException
* 否则,返回头节点元素信息
* 思考:peekLast和getLast的不同点
*/
public E getLast() {
final Node l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}
/**
* 移除第一个匹配指定元素o的节点
* 如果没有元素匹配,返回false
*/
public boolean removeFirstOccurrence(Object o) {
return remove(o);
}
/**
* 移除最后一个匹配指定元素o的节点
*
* 思考:第一个和最后一个匹配指定元素o的算法不同点
* 答:移除第一个匹配是从链表头节点开始遍历匹配的第一个节点
* 移除最后一个匹配是从链表尾部节点开始遍历匹配的第一个节点
*
* 如果没有元素匹配,返回false
*/
public boolean removeLastOccurrence(Object o) {
if (o == null) {
for (Node x = last; x != null; x = x.prev) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node x = last; x != null; x = x.prev) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
LinkedList对FIFO (First-In-First-Out)的支持
FIFO先入先出队列
- add(e),addLast(e),offer(e), offerLast(e)
- remove(), removeFirst(),poll(), pollFirst()
- element(), getFirst(),peek(), peekFirst()
public boolean add(E e) {
linkLast(e);
return true;
}
public boolean offer(E e) {
return add(e);
}
public E remove() {
return removeFirst();
}
public E poll() {
final Node f = first;
return (f == null) ? null : unlinkFirst(f);
}
public E element() {
return getFirst();
}
public E peek() {
final Node f = first;
return (f == null) ? null : f.item;
}
LinkedList对LIFO (Last-In-First-Out)的支持
LIFO后入先出栈操作
- push(e),addFirst(e)
- pop(),removeFirst()
- peek(),peekFirst()
public void push(E e) {
addFirst(e);
}
public E pop() {
return removeFirst();
}
感谢大家的支持!!!