ArrayDeque1.6
ArrayDeque是JDK容器中的一个双端队列实现,内部使用数组进行元素存储,不允许存储null值,可以高效的进行元素查找和尾部插入取出,是用作队列、双端队列、栈的绝佳选择,性能比LinkedList还要好,1.6引入
问题:为什么要用循环数组 ?
由于一般的数组实现的队列结构在频繁出队的情况下,会产生假溢出现象,导致数组使用效率降低,所以引入循环队列这种结构。
假溢出:在顺序队中,当尾指针已经到了数组的上界,不能再有入队操作,但其实数组中还有空位置。因为尾指针总会指向最后一个元素的下一个位置,当下一个位置为数组长度+1时,此时判断数组满,但是数组还有空位置,这就是假溢出现象,为了解决此问题引入了循环数组的概念
特性
- 无容量大小限制,容量按需增长;
- 非线程安全队列,无同步策略,不支持多线程安全访问;
- 当用作栈时,性能优于
Stack
,当用于队列时,性能优于LinkedList
- 两端都可以操作
- 具有fail-fast特征
- 不能存储
null
,因为tail总指向的空,如果存在空元素,将无法判断数组是否满 - 支持双向迭代器遍历
源码分析
继承和实现
/**
* Resizable-array implementation of the {@link Deque} interface. Array
* deques have no capacity restrictions; they grow as necessary to support
* usage. They are not thread-safe; in the absence of external
* synchronization, they do not support concurrent access by multiple threads.
* Null elements are prohibited. This class is likely to be faster than
* {@link Stack} when used as a stack, and faster than {@link LinkedList}
* when used as a queue.
{@link Deque}接口的可调整数组实现。阵列
deques没有容量限制;它们根据需要增长以支持
用法。它们不是线程安全的;在没有外部
同步,它们不支持多线程并发访问。
禁止使用空元素。这节课可能比
{@link Stack}用作堆栈时,速度比{@link LinkedList}快
当用作队列时。
public class ArrayDeque<E> extends AbstractCollection<E>
implements Deque<E>, Cloneable, Serializable
- 继承AbstractCollection,而AbstractCollection继承Collection,里面实现集合的基本方法
- 实现Deque接口,双端队列
- Cloneable接口 标记接口,重写Object中的clone方法,如果不实现这个接口,则会抛出CloneNotSupportedException(克隆不被支持)异常
- Serializable接口 实现可序列化
属性
//数组
transient Object[] elements; // non-private to simplify nested class access非私有以简化嵌套类访问
transient int head; // 头部引用
transient int tail;//尾部引用
private static final int MIN_INITIAL_CAPACITY = 8;//初始大小,必须是2的幂次方
构造器
public ArrayDeque() {
//无参构造 数组初始值大小为16
elements = new Object[16];
}
public ArrayDeque(int numElements) {
// 有参构造,因为底层是循环数组,必须满足是2的n次方幂,若不是2的n次方则会进行以下操作
allocateElements(numElements);
}
//分配
private void allocateElements(int numElements) {
elements = new Object[calculateSize(numElements)];
}
//计算大小 分配合适大小的数组
private static int calculateSize(int numElements) {
int initialCapacity = MIN_INITIAL_CAPACITY;// 8
// Find the best power of two to hold elements.
// Tests "<=" because arrays aren't kept full.
//找到2的最佳幂。
//测试“<=”,因为数组没有保持满。
if (numElements >= initialCapacity) {
//判断传入大小和初始大小的大小
initialCapacity = numElements;
// a|b的规则是 两个二进制对应位为0时该位为0,否则为1
//此处设计思想为:
//通过| 和 位操作 将原数值增大到最近的2的n次方数值
// 比如5 0000 0101 最近的2的n次方为 2^3 8
// 第一次 0101 | 0010 = 0111——》 7
initialCapacity |= (initialCapacity >>> 1);// /2
//第二次 0111 | 0001 = 0111 7 以下一样
initialCapacity |= (initialCapacity >>> 2);// /4
initialCapacity |= (initialCapacity >>> 4);// /8
initialCapacity |= (initialCapacity >>> 8);// /16
initialCapacity |= (initialCapacity >>> 16);// /32
//最后输出7 在进行++ 操作 即为8
initialCapacity++;
// 但是会出现如果传入值大于2^30 那经过转化后就会变为负值,此时会把初始值设置为2^30最大容量
if (initialCapacity < 0) // Too many elements, must back off
initialCapacity >>>= 1;// Good luck allocating 2 ^ 30 elements
}
return initialCapacity;
}
// 将集合c加入数组中
public ArrayDeque(Collection<? extends E> c) {
allocateElements(c.size());
addAll(c);
}
方法
因为它大多数情况下都会用作栈和队列的使用,除了基本的Collection接口的操作外,还有针对栈和队列的操作,以下会介绍增删改查的源码分析
增加
需要注意的时:此处的add和offer add会判处异常,而offer不会抛出异常
public void addFirst(E e) {
// 头部添加
if (e == null)//误差分析
throw new NullPointerException(); //抛出异常 没有指针异常
//这里用用&操作,是存在head=0的情况 -1和elements.length - 1 & 的值为elements.length - 1,达到循环数组的操作
elements[head = (head - 1) & (elements.length - 1)] = e;
if (head == tail)//在这里的tail不是指的是队尾元素,而是尾元素的下一个空位置
doubleCapacity();//此时head = tail 扩容看下边
}
public void addLast(E e) {
//尾部添加
if (e == null)
throw new NullPointerException();
elements[tail] = e; //直接tail位置
//如果tail=head,说明数组满,与要扩容
if ( (tail = (tail + 1) & (elements.length - 1)) == head)
doubleCapacity();
}
public boolean offerFirst(E e) {
// 头部添加
addFirst(e);
return true;
}
public boolean offerLast(E e) {
//尾部添加
addLast(e);
return true;
}
public boolean add(E e) {
//当用作队列时 默认队尾添加
addLast(e);
return true;
}
public boolean offer(E e) {
//队尾添加
return offerLast(e);
}
public void push(E e) {
//当栈用时,默认头部入栈
addFirst(e);
}
扩容处理,采用2倍扩容方式
此处的assert关键字 1.4引入,其意思为对某种假设条件进行检查,它被定义为宏的形式,不是函数,程序在假设条件下,能够正常良好的运作,其实就相当于一个 if 语句
if(假设成立)
{
程序正常运行;
}
else
{
报错&&终止程序!(避免由程序运行引起更大的错误)
}
private void doubleCapacity() {
assert head == tail; // 断言判断是否相等
int p = head; // 保存head
int n = elements.length; //保存数组长度
int r = n - p; // number of elements to the right of p 保存p(head)右侧元素数
int newCapacity = n << 1; // 2倍扩容
if (newCapacity < 0) //可能超出2的30次方了
throw new IllegalStateException("Sorry, deque too big");
Object[] a = new Object[newCapacity]; //定义一个新数组
//这里为什么要用两次arraycopy,因为要保证head还指向头部
System.arraycopy(elements, p, a, 0, r);
System.arraycopy(elements, 0, a, r, p);
elements = a;
head = 0; // 更新head指向头位置
tail = n;//更新tail指向尾部元素的下一个
}
删除元素,首尾
此处的remove 和 poll 前者会抛出异常,后者不会
public E remove() {
// 当作队列时默认头部删除
return removeFirst();
}
public E poll() {
// 默认头部删除
return pollFirst();
}
public E removeFirst() {
E x = pollFirst();
if (x == null)//此时不存在会抛出异常
throw new NoSuchElementException();//没有此元素异常
return x;
}
public E removeLast() {
E x = pollLast();
if (x == null)
throw new NoSuchElementException();
return x;
}
public E pollFirst() {
int h = head; //保存头部
@SuppressWarnings("unchecked") //未选中 告诉编译器忽略 unchecked 警告信息,意思可以是null元素,编译期会通过,因为deque接口不允许有空值
E result = (E) elements[h]; //保存这个元素
// Element is null if deque empty 如果deque为空,则元素为null
if (result == null)
return null;
elements[h] = null; // Must null out slot 垃圾回收
head = (h + 1) & (elements.length - 1);//头指向指向下一个
return result;//返回这个值
}
public E pollLast() {
//尾部删除
int t = (tail - 1) & (elements.length - 1);//因为tail指向的是最后一个元素的下一个空位置,所以得先找到最后一个元素
@SuppressWarnings("unchecked")//可以是空
E result = (E) elements[t];
if (result == null)
return null;
elements[t] = null;
tail = t;
return result;
}
public E pop() {
//当作栈时默认头部出栈
return removeFirst();
}
删除指定元素,为什么要进行多次安全检测,因为Arraydeque是非线程安全的
// 删除第一次出现的元素
public boolean removeFirstOccurrence(Object o) {
if (o == null)
return false;
int mask = elements.length - 1;//保存数组长度 mask即掩码
int i = head;//保存头部
Object x; //x用于保存待删的元素
while ( (x = elements[i]) != null) {
//从前往后遍历数组
if (o.equals(x)) {
//如果相等
delete(i); //删除i
return true;
}
i = (i + 1) & mask;
}
return false;
}
public boolean removeLastOccurrence(Object o) {
if (o == null)
return false;
int mask = elements.length - 1;
int i = (tail - 1) & mask;
Object x;
while ( (x = elements[i]) != null) {
//从后往前遍历数组
if (o.equals(x)) {
delete(i);
return true;
}
i = (i - 1) & mask;
}
return false;
}
private void checkInvariants() {
// 有效性检查,
assert elements[tail] == null; // 判断是否尾部为空,tail位置没有元素
//如果head == tail说明数组为空,令head位置为空,否则头部有元素,tail-1有元素
assert head == tail ? elements[head] == null :
(elements[head] != null &&
elements[(tail - 1) & (elements.length - 1)] != null);
//head-1位置没有元素
assert elements[(head - 1) & (elements.length - 1)] == null;
}
private boolean delete(int i) {
//删除 i位置元素
checkInvariants();//校验不变量 有效性检查
final Object[] elements = this.elements;//定义一个新数组保存旧数组
final int mask = elements.length - 1;
final int h = head;//保存头部
final int t = tail;//保存尾部
final int front = (i - h) & mask;//i位置前的元素个数
final int back = (t - i) & mask;//i位置后的元素个数
// Invariant: head <= i < tail mod circularity
//不变的是: 头小于i小于tail 保证循环性
//再次检验,如果i到头部的距离大于等于尾部到头部的距离,表示当前队列已经被修改了,通过最开始检测,i是不应该满足该条件。
if (front >= ((t - h) & mask))//如果i不在head和tail之间
throw new ConcurrentModificationException();
//判断i靠近头还是尾,尽量移动较少元素
// Optimize for least element motion
if (front < back) {
//如果i靠近head
if (h <= i) {
//在进行检测 h小于等于i 在i前面
//直接覆盖 比如 0 1 2 3 3是待删元素,覆盖后 0 0 1 2
System.arraycopy(elements, h, elements, h + 1, front);
} else {
// Wrap around h大于i 在i后面
System.arraycopy(elements, 0, elements, 1, i);
elements[0] = elements[mask];
System.arraycopy(elements, h, elements, h + 1, mask - h);
}
elements[h] = null;
head = (h + 1) & mask;
return false;//返回false则是 从左往右移
} else {
//i靠近tail
if (i < t) {
// Copy the null tail as well i在tail前
System.arraycopy(elements, i + 1, elements, i, back);
tail = t - 1;
} else {
// Wrap around i在tail后面
System.arraycopy(elements, i + 1, elements, i, mask - i);
elements[mask] = elements[0];
System.arraycopy(elements, 1, elements, 0, t);
tail = (t - 1) & mask;
}
return true; //返回true则是从右往左
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6IDsaiLa-1608379323651)(D:\笔记\Typora\JAVANSH\数据结构\quque和deque接口\image-20201218223914922.png)]
获取元素
public E element() {
//获取元素 默认获取队头
return getFirst();
}
public E getFirst() {
@SuppressWarnings("unchecked")
E result = (E) elements[head];
if (result == null)
throw new NoSuchElementException();
return result;
}
public E getLast() {
@SuppressWarnings("unchecked")
E result = (E) elements[(tail - 1) & (elements.length - 1)];
if (result == null)
throw new NoSuchElementException();
return result;
}
@SuppressWarnings("unchecked")
public E peekFirst() {
// elements[head] is null if deque empty
return (E) elements[head];
}
@SuppressWarnings("unchecked")
public E peekLast() {
return (E) elements[(tail - 1) & (elements.length - 1)];
}
是否包含contains
public boolean contains(Object o) {
if (o == null)
return false;
int mask = elements.length - 1;
int i = head;
Object x;
while ( (x = elements[i]) != null) {
if (o.equals(x))
return true;
i = (i + 1) & mask;
}
return false;
}
迭代器
//向前迭代器
public Iterator<E> iterator() {
return new DeqIterator();
}
private class DeqIterator implements Iterator<E> {
private int cursor = head;
private int fence = tail;
private int lastRet = -1;//指向最近的next()调用返回的索引,可以找到需要删除的位置
public boolean hasNext() {
return cursor != fence; //终止条件 head = tail时
}
public E next() {
if (cursor == fence)
throw new NoSuchElementException();
@SuppressWarnings("unchecked")
E result = (E) elements[cursor]; // 保存当前值
// This check doesn't catch all possible comodifications,
// but does catch the ones that corrupt traversal
//这个检查并没有捕捉到所有可能的修改,
//但能抓到那些破坏遍历的
if (tail != fence || result == null)//有效性检查
throw new ConcurrentModificationException();
lastRet = cursor;// 更新lastRet
cursor = (cursor + 1) & (elements.length - 1);//指向下一个
return result;
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
if (delete(lastRet)) {
// if left-shifted, undo increment in next() 如果将元素从右往左移,需要将游标减1。因为next时游标+1了
cursor = (cursor - 1) & (elements.length - 1);//回退1
fence = tail; //更新fence
}
lastRet = -1;//重置
}
//向后迭代器
public Iterator<E> descendingIterator() {
//跟向前差不多
return new DescendingIterator();
}
实现栈
//判空
public boolean isEmpty() {
return head == tail;
}
//出栈
public E pop() {
return removeFirst();
}
//入栈
public void push(E e) {
addFirst(e);
}
//获取栈顶元素
public E peek() {
return peekFirst();
}
实现队列
public boolean add(E e) {
addLast(e);
return true;
}
public boolean offer(E e) {
return offerLast(e);
}
public E remove() {
return removeFirst();
}
public E poll() {
return pollFirst();
}
public E element() {
return getFirst();
}
public E peek() {
return peekFirst();
}
Collection集合方法
public int size() {
//有效个数
return (tail - head) & (elements.length - 1);
}
//判空
public boolean isEmpty() {
return head == tail;
}
//迭代器
public Iterator<E> iterator() {
return new DeqIterator();
}
public Iterator<E> descendingIterator() {
return new DescendingIterator();
}
//是否包含
public boolean contains(Object o) {
if (o == null)
return false;
int mask = elements.length - 1;
int i = head;
Object x;
while ( (x = elements[i]) != null) {
if (o.equals(x))
return true;
i = (i + 1) & mask;
}
return false;
}
//转数组
public Object[] toArray() {
return copyElements(new Object[size()]);
}
private <T> T[] copyElements(T[] a) {
if (head < tail) {
System.arraycopy(elements, head, a, 0, size());
} else if (head > tail) {
int headPortionLen = elements.length - head;
System.arraycopy(elements, head, a, 0, headPortionLen);
System.arraycopy(elements, 0, a, headPortionLen, tail);
}
return a;
}