学习内容:
- Queue,Deque概述
- Queue集合实现–ArrayDeque
学习产出:
概述
Queue(队列),Deque(双端队列)
队列存储数据允许从结构的一端进行操作,并且从结构的另一端进行移除操作,进行入对的是尾部,出队的是头部。双端队列是指可以在一端进行入队操作,又可以进行出队操作的结构
注意:队列和双端队列都不允许在除了队列头部和尾部的其他位置上进行操作,在JCF中具有队列操作特性的集合都实现了java.util.Queue
除了java.util包下面的linkedList和ArrayDeque外,还有大量的实现在java.util.concurrent中。
要学习的ArrayQuque和PriorityQueue使用的分别是可循环的双指针数组和小顶堆
Queue集合实现–ArrayDeque
概述
ArrayDeque是基于数组(可扩容的数组)结构实现的双端队列,与普通的数组结构相比,可以有效减少扩容次数,线程不安全,不能在多线程场景中使用。
ArrayDuque既有队列的,双端队列的操作特点,又有栈结构的操作特点
ArrayDeque结构和相关方法
内部主要结构为一个循环数组,循环数组是一个固定大小的数组,并且定义了一个动态的有效数据范围(不会大于数组长度),只有在这个范围内数据才能被读写,这个范围不好受数组头部和尾部的限制。
transient Object[] elements;
//用来标识下一次移除操作的数据索引位
transient int head;
//下一次添加数据的位置
transient int tail;
作为队列的主要方法
Column 1 | Column 2 |
---|---|
add(e) | 在队列尾部添加新元素,不能为空,成功返回true |
offer(e) | 与add(e)相同,成功返回true |
remove(e) | 从队列头部移除数据,没有数据会抛异常 |
poll(e) | 从队列头部移除数据,没有数据返回null |
element(e) | 获取队列头部数据,不会移除,没有则抛异常 |
peek(e) | 获取队列头部数据,不会移除,没有返回null |
作为双端队列使用的主要方法
方法名 | 方法说明 |
---|---|
addLast(E) | 在队列尾部添加数据,插入的数据不能为空,否则抛异常 |
addFirst(E) | 在队列头部添加数据,插入的数据不能为空,否则抛异常 |
offerFirst(E) | 作用与addFirst(E)相同 ,就是调用addFirst(e),成功返回true |
offerLast(E) | 与addLast(E)相同,就是调用addLast(e),成功返回true |
getFirst() | 获取队列头部数据,没有会抛异常 |
getLast() | 获取队列尾部数据,没有会抛异常 |
peekFirst() | 获取队列头部数据,没有则返回null |
peekLast() | 获取队列尾部数据,没有则返回null |
removeFirst() | 移除队列头部对象,没有抛异常 |
removeLast() | 移除队列尾部对象,没有抛异常 |
pollFirst() | 移除双向链表头部节点,没有返回null |
pollLast() | 移除双向链表尾部节点,没有返回null |
作为栈结构使用
方法名 | 方法说明 |
---|---|
push() | 在栈结构头部添加数据,不能为空 |
pop() | 在栈结构头部移除数据,没有会抛异常 |
ArrayDeque初始化过程
三个重要的属性和三个构造方法
public class ArrayDeque<E> extends AbstractCollection<E>
implements Deque<E>, Cloneable, Serializable
{
transient Object[] elements;
transient int head;
transient int tail;
//集合容量上限
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//初始化一个长度为16+1的数组
public ArrayDeque() {
elements = new Object[16 + 1];
}
//设置集合的初始化容量,如果小于一,则初始化容量值为1的数组
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);
}
1.8之后对内部逻辑做了比较大的修改,主要的思路是在循环数组这添加了一个空索引位置,用于表示在完成数据对象操作后是否需要进行扩容。对扩容过程也进行了调整,使数组的容量值不需要严格满足2的幂。
ArrayDeque集合添加操作
完成初始化之后,可以使用push(E),offerFirst(E),addFirst(E),addLast(E)等方法,在head和tail表示的数据范围的头部或者尾部添加新的数据对象了。使用什么样的操作取决于调用者以何种方式(队列,双端队列或者栈)。实际进行工作的方法只有两个addFirst(E),addLast(E)
addFirst方法
public void addFirst(E e) {
if (e == null)
throw new NullPointerException();
final Object[] es = elements;
//取得上一个索引栏位,并且就数据设置于上面
//在将新位置的索引赋给head,将其成为新的头节点
es[head = dec(head, es.length)] = e;
//如果成立则说明添加操作后数组没有空余索引位,需要进行扩容操作
if (head == tail)
grow(1);
}
static final int dec(int i, int modulus) {
//主要是确认0号索引位置的上一个位置
if (--i < 0) i = modulus - 1;
return i;
}
addLast(E)方法
public void addLast(E e) {
if (e == null)
throw new NullPointerException();
final Object[] es = elements;
es[tail] = e;
//将下一个位置赋给tail,如果和head相等则扩容
if (head == (tail = inc(tail, es.length)))
grow(1);
}
//主要是确实索引为最后一位时的下一个位置
static final int inc(int i, int modulus) {
if (++i >= modulus) i = 0;
return i;
}
ArrayDeque的扩容操作
private void grow(int needed) {
// overflow-conscious code
final int oldCapacity = elements.length;
int newCapacity;
// Double capacity if small; else grow by 50%
//根据是否大于64计算增量
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);
// Exceptionally, here tail == head needs to be disambiguated
if (tail < head || (tail == head && es[head] != null)) {
// wrap around; slide first leg forward to end of array
int newSpace = newCapacity - oldCapacity;
System.arraycopy(es, head,
es, head + newSpace,
oldCapacity - head);
for (int i = head, to = (head += newSpace); i < to; i++)
es[i] = null;
}
}
最后面的操作就是把从head到有数据的这一段(head到原数组这一段)移到最后,再把移动的数据设置为空。