一,Deque 双向队列接口
1.1 Deque 介绍
单向队列接口Queue 讲解完之后,Java里面在JDK1.6 也提供双向队列接口;从下面的代码继承结构可以看出Deque 双向队列也是单向队列接口的一种扩展;
public interface Deque<E> extends Queue<E> {/* 忽略代码*/}
Deque 双向队列接口设计提供了先进先出(FIFO)和先进后出(LIFO)两种队列的实现;对应的方法别分是从头部添加和从尾部添加。
Deque 双向队列接口并不支持数组处理;
Deque 双向队列接口和Queue接口一样。为插入,删除,检索提供两种处理方式;这两种处理方法分别如下:
- 【Throws exception】操作失败抛出异常
- 【Special value】操作失败返回特殊值(boolean/null)
从官网JDK17也可以看出,绿色框框里面是从头部出发处理相关业务(从头部添加- addFirst/offerFirst,从头部删除-removeFirst/pollFirst,从头部开始检索-getFirst/peekFirst);红色框框里面是从尾部出发处理相关业务(从尾部添加-addLast/offerLast,从尾部删除-removeLast/pollLast,从尾部开始检索-getLast/peekLast)。
Throws exception一列的处理就是属于操作失败抛异常的;Special value 一列的处理就是属于操作失败返回特殊值(boolean/null);
总结得知,Deque队列提供从头部和尾部两个方法的相关业务方法,如插入,删除,检索(查看);并且每个插入,删除,检索都支持两种处理方式,一种是操作失败抛异常,一种是操作失败返回特殊值。
1.2 Deque 接口源码分析
Deque 双向队列的接口主要是做了三件事情;第一件事就是为双向队列定义业务标准接口;第二件事情就是对Queue父接口的方法进行继承;第三件事件就是对Collection 集合相关业务进行继承;
package java.util;
/**
* @author Doug Lea
* @author Josh Bloch
* @since 1.6
* @param <E> the type of elements held in this deque
*/
public interface Deque<E> extends Queue<E> {
/************************ 【第一件事情】双向队列基本业务接口 *****************************/
/**
* 头部添加元素;操作失败抛出异常
* @param e 要添加的元素
* @throws IllegalStateException 如果由于容量限制,此时无法添加该元素
* @throws ClassCastException 元素类型不一致,抛出类型转换异常
* @throws NullPointerException 不允许添加null元素,否则抛空指针异常
* @throws IllegalArgumentException 传入与队列无关的非法元素则抛出 非法参数异常
*/
void addFirst(E e);
/**
* 尾部添加元素;操作失败抛出异常
* @param e the element to add
* @throws IllegalStateException 如果由于容量限制,此时无法添加该元素
* @throws ClassCastException 元素类型不一致,抛出类型转换异常
* @throws NullPointerException 不允许添加null元素,否则抛空指针异常
* @throws IllegalArgumentException 传入与队列无关的非法元素则抛出 非法参数异常
*/
void addLast(E e);
/**
* 头部添加元素;操作失败返回特殊值
* @param 要添加的元素
* @return true - 添加成功 | false - 添加失败
* @throws ClassCastException 元素类型不一致,抛出类型转换异常
* @throws NullPointerException 不允许添加null元素,否则抛空指针异常
* @throws IllegalArgumentException 传入与队列无关的非法元素则抛出 非法参数异常
*/
boolean offerFirst(E e);
/**
* 尾部添加元素;操作失败返回特殊值
* @param 要添加的元素
* @return true - 添加成功 | false - 添加失败
* @throws ClassCastException 元素类型不一致,抛出类型转换异常
* @throws NullPointerException 不允许添加null元素,否则抛空指针异常
* @throws IllegalArgumentException 传入与队列无关的非法元素则抛出 非法参数异常
*/
boolean offerLast(E e);
/**
* 头部删除元素;操作失败抛异常;
* 检索并从头部删除一个元素;如果队列为空抛NoSuchElementException
* @return 返回队列头部元素
* @throws NoSuchElementException 队列为空,抛NoSuchElementException异常
*/
E removeFirst();
/**
* 尾部删除元素;操作失败抛异常;
* 检索并从尾部删除一个元素;如果队列为空抛NoSuchElementException
* @return 返回队列尾部元素
* @throws NoSuchElementException 队列为空,抛NoSuchElementException异常
*/
E removeLast();
/** 检索并删除队列头部元素,如果队列为空则返回null */
E pollFirst();
/** 检索并删除队列尾部元素,如果队列为空则返回null */
E pollLast();
/**
* 查看头部元素
* @return 返回头部元素
* @throws NoSuchElementException 队列为空,抛NoSuchElementException异常
*/
E getFirst();
/**
* 查看尾部元素
* @return 返回尾部元素
* @throws NoSuchElementException 队列为空,抛NoSuchElementException异常
*/
E getLast();
/**
* 检索(查看)头部元素
* @return 返回头部元素
* @throws NoSuchElementException 队列为空,抛NoSuchElementException异常
*/
E peekFirst();
/**
* 检索(查看)尾部元素
* @return 返回尾部元素
* @throws NoSuchElementException 队列为空,抛NoSuchElementException异常
*/
E peekLast();
/**
* 删除队列中第一个指定的元素
* @param o 指定要删除的元素
* @return true - 删除成功 | false - 删除失败
* @throws ClassCastException 类转换异常
* @throws NullPointerException 空指针异常
*/
boolean removeFirstOccurrence(Object o);
/**
* 删除队列中最后一个指定的元素
* @param o 指定要删除的元素
* @return true - 删除成功 | false - 删除失败
* @throws ClassCastException 类转换异常
* @throws NullPointerException 空指针异常
*/
boolean removeLastOccurrence(Object o);
/****************************【第二件事情】 Queue接口方法的扩展 **************************/
/**
* 添加元素;操作失败抛出异常;该方法等同于addLast()
* @param e the element to add
* @throws IllegalStateException 如果由于容量限制,此时无法添加该元素
* @throws ClassCastException 元素类型不一致,抛出类型转换异常
* @throws NullPointerException 不允许添加null元素,否则抛空指针异常
* @throws IllegalArgumentException 传入与队列无关的非法元素则抛出 非法参数异常
*/
boolean add(E e);
/**
* 添加元素;操作失败返回特殊值;该方法等同于addOffer()
* @param 要添加的元素
* @return true - 添加成功 | false - 添加失败
* @throws ClassCastException 元素类型不一致,抛出类型转换异常
* @throws NullPointerException 不允许添加null元素,否则抛空指针异常
* @throws IllegalArgumentException 传入与队列无关的非法元素则抛出 非法参数异常
*/
boolean offer(E e);
/**
* 删除元素: 操作失败抛异常;该方法等于于removeFirst()
* 检索并从头部删除一个元素;如果队列为空抛NoSuchElementException
* @return 返回队列头部元素
* @throws NoSuchElementException 队列为空,抛NoSuchElementException异常
*/
E remove();
/** 检索并删除队列头部元素,如果队列为空则返回null; 该方法等于于pollFirst() */
E poll();
/**
* 查看头部元素; 该方法等同于 getFirst();
* @return 返回头部元素
* @throws NoSuchElementException 队列为空,抛NoSuchElementException异常
*/
E element();
/**
* 检索(查看)头部元素; 该方法等同于 peekFirst();
* @return 返回头部元素
* @throws NoSuchElementException 队列为空,抛NoSuchElementException异常
*/
E peek();
/**
* 添加元素集;操作失败抛出异常
* @param e the element to add
* @throws IllegalStateException 如果由于容量限制,此时无法添加该元素
* @throws ClassCastException 元素类型不一致,抛出类型转换异常
* @throws NullPointerException 不允许添加null元素,否则抛空指针异常
* @throws IllegalArgumentException 传入与队列无关的非法元素则抛出 非法参数异常;
*/
boolean addAll(Collection<? extends E> c);
/********************** stack 栈队列方法 LIFO 后进先出 ***************************/
/**
* 入栈:将元素添加到队列中,操作失败抛出异常;该方法等同于addFirst()
* @param e 要添加的元素
* @throws IllegalStateException 如果由于容量限制,此时无法添加该元素
* @throws ClassCastException 元素类型不一致,抛出类型转换异常
* @throws NullPointerException 不允许添加null元素,否则抛空指针异常
* @throws IllegalArgumentException 传入与队列无关的非法元素则抛出 非法参数异常
*/
void push(E e);
/**
* 出栈:将头部元素进行出栈处理,并删除该元素;该方法等同于removeFirst()
* 检索并从头部删除一个元素;如果队列为空抛NoSuchElementException
* @return 返回队列头部元素
* @throws NoSuchElementException 队列为空,抛NoSuchElementException异常
*/
E pop();
/********************** 【第三件事情】Collection 相关业务**************************/
/**
* 删除队列中指定的元素;该方法等同于removeFirstOccurrence()
* @param o 指定要删除的元素
* @return true - 删除成功 | false - 删除失败
* @throws ClassCastException 类转换异常
* @throws NullPointerException 空指针异常
*/
boolean remove(Object o);
/**
* 是否包含指定元素
* @param o 指定队列是否包含该元素
* @return
* @throws ClassCastException
* @throws NullPointerException
*/
boolean contains(Object o);
/** 队列大小 */
int size();
/** 获取队列地迭代器 */
Iterator<E> iterator();
/** 获取倒叙迭代器 */
Iterator<E> descendingIterator();
}
二,ArrayDeque
ArrayDeque 从名称上来看就是一个通过数组来实现的双向队列;
2.1 ArrayDeque 继承结构
从继承结构来讲,除了实现Deque 接口,还实现了Cloneable 接口和Serializable接口,也就意味着该类具有克隆能力和序列化能力;由于还继承了AbstractCollection 抽象类也就意味着拥有集合的基本业务;
package java.util;
public class ArrayDeque<E> extends AbstractCollection<E>
implements Deque<E>, Cloneable, Serializable
{/* 忽略代码 */}
2.2 ArrayDeque 属性
public class ArrayDeque<E> extends AbstractCollection<E>
implements Deque<E>, Cloneable, Serializable{
/** 双向队列 */
transient Object[] elements;
/** 头部节点索引 */
transient int head;
/** 尾部节点索引 */
transient int tail;
/** ArrayDeque 最大容量,超出抛OutOfMemoryError 异常 */
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
}
2.3 ArrayDeque 构造器
【无参构造器】默认大小为16;
public ArrayDeque() {
elements = new Object[16 + 1];
}
为什么不是是17,明明还+1了?在设计双向队列时,通常会考虑到实现细节和性能问题。像elements = new Object[16 + 1]; 再加1的作用如下:
循环队列实现的便捷性:再环形队列中需要区分队列已满和队列空的状态。如果数组的长度刚好时容量所需要的长度。因为队列满的时候,队列的头和尾部指针会相等,这与队列空时的情况时一样。因此多加一个元素作为标记,通过它可以方便的判断出队列是否已满和队列是否为空的条件实现细节更加简单。并非不加1无法实现,而是加1实现更加简单;
/* 队列是否为空 */
public boolean isEmpty() { return head == tail; }
【构造方法】创建指定元素数量的双向队列
/**
* 元素数量
*/
public ArrayDeque(int numElements) {
elements =
new Object[(numElements < 1) ? 1 :
(numElements == Integer.MAX_VALUE) ? Integer.MAX_VALUE :
numElements + 1];
}
【构造方法】创建指定元素集的双向队列
/**
* 将指定元素集的元素创建一个双向队列
*/
public ArrayDeque(Collection<? extends E> c) {
this(c.size());
copyElements(c);
}
2.4 ArrayDeque 头部相关业务
2.4.1 头部新增业务
2.4.1.1 第一种新增:操作失败抛异常
/**
* 添加指定元素
* @param e 要添加的元素
*/
public void addFirst(E e) {
if (e == null) throw new NullPointerException();
final Object[] es = elements;
/** 使用 dec 方法更新头指针 head,并将元素 e 插入到更新后的 head 位置 */
es[head = dec(head, es.length)] = e;
if (head == tail) // 队列为空,进行扩容
grow(1);
}
2.4.1.2 第二种新增:操作失败返回特殊值(boolean/null)
/**
* 新增元素
* @param e 要新增的元素
*/
public boolean offerFirst(E e) {
addFirst(e);
return true;
}
2.4.2 头部删除业务
2.4.2.1 第一种删除:操作失败抛异常
/** 删除头部元素,元素不存在,抛NoSuchElementException异常 */
public E removeFirst() {
E e = pollFirst();
if (e == null)
throw new NoSuchElementException();
return e;
}
2.4.2.2 第二种删除:操作失败返回特殊值(boolean/null)
/** 删除头部元素,并返回要删除的头部元素,不存在返回为null */
public E pollFirst() {
final Object[] es;
final int h;
/*es = elements, h = head获取当前数组和头指针,并通过elementAt获取当前头部元素索引*/
E e = elementAt(es = elements, h = head);
if (e != null) {
es[h] = null; // 将数组 es 的位置 h 处的元素置为 null,表示该头部位置已经被移除
head = inc(h, es.length); // 调用 inc 方法更新头指针 head。
}
return e;
}
2.4.3 头部检索业务
2.4.3.1 第一种检索:操作失败抛异常
/** 获取头部元素,没有获取到抛NoSuchElementException异常 */
public E getFirst() {
E e = elementAt(elements, head); // 获取头部元素
if (e == null)
throw new NoSuchElementException();
return e;
}
2.4.3.2 第二种检索:操作失败返回特殊值(boolean/null)
/** 获取头部元素,获取失败返回为null */
public E peekFirst() {
return elementAt(elements, head);
}
2.5 ArrayDeque 尾部相关业务
2.5.1 尾部新增业务
2.5.1.1 第一种新增:操作失败抛异常
/**
* 从尾部新增元素
* @param e 要新增的元素
*/
public void addLast(E e) {
// ArrayDeque 不允许存储null;否则抛空指针异常
if (e == null) throw new NullPointerException();
final Object[] es = elements;
es[tail] = e; // 更新尾部元素
/*
* tail = inc(tail, es.length):调用 inc 方法更新尾指针 tail。
* inc 方法用于处理循环队列中的指针递增操作,确保指针在数组范围内循环。
* if (head == tail) :检查头指针 head 是否等于更新后的尾指针 tail。如果相等,说明队列已满,需要扩容。
*/
if (head == (tail = inc(tail, es.length))) grow(1);
}
2.5.1.2 第二种新增:操作失败返回特殊值
该接口的定义明明是操作失败返回特殊值,为什么在这里是直接调用addLast();该方法操作失败可以会抛异常的;之所以这样写,是因为这是一个无界的队列;那些写jdk的人认为在无界的队列当中,添加是不存在失败的,除非是添加null这样不允许添加的元素;官网中也可以找到这个说法依据;
/** 新增元素,新增成功返回true; 否则抛异常; */
public boolean offerLast(E e) {
addLast(e);
return true;
}
2.5.2 尾部删除业务
2.5.2.1 第一种删除:操作失败抛异常
public void addLast(E e) {
// ArrayDeque 不允许存储null;否则抛空指针异常
if (e == null) throw new NullPointerException();
final Object[] es = elements;
es[tail] = e;
/*
* 调用 inc 方法更新尾指针 tail。
* inc 方法用于处理循环队列中的指针递增操作,确保指针在数组范围内循环。
* if (head == tail):检查头指针 head 是否等于更新后的尾指针 tail。
* 如果相等,说明队列已满,需要扩容
*/
if (head == (tail = inc(tail, es.length)))
grow(1); // 1 表示需要扩展的最小容量
}
2.5.2.2 第二种删除:操作失败返回特殊值
/* 新增元素,新增成功返回true,操作失败抛异常 */
public boolean offerLast(E e) {
addLast(e);
return true;
}
2.5.3 尾部检索业务
2.5.3.1 第一种检索:操作失败抛异常
/*检索(查看)尾部元素;获取失败抛NoSuchElementException异常 */
public E getLast() {
final Object[] es = elements;
E e = elementAt(es, dec(tail, es.length));
if (e == null)
throw new NoSuchElementException();
return e;
}
2.5.3.2 第二种检索:操作失败返回特殊值
/* 检索(查看)尾部元素,没有获取到返回null */
public E peekLast() {
final Object[] es;
return elementAt(es = elements, dec(tail, es.length));
}
2.6 ArrayDeque 环形业务
环形队列是一种利用数组实现的队列数据结构,当数组的末端被使用后,接下来的元素会被放置到数组的开始位置。这意味着队列的“头”和“尾”指针可以在数组的任意位置,这样能更有效地利用空间。
在上面的新增或者是删除业务里面,可以看到有 inc 和 dec 两个方法;这两个方法是用于处理索引的;
inc 是用于处理循环队列中的指针递增;
dec 是用于处理循环队列中的指针递减;
static final int inc(int i, int modulus) {
if (++i >= modulus) i = 0;
return i;
}
static final int dec(int i, int modulus) {
if (--i < 0) i = modulus - 1;
return i;
}
从代码可以看出,利用取模的逻辑来使得这两个方法能够确保队列指针在数组范围内循环,使得队列可以在数组的首尾之间无缝地移动。
【举列子】
原本的队列内容是
elements = [A, B, C, D, E]
后面通过removeFirst(); 将A, B 删除了,位置变成了null,null;
elements = [null, null, C, D, E]
后面再通过addLast,进行追加 F, J,那内容是
elements = [null, null, C, D, E, F, J]
但是通过 inc或者是dec 进行循环利用空余位置,所以内容就是
elements = [C, D, E, F, J]; 这样仅仅只是将位置移动,减少扩容次数来带的性能影响;
所以 inc 和 dec 实时更新head, tail 头尾索引使得数组内容成队列一样有序,并且还可以减少扩容次数来带的性能影响;
2.7 ArrayDeque 扩容机制
private void grow(int needed) {
/*初始化数据,记录旧的容量大小,和定义新的容量*/
final int oldCapacity = elements.length;
int newCapacity;
/* 如果旧容量没有达到64,扩容数量为2; 否则扩容数量为旧容量的50%;*/
int jump = (oldCapacity < 64) ? (oldCapacity + 2) : (oldCapacity >> 1);
if (jump < needed
|| (newCapacity = (oldCapacity + jump)) - MAX_ARRAY_SIZE > 0)
newCapacity = newCapacity(needed, jump);
final Object[] es = elements = Arrays.copyOf(elements, newCapacity);
/* 是否发生环形队列,发生进行处理环形队列 */
if (tail < head || (tail == head && es[head] != null)) {
// 发生环形,再新增空间
int newSpace = newCapacity - oldCapacity;
System.arraycopy(es, head,
es, head + newSpace,
oldCapacity - head);
// 新增出来的空间,从head开始全部设置为null;以备使用
for (int i = head, to = (head += newSpace); i < to; i++)
es[i] = null;
}
}
为什么要处理环形?
当 ArrayDeque
扩容时,数组的头尾指针可能会处在数组的任意位置,因此在将旧数组的数据复制到新数组时,需要保持队列元素的顺序和连续性。处理环形的目的是为了将环形队列的数据重新排列为线性顺序,以便正确地存储在新扩展的数组中。
假设旧数组如下所示,容量为 8,队列从索引 6 到 7 和 0 到 2:
[5, 6, 7, null, null, null, 1, 2, 3, 4]
扩容后新数组的容量为 12:
[null, null, null, null, null, null, 1, 2, 3, 4, 5, 6, 7, null, null, null, null, null, null, null, null, null, null, null]
环形队列在新数组中被重新排列,元素的顺序和位置得到保持;在扩容 ArrayDeque
时处理环形队列的目的是为了确保数据在扩展后的数组中仍然保持连续和有序。通过这种方法,可以有效地利用数组空间并保持队列的正确性和完整性。
2.8 ArrayDeque 相关 Queue 业务
2.8.1 新增业务
【add-属于操作失败抛异常】等同于addLast方法
public boolean add(E e) {
addLast(e);
return true;
}
【offer-属于操作失败返回特殊值】等同于offerLast();
public boolean offer(E e) {
return offerLast(e);
}
2.8.2 删除业务
【remove-属于操作失败抛异常】等同于removeFirst()
public E remove() {
return removeFirst();
}
【poll-属于操作失败返回特殊值】
public E poll() {
return pollFirst();
}
2.8.3 检索业务
【peek-属于操作失败返回特殊值】等同于peekFirst();
public E peek() {
return peekFirst();
}
【peek-属于操作失败抛异常】等同于getFirst();
public E element() {
return getFirst();
}
2.9 ArrayDeque 栈结构业务
2.9.1 入栈
public void push(E e) {
addFirst(e);
}
2.9.2 出栈
public E pop() {
return removeFirst();
}
Deque 双向队列接口在头部队列再封装了一层栈结构的方法API;
2.10 ArrayDeque 克隆业务和序列化业务
在ArrayDeque 的继承结构可以看到,用实现Cloneable接口,也就意味着拥有克隆能力;代码如下:
public ArrayDeque<E> clone() {
try {
@SuppressWarnings("unchecked")
ArrayDeque<E> result = (ArrayDeque<E>) super.clone();
result.elements = Arrays.copyOf(elements, elements.length);
return result;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
同理,ArrayDeque 序列化业务Serializable 接口,也就意味着也拥有序列化的能力,代码如下:
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
s.defaultWriteObject();
s.writeInt(size());
final Object[] es = elements;
for (int i = head, end = tail, to = (i <= end) ? end : es.length;
; i = 0, to = end) {
for (; i < to; i++)
s.writeObject(es[i]);
if (to == end) break;
}
}
@java.io.Serial
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
// Read in size and allocate array
int size = s.readInt();
SharedSecrets.getJavaObjectInputStreamAccess().checkArray(s, Object[].class, size + 1);
elements = new Object[size + 1];
this.tail = size;
// Read in all elements in the proper order.
for (int i = 0; i < size; i++)
elements[i] = s.readObject();
}
2.11 删除指定元素
【顺序遍历-从头部开始查看】
/**
* 删除队列中第一个指定的元素;
* @param o 要删除的元素
*/
public boolean removeFirstOccurrence(Object o) {
if (o != null) {
final Object[] es = elements;
// 开始位置从head到tail 顺序遍历,获取到立即返回
for (int i = head, end = tail, to = (i <= end) ? end : es.length;
; i = 0, to = end) {
for (; i < to; i++)
if (o.equals(es[i])) {
delete(i);
return true;
}
if (to == end) break;
}
}
// 没有该元素;返回false
return false;
}
【倒叙遍历-从尾部开始查看】
public boolean removeLastOccurrence(Object o) {
if (o != null) {
final Object[] es = elements;
for (int i = tail, end = head, to = (i >= end) ? end : 0;
; i = es.length, to = end) {
for (i--; i > to - 1; i--)
if (o.equals(es[i])) {
delete(i);
return true;
}
if (to == end) break;
}
}
return false;
}
三,LinkedList
LinkedList 不仅仅是List 链表实现的列表集合,也是双向队列的具体实现子类;有关于集合LinkedList的List接口旗下相关业务讲解链接:
java-01基础篇-04 Java集合-02-List接口(二)-CSDN博客
这章只讲解Deque接口旗下的相关业务;
3.1 LinkedList 继承结构
package java.util;
import java.util.function.Consumer;
/**
* @author Josh Bloch
* @see List
* @see ArrayList
* @since 1.2
* @param <E> the type of elements held in this collection
*/
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable { /*忽略代码*/}
从继承结构上来看,实现了List,Deque 接口,Cloneable,以及Serializable接口。意味着该类拥有List 列表集合,双向队列,克隆以及序列化相关功能;
3.2 LinkedList 属性
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable {
/* 容量大小 */
transient int size = 0;
/** 头部节点*/
transient Node<E> first;
/** 尾部节点 */
transient Node<E> last;
}
从LinkedList 属性可知,上面三个属性由于被transient 关键字所修饰,意味着在序列化的时候会忽略这三个字段的内容。
3.3 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;
}
}
3.4 LinkedList 头部相关业务
3.4.1 LinkedList 头部新增业务
3.4.1.1 第一种新增:操作失败抛异常
/**
* 从头部新增元素;新增失败抛异常
* @param e 要新增的元素
*/
public void addFirst(E e) {
linkFirst(e);
}
3.4.1.2 第二种新增:操作失败返回特殊值
/**
* 新增元素,操作失败返回特殊值
* @param e 要新增元素
*/
public boolean offerFirst(E e) {
addFirst(e);
return true;
}
3.4.1.3 linkFirst() 头部新增业务
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++; // 修改次数加一
}
3.4.2 LinkedList 头部删除业务
3.4.2.1 第一种删除:删除失败抛异常
/* 删除头部节点 */
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f); // 进行解绑删除
}
3.4.2.2 第二种删除:删除失败返回特殊值(null)
/*删除头部元素,删除成功返回头部节点,删除失败返回为null */
public E pollFirst() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
3.4.2.3 unlinkFirst() 解绑删除业务
/**
* 解绑删除头部元素
* @param f 要解绑删除的元素
*/
private E unlinkFirst(Node<E> f) {
// 将要删除的节点的item,next 都设置为null表示删除
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null; // help GC
first = next;
// 要删除的头部节点的后置节点 next 为空,意味着后面没有内容了,该节点就是尾部节点
if (next == null)
last = null;
else // 否则只其对应后置节点的前置节点进行解绑
next.prev = null;
size--;
modCount++;
return element;
}
3.4.3 LinkList 头部检索业务
3.4.3.1 第一种检索:操作失败抛异常
/* 获取头部节点;没有抛异常 */
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
3.4.3.2 第二种检索:操作失败返回特殊值(null)
/* 检索头部节点,没有返回为null*/
public E peekFirst() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
3.5 LinkedList 尾部相关业务
3.5.1 LinkedList 尾部新增业务
3.5.1.1 第一种新增:操作失败抛异常
/**
* 从尾部新增元素;新增失败抛异常
* @param e 要新增的元素
*/
public void addLast(E e) {
linkLast(e);
}
3.5.1.2 第二种新增:操作失败返回特殊值
/**
* 从尾部新增元素,如果失败返回特殊值(boolean)
* @param e 要新增的元素
*/
public boolean offerLast(E e) {
addLast(e);
return true;
}
3.5.1.3 linkLast() 尾部新增业务
/**
* 从尾部新增元素
* @param e 要新增的元素
*/
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++;
}
3.5.2 LinkedList 尾部删除业务
3.5.2.1 第一种删除:操作失败抛异常
/** 删除尾部节点,删除失败抛异常 */
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l); // 进行解绑删除
}
3.5.2.2 第二种删除:操作失败返回特殊值(null)
/** 删除尾部节点,删除失败返回null;删除成功返回尾部节点的元素 */
public E peekLast() {
final Node<E> l = last;
return (l == null) ? null : l.item;
}
3.5.2.3 unlinkLast() 解绑删除业务
/** 解绑删除尾部节点 */
private E unlinkLast(Node<E> l) {
/* 将尾部节点的元素,前置节点都设置为null 表示删除,由GC回收,尾部节点的后置节点本身就为空,不需要额外处理 */
final E element = l.item;
final Node<E> 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;
}
3.5.3 LinkList 尾部检索业务
3.5.3.1 第一种检索:操作失败抛异常
/** 检索(查看)尾部节点元素 */
public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}
3.5.3.2 第二种检索:操作失败返回特殊值(null)
/** 检索尾部节点,没有返回null*/
public E peekLast() {
final Node<E> l = last;
return (l == null) ? null : l.item;
}
3.6 LinkedList 对Queue接口相关实现
3.6.1 新增业务
【add-属于操作失败抛异常】等同于linkLast方法
public boolean add(E e) {
linkLast(e);
return true;
}
【offer-属于操作失败返回特殊值】
public boolean offer(E e) {
return add(e);
}
3.6.2 删除业务
【remove-属于操作失败抛异常】
public E remove() {
return removeFirst();
}
【poll-属于操作失败返回特殊值】
public E poll() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
3.6.3 检索业务
【peek-属于操作失败返回特殊值】
public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
【element-属于操作失败抛异常】
public E element() {
return getFirst();
}
3.7 LinkedList的克隆业务
public Object clone() {
LinkedList<E> clone = superClone();
// Put clone into "virgin" state
clone.first = clone.last = null;
clone.size = 0;
clone.modCount = 0;
// Initialize clone with our elements
for (Node<E> x = first; x != null; x = x.next)
clone.add(x.item);
return clone;
}
private LinkedList<E> superClone() {
try {
return (LinkedList<E>) super.clone();
} catch (CloneNotSupportedException e) {
throw new InternalError(e);
}
}
3.8 LinkedList 的栈结构方法
3.8.1 入栈
public void push(E e) {
addFirst(e);
}
3.8.2 出栈
public E pop() {
return removeFirst();
}
四,ConcurrentLinkedDeque
4.1 ConcurrentLinkedDeque继承结构
package java.util.concurrent;
public class ConcurrentLinkedDeque<E>
extends AbstractCollection<E>
implements Deque<E>, java.io.Serializable {/* 忽略代码 */}
该接口实现Serializable接口,拥有序列化的能力。
4.2 ConcurrentLinkedDeque 属性
public class ConcurrentLinkedDeque<E>
extends AbstractCollection<E>
implements Deque<E>, java.io.Serializable {
private static final long serialVersionUID = 876323262645176354L;
/** 头部节点 */
private transient volatile Node<E> head;
/** 尾部节点 */
private transient volatile Node<E> tail;
/** 前置节点屏障,后置节点屏障,用于前置节点和后置节点迭代时标记末端;防止防止继续向前或向后遍历 */
private static final Node<Object> PREV_TERMINATOR, NEXT_TERMINATOR;
// VarHandle mechanics 相关属性的VarHandle 类,该类提供CAS操作
private static final VarHandle HEAD;
private static final VarHandle TAIL;
private static final VarHandle PREV;
private static final VarHandle NEXT;
private static final VarHandle ITEM;
static {
PREV_TERMINATOR = new Node<Object>();
PREV_TERMINATOR.next = PREV_TERMINATOR;
NEXT_TERMINATOR = new Node<Object>();
NEXT_TERMINATOR.prev = NEXT_TERMINATOR;
try {
MethodHandles.Lookup l = MethodHandles.lookup();
HEAD = l.findVarHandle(ConcurrentLinkedDeque.class, "head",
Node.class);
TAIL = l.findVarHandle(ConcurrentLinkedDeque.class, "tail",
Node.class);
PREV = l.findVarHandle(Node.class, "prev", Node.class);
NEXT = l.findVarHandle(Node.class, "next", Node.class);
ITEM = l.findVarHandle(Node.class, "item", Object.class);
} catch (ReflectiveOperationException e) {
throw new ExceptionInInitializerError(e);
}
}
}
4.3 Node 节点
static final class Node<E> {
volatile Node<E> prev;
volatile E item;
volatile Node<E> next;
}
这个节点类并没有提供构造器有参构造之类,后续的属性操作都是通过CAS来完成,因为涉及到多线程并发问题,通过CAS来实现保障安全。
4.4 ConcurrentLinkedDeque 构造器
为什么要在初始化的时候head == tail ?
1 创建一个空deque 队列,head== tail 都指向同一个初始节点,这表示队列的两端都是空的。
简化逻辑:
通过这种设计简化后续逻辑,可以不用区分是从队列头部添加还是从尾部添加,因为此时head和tail都指向同一个初始节点。
便于扩展:
这种设计为后续的元素添加和删除操作提供了一个一致的起点。
防止空指针
初始化head 和tail 为同一个节点,可以避免在某些操作中出现空指针异常。
public ConcurrentLinkedDeque() {
head = tail = new Node<E>();
}
从一个现有的集合 c
创建一个新的双端队列
public ConcurrentLinkedDeque(Collection<? extends E> c) {
// Copy c into a private chain of Nodes
Node<E> h = null, t = null;
// 通过循环将元素封装成节点,并通过对应属性的VarHandle的CAS操作设置对应的前置节点和后置节点
for (E e : c) {
Node<E> newNode = newNode(Objects.requireNonNull(e));
if (h == null)
h = t = newNode;
else {
NEXT.set(t, newNode);
PREV.set(newNode, t);
t = newNode;
}
}
// 初始化头部尾部 调用 initHeadTail(h, t),将 h 设置为队列的 head,t 设置为队列的 tail
initHeadTail(h, t);
}
4.5 ConcurrentLinkedDeque 头部相关业务
4.5.1 头部新增业务
4.5.1.1 第一种新增:操作失败抛出异常
public void addFirst(E e) {
linkFirst(e);
}
4.5.1.2 第二种新增:操作失败返回特殊值(boolean)
public boolean offerFirst(E e) {
linkFirst(e);
return true;
}
4.5.1.3 linkFirst() 头部新增业务
private void linkFirst(E e) {
final Node<E> newNode = newNode(Objects.requireNonNull(e));
restartFromHead:
for (;;)
/*
* 从 head 开始,通过前驱节点 prev 向前遍历,
* 检查每个节点的 prev 是否为 null。如果找到节点的 prev 不为 null,
* 则继续向前遍历两个节点(双步遍历:指定的是步长,一般是逐个逐个遍历,现在是每哥隔两个进行遍历),
* 以减少 CAS 操作
*/
for (Node<E> h = head, p = h, q;;) {
if ((q = p.prev) != null &&
(q = (p = q).prev) != null)
p = (h != (h = head)) ? h : q;
else if (p.next == p) // PREV_TERMINATOR 表示已到结尾了,得重新从head开始遍历了
continue restartFromHead;
else {
NEXT.set(newNode, p); // 新节点的 next 指向当前第一个节点 p
// 将当前第一个节点的 prev 从 null 设置为新节点 newNode。如果 CAS 成功,则插入完成,新节点成为第一个节点
if (PREV.compareAndSet(p, null, newNode)) {
if (p != h) // // 每次跳过两个节点;失败也没关系
HEAD.weakCompareAndSet(this, h, newNode);
return;
}
}
}
}
4.5.2 ConcurrentLinkedDeque 头部删除业务
4.5.2.1 第一种删除:操作失败抛异常
public E removeFirst() {
return screenNullResult(pollFirst());
}
private E screenNullResult(E v) {
if (v == null)
throw new NoSuchElementException();
return v;
}
4.5.2.2 第二种删除:操作失败返回特殊值(null)
public E pollFirst() {
restart: for (;;) {
for (Node<E> first = first(), p = first;;) {
final E item;
if ((item = p.item) != null) {
// recheck for linearizability
if (first.prev != null) continue restart;
if (ITEM.compareAndSet(p, item, null)) {
unlink(p);
return item;
}
}
if (p == (p = p.next)) continue restart;
if (p == null) {
if (first.prev != null) continue restart;
return null;
}
}
}
}
4.5.3 头部检索业务
4.5.3.1 第一种检索:操作失败抛异常
public E getFirst() {
return screenNullResult(peekFirst());
}
private E screenNullResult(E v) {
if (v == null)
throw new NoSuchElementException();
return v;
}
4.5.3.2 第二种检索:操作失败返回特殊值
public E peekFirst() {
restart: for (;;) {
E item;
Node<E> first = first(), p = first;
while ((item = p.item) == null) {
if (p == (p = p.next)) continue restart;
if (p == null)
break;
}
// recheck for linearizability
if (first.prev != null) continue restart;
return item;
}
}
4.6 ConcurrentLinkedDeque 尾部相关业务
4.6.1 尾部新增业务
4.6.1.1 第一种新增:操作失败抛异常
public void addLast(E e) {
linkLast(e);
}
4.6.1.2 第二种新增:操作失败返回特殊值
public boolean offerLast(E e) {
linkLast(e);
return true;
}
4.6.1.3 linkLast() 尾部新增业务
private void linkLast(E e) {
final Node<E> newNode = newNode(Objects.requireNonNull(e));
restartFromTail:
for (;;)
for (Node<E> t = tail, p = t, q;;) {
if ((q = p.next) != null &&
(q = (p = q).next) != null)
// Check for tail updates every other hop.
// If p == q, we are sure to follow tail instead.
p = (t != (t = tail)) ? t : q;
else if (p.prev == p) // NEXT_TERMINATOR
continue restartFromTail;
else {
// p is last node
PREV.set(newNode, p); // CAS piggyback
if (NEXT.compareAndSet(p, null, newNode)) {
// Successful CAS is the linearization point
// for e to become an element of this deque,
// and for newNode to become "live".
if (p != t) // hop two nodes at a time; failure is OK
TAIL.weakCompareAndSet(this, t, newNode);
return;
}
// Lost CAS race to another thread; re-read next
}
}
}
4.6.2 尾部删除业务
4.6.2.1 第一种删除:删除失败抛异常
public E removeLast() {
return screenNullResult(pollLast());
}
private E screenNullResult(E v) {
if (v == null)
throw new NoSuchElementException();
return v;
}
4.6.2.2 第二种删除:删除失败返回特殊值
public boolean offerLast(E e) {
linkLast(e);
return true;
}
4.6.2.3 linkLast 尾部新增业务
private void linkLast(E e) {
final Node<E> newNode = newNode(Objects.requireNonNull(e));
restartFromTail:
for (;;)
for (Node<E> t = tail, p = t, q;;) {
if ((q = p.next) != null &&
(q = (p = q).next) != null)
// Check for tail updates every other hop.
// If p == q, we are sure to follow tail instead.
p = (t != (t = tail)) ? t : q;
else if (p.prev == p) // NEXT_TERMINATOR
continue restartFromTail;
else {
// p is last node
PREV.set(newNode, p); // CAS piggyback
if (NEXT.compareAndSet(p, null, newNode)) {
// Successful CAS is the linearization point
// for e to become an element of this deque,
// and for newNode to become "live".
if (p != t) // hop two nodes at a time; failure is OK
TAIL.weakCompareAndSet(this, t, newNode);
return;
}
// Lost CAS race to another thread; re-read next
}
}
}
4.6.3 尾部检索业务
4.6.3.1 第一种检索:操作失败抛异常
public E getLast() {
return screenNullResult(peekLast());
}
private E screenNullResult(E v) {
if (v == null)
throw new NoSuchElementException();
return v;
}
4.6.3.2 第二种检索:操作失败返回特殊值(null)
public E peekLast() {
restart: for (;;) {
E item;
Node<E> last = last(), p = last;
while ((item = p.item) == null) {
if (p == (p = p.prev)) continue restart;
if (p == null)
break;
}
// recheck for linearizability
if (last.next != null) continue restart;
return item;
}
}
4.7 ConcurrentLinkedDeque 相关Queue实现
4.7.1 新增业务
【add-属于操作失败抛异常】
public boolean add(E e) {
return offerLast(e);
}
【offer-属于操作失败返回特殊值】
public boolean offer(E e) {
return offerLast(e);
}
4.7.2 删除业务
【remove-属于操作失败抛异常】
public E remove() { return removeFirst(); }
【poll-属于操作失败返回特殊值】
public E poll() { return pollFirst(); }
4.7.3 检索业务
【peek-属于操作失败返回特殊值】
public E peek() { return peekFirst(); }
【element-属于操作失败抛异常】
public E element() { return getFirst(); }
4.8 ConcurrentLinkedDeque 栈结构的业务
4.8.1 入栈
public void push(E e) { addFirst(e); }
4.8.2 出栈
public E pop() { return removeFirst(); }