ArrayDeque类的使用详解
ArrayDeque
是Deque
接口的一个实现,使用了可变数组,所以没有容量上的限制。ArrayDeque
是线程不安全的,在没有外部同步的情况下,不能再多线程环境下使用。ArrayDeque
是Deque
的实现类,可以作为栈来使用,效率高于Stack
;LinkedList
。ArrayDeque
不支持null
值。一、常用方法
1.添加元素
addFirst(E e)在数组前面添加元素
addLast(E e)在数组后面添加元素
offerFirst(E e) 在数组前面添加元素,并返回是否添加成功
offerLast(E e) 在数组后天添加元素,并返回是否添加成功 2.删除元素
removeFirst()删除第一个元素,并返回删除元素的值,如果元素为null,将抛出异常
pollFirst()删除第一个元素,并返回删除元素的值,如果元素为null,将返回null
removeLast()删除最后一个元素,并返回删除元素的值,如果为null,将抛出异常
pollLast()删除最后一个元素,并返回删除元素的值,如果为null,将返回null
removeFirstOccurrence(Object o) 删除第一次出现的指定元素
removeLastOccurrence(Object o) 删除最后一次出现的指定元素 3.获取元素
getFirst() 获取第一个元素,如果没有将抛出异常
getLast() 获取最后一个元素,如果没有将抛出异常 4.队列操作
add(E e) 在队列尾部添加一个元素
offer(E e) 在队列尾部添加一个元素,并返回是否成功
remove() 删除队列中第一个元素,并返回该元素的值,如果元素为null,将抛出异常(其实底层调用的是removeFirst())
poll() 删除队列中第一个元素,并返回该元素的值,如果元素为null,将返回null(其实调用的是pollFirst())
element() 获取第一个元素,如果没有将抛出异常
peek() 获取第一个元素,如果返回null 5.栈操作
push(E e) 栈顶添加一个元素
pop(E e) 移除栈顶元素,如果栈顶没有元素将抛出异常 6.其他
size() 获取队列中元素个数
isEmpty() 判断队列是否为空
iterator() 迭代器,从前向后迭代
descendingIterator() 迭代器,从后向前迭代
contain(Object o) 判断队列中是否存在该元素
toArray() 转成数组
clear() 清空队列
clone() 克隆(复制)一个新的队列
二、源码分析
结构
在ArrayDeque
底层使用了数组来存储数据,同时用两个int
值head
和tail
来表示头部和尾部。
不过需要注意的是tail
并不是尾部元素的索引,而是尾部元素的下一位,即下一个将要被加入的元素的索引。
//用数组存储元素
transient Object[] elements; // non-private to simplify nested class access
//头部元素的索引
transient int head;
//尾部下一个将要被加入的元素的索引
transient int tail;
//最小容量,必须为2的幂次方
private static final int MIN_INITIAL_CAPACITY = 8;
2. 构造方法
ArrayDeque
有三个构造函数来初始化,除了无参的构造函数使用了默认容量,其它两个构造函数会通过allocateElements
来计算初始容量。
public ArrayDeque() {
elements = (E[]) new Object[16]; // 默认的数组长度大小
}
public ArrayDeque(int numElements) {
allocateElements(numElements); // 需要的数组长度大小
}
public ArrayDeque(Collection<? extends E> c) {
allocateElements(c.size()); // 根据集合来分配数组大小
addAll(c); // 把集合中元素放到数组中
}
3. 分配合适大小的数组
ArrayDeque
对数组的大小(即队列的容量)有特殊的要求,必须是 2^n。我们来看一下allocateElements
方法。
private void allocateElements(int numElements) {
int initialCapacity = MIN_INITIAL_CAPACITY;
// 找到大于需要长度的最小的2的幂整数。
// Tests "<=" because arrays aren't kept full.
if (numElements >= initialCapacity) {
initialCapacity = numElements;
initialCapacity |= (initialCapacity >>> 1);
initialCapacity |= (initialCapacity >>> 2);
initialCapacity |= (initialCapacity >>> 4);
initialCapacity |= (initialCapacity >>> 8);
initialCapacity |= (initialCapacity >>> 16);
initialCapacity++;
if (initialCapacity < 0) // Too many elements, must back off
initialCapacity >>>= 1;// Good luck allocating 2 ^ 30 elements
}
elements = (E[]) new Object[initialCapacity];
}
2^30
的值,经过五次右移和位或操作后,可以得到一个2^k - 1
的值。最后再将这个值+1
,得到2^k
。通过这个方法,可以将一个任意的初始值转化为2^n
的值,不过有一点不足在于,如果本身传进来的值就是2^n
的值,那么经过转化会变成2^(n+1)
,所以我们在不用刻意去传入2^n
的值。还有一点在于,如果传入的值大于等于2^30
,那么经过转化会变成负值,即< 0
,此时会把初始值设置为2^30
,即最大的容量只有2^30
。4. 扩容
// 扩容为原来的2倍。
private void doubleCapacity() {
assert head == tail;
int p = head;
int n = elements.length;
int r = n - p; // number of elements to the right of p
int newCapacity = n << 1;
if (newCapacity < 0)
throw new IllegalStateException("Sorry, deque too big");
Object[] a = new Object[newCapacity];
// 既然是head和tail已经重合了,说明tail是在head的左边。
System.arraycopy(elements, p, a, 0, r); // 拷贝原数组从head位置到结束的数据
System.arraycopy(elements, 0, a, r, p); // 拷贝原数组从开始到head的数据
elements = (E[])a;
head = 0; // 重置head和tail为数据的开始和结束索引
tail = n;
}
// 拷贝该数组的所有元素到目标数组
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;
}
5. 添加元素
public void addFirst(E e) {
if (e == null)
throw new NullPointerException();
// 本来可以简单地写成head-1,但如果head为0,减1就变为-1了,和elements.length - 1进行与操作就是为了处理这种情况,这时结果为elements.length - 1。
elements[head = (head - 1) & (elements.length - 1)] = e;
if (head == tail) // head和tail不可以重叠
doubleCapacity();
}
public void addLast(E e) {
if (e == null)
throw new NullPointerException();
// tail位置是空的,把元素放到这。
elements[tail] = e;
// 和head的操作类似,为了处理临界情况 (tail为length - 1时),和length - 1进行与操作,结果为0。
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;
}
6. 删除元素 删除首尾元素:
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;
E result = elements[h]; // Element is null if deque empty
if (result == null)
return null;
// 表明head位置已为空
elements[h] = null; // Must null out slot
head = (h + 1) & (elements.length - 1); // 处理临界情况(当h为elements.length - 1时),与后的结果为0。
return result;
}
public E pollLast() {
int t = (tail - 1) & (elements.length - 1); // 处理临界情况(当tail为0时),与后的结果为elements.length - 1。
E result = elements[t];
if (result == null)
return null;
elements[t] = null;
tail = t; // tail指向的是下个要添加元素的索引。
return result;
}
删除指定元素
public boolean removeFirstOccurrence(Object o) {
if (o == null)
return false;
int mask = elements.length - 1;
int i = head;
E x;
while ( (x = elements[i]) != null) {
if (o.equals(x)) {
delete(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; // 末尾元素的索引
E 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位置没有元素
assert head == tail ? elements[head] == null :
(elements[head] != null &&
elements[(tail - 1) & (elements.length - 1)] != null); // 如果head和tail重叠,队列为空;否则head位置有元素,tail-1位置有元素。
assert elements[(head - 1) & (elements.length - 1)] == null; // head-1的位置没有元素。
} private boolean delete(int i) {
checkInvariants();
final E[] 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
if (front >= ((t - h) & mask)) // i不在head和tail之间
throw new ConcurrentModificationException(); // Optimize for least element motion
if (front < back) { // i的位置靠近head,移动开始的元素,返回false。
if (h <= i) {
System.arraycopy(elements, h, elements, h + 1, front);
} else { // Wrap around
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; // head位置后移
return false;
} else { // i的位置靠近tail,移动末尾的元素,返回true。
if (i < t) { // Copy the null tail as well
System.arraycopy(elements, i + 1, elements, i, back);
tail = t - 1;
} else { // Wrap around
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;
}
}
7. 获取元素
public E getFirst() {
E x = elements[head];
if (x == null)
throw new NoSuchElementException();
return x;
}
public E getLast() {
E x = elements[(tail - 1) & (elements.length - 1)]; // 处理临界情况(当tail为0时),与后的结果为elements.length - 1。
if (x == null)
throw new NoSuchElementException();
return x;
}
public E peekFirst() {
return elements[head]; // elements[head] is null if deque empty
}
public E peekLast() {
return elements[(tail - 1) & (elements.length - 1)];
}
8. 队列操作
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();
}
9. 栈操作
public void push(E e) {
addFirst(e);
} public E pop() {
return removeFirst();
}
10. 集合方法
public int size() {
return (tail - head) & (elements.length - 1); // 和elements.length - 1进行与操作是为了处理当tail < head时的情况。
}
public boolean isEmpty() {
return head == tail; // tail位置的元素一定为空,head和tail相等,也为空。
}
// 向前迭代器
public Iterator<E> iterator() {
return new DeqIterator();
}
// 向后迭代器
public Iterator<E> descendingIterator() {
return new DescendingIterator();
} 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;
}
public E next() {
if (cursor == fence)
throw new NoSuchElementException();
E result = 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;
cursor = (cursor + 1) & (elements.length - 1); // 游标位置加1
return result;
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
if (delete(lastRet)) { // 如果将元素从右往左移,需要将游标减1。
cursor = (cursor - 1) & (elements.length - 1); // 游标位置回退1。
fence = tail; // 重置阀值。
}
lastRet = -1;
}
} private class DescendingIterator implements Iterator<E> {
private int cursor = tail; // 游标开始索引为tail
private int fence = head; // 游标的阀值为head
private int lastRet = -1;
public boolean hasNext() {
return cursor != fence;
}
public E next() {
if (cursor == fence)
throw new NoSuchElementException();
cursor = (cursor - 1) & (elements.length - 1); // tail是下个添加元素的位置,所以要减1才是尾节点的索引。
E result = elements[cursor];
if (head != fence || result == null)
throw new ConcurrentModificationException();
lastRet = cursor;
return result;
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
if (!delete(lastRet)) { // 如果从左往右移,需要将游标加1。
cursor = (cursor + 1) & (elements.length - 1);
fence = head;
}
lastRet = -1;
}
}
public boolean contains(Object o) {
if (o == null)
return false; // ArrayDeque不可以存储null元素
int mask = elements.length - 1;
int i = head;
E x;
while ( (x = elements[i]) != null) {
if (o.equals(x))
return true;
i = (i + 1) & mask; // 处理临界情况
}
return false;
}
public boolean remove(Object o) {
return removeFirstOccurrence(o);
}
public void clear() {
int h = head;
int t = tail;
if (h != t) { // clear all cells
head = tail = 0; // 重置首尾索引
int i = h;
int mask = elements.length - 1;
do {
elements[i] = null; // 清除元素
i = (i + 1) & mask;
} while (i != t);
}
}
public Object[] toArray() {
return copyElements(new Object[size()]); // 把所有元素拷贝到新创建的Object数组上,所以对返回数组的修改不会影响该双端队列。
}
public <T> T[] toArray(T[] a) {
int size = size();
if (a.length < size) // 目标数组大小不够
a = (T[])java.lang.reflect.Array.newInstance(
a.getClass().getComponentType(), size); // 利用反射创建类型为T,大小为size的数组。
yElements(a); // 拷贝所有元素到目标数组。
if (a.length > size)
a[size] = null; // 结束标识
return a;
}
11. Object方法
public ArrayDeque<E> clone() {
try {
ArrayDeque<E> result = (ArrayDeque<E>) super.clone();
result.elements = Arrays.copyOf(elements, elements.length); // 深度复制。
return result;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
ArrayDeque类的使用详解的更多相关文章
- 【转】UML类图与类的关系详解
UML类图与类的关系详解 2011-04-21 来源:网络 在画类图的时候,理清类和类之间的关系是重点.类的关系有泛化(Generalization).实现(Realization).依赖(D ...
- String类的构造方法详解
package StringDemo; //String类的构造方法详解 //方法一:String(); //方法二:String(byte[] bytes) //方法三:String (byte[] ...
- [转]c++类的构造函数详解
c++构造函数的知识在各种c++教材上已有介绍,不过初学者往往不太注意观察和总结其中各种构造函数的特点和用法,故在此我根据自己的c++编程经验总结了一下c++中各种构造函数的特点,并附上例子,希望对初 ...
- Scala 深入浅出实战经典 第63讲:Scala中隐式类代码实战详解
王家林亲授<DT大数据梦工厂>大数据实战视频 Scala 深入浅出实战经典(1-87讲)完整视频.PPT.代码下载:百度云盘:http://pan.baidu.com/s/1c0noOt6 ...
- UML类图与类的关系详解
摘自:http://www.uml.org.cn/oobject/201104212.asp UML类图与类的关系详解 2011-04-21 来源:网络 在画类图的时候,理清类和类之间的关系是重点.类 ...
- phpcms加载系统类与加载应用类之区别详解
<?php 1. 加载系统类方法load_sys_class($classname, $path = ''", $initialize = 1)系统类文件所在的文件路径:/phpcms ...
- c++类的构造函数详解
c++类的构造函数详解 一. 构造函数是干什么的 class Counter{ public: // 类Counter的构造函数 // 特点:以类名作为函数名,无返回类 ...
- 对python3中pathlib库的Path类的使用详解
原文连接 https://www.jb51.net/article/148789.htm 1.调用库 ? 1 from pathlib import 2.创建Path对象 ? 1 2 3 4 5 ...
- String类内存空间详解
java.lang.String类内存问题详解 字符串理解的难点在于其在堆内存空间上的特殊性,字符串String对象在堆内存上有两种空间: 字符串池(String pool):特殊的堆内存,专门存放S ...
随机推荐
- Machine Learning--决策树(一)
决策树(decision tree):是机器学习常见的算法之一.是基于树形结构进行决策的. 讲决策树就要提到“信息熵”.“信息增益”.“增益率”和“基尼指数”的概念. 我们先来介绍一下这几个概念:(讲 ...
- 【DFS序+线段树区间更新区间求最值】HDU 5692 Snacks
http://acm.hdu.edu.cn/showproblem.php?pid=5692 [思路] 每更新一个点,子树的所有结点都要更新,所以是区间更新 每查询一个点,子树的所有结点都要查询,所以 ...
- Java线程池快速学习教程
1. Java线程池 线程池:顾名思义,用一个池子装载多个线程,使用池子去管理多个线程. 问题来源:应用大量通过new Thread()方法创建执行时间短的线程,较大的消耗系统资源并且系统的响应速度变 ...
- sgu 102模拟欧拉函数
感觉自己弱爆了,做做SGU吧... #include<iostream> #include<cmath> //欧拉函数 using namespace std; int eul ...
- ubuntu下用webbench做网站压力测试
安装依赖 exuberant-ctags sudo apt-get install exuberant-ctags 下载源码并安装 wget http://blog.s135.com/soft/lin ...
- ls 不是内部或外部命令
在C:\windows目录下新建一个文件 命名为 ls.bat 打开编辑这个文件 输入: @echo off dir 这两句保存即可.
- 洛谷——P1025 数的划分
P1025 数的划分 题目描述 将整数n分成k份,且每份不能为空,任意两个方案不相同(不考虑顺序). 例如:n=7,k=3,下面三种分法被认为是相同的. 1,1,5; 1,5,1; 5,1,1; 问有 ...
- String的引用传递
一 引用传递的三个范例 范例一 package com.mtzsoft; /** * 范例一 * * @author Administrator * */ public class Test1 { p ...
- Java中获取项目根路径和类加载路径的7种方法
引言 在web项目开发过程中,可能会经常遇到要获取项目根路径的情况,那接下来我就总结一下,java中获取项目根路径的7种方法,主要是通过thisClass和System,线程和request等方法. ...
- Google代码风格指南
官网:https://github.com/google/styleguide 中文版:https://github.com/zh-google-styleguide/zh-google-styleg ...