在Java零基础手把手系列:Queue最简入门之后,今天一起学习其中双端队列Deque的一种实现:ArrayDeque
可以添加博主VX:crazy042438讨论
目录
1. ArrayDeque简介
2. 主要方法
3. 主要用法
3.1 使用ArrayDeque实现Stack
3.2 使用ArrayDeque实现Queue
4.实现原理简析
4.1 主要成员
4.2 特别的容量计算
4.2.1 容量是如何计算的
4.2.2 容量计算图解
4.3 用作Stack(栈)
4.4 用作Queue(队列)
5. ArrayDeque特点总结
1. ArrayDeque简介
ArrayDeque是一种特殊的自增长数组:允许往它的头部和尾部插入或者删除数据,它是Java内置的实现类(继承自Deque)。
既然头部/尾部都可以添加/删除数据,所以ArrayDeque可以用来实现数据结构中的Stack(后进先出LIFO)和队列(先进先出FIFO)。
虽然Java里面实现Stack和Queue有很多替换类,但是文档里面说:
这就非常有意思了,我们一起来看看。
2. 主要方法
ArrayDeque的方法(增加删除等)都提供两类操作:一类调用之后返回状态(如true),一类调用不成功会抛出异常,如(NullPointerException),主要的方法如下:
| 功能 | 方法 | 返回状态 | 方法 | 抛出异常 |
|---|---|---|---|---|
| 往头部插入 | offerFirst(e) | true/异常 | addFirst(e) | NullPointerException |
| 从头部删除 | pollFirst() | null/元素 | removeFirst() | NoSuchElementException |
| 从头部获取 | peekFirst() | null/元素 | getFirst() | NoSuchElementException |
| 往尾部插入 | offerLast(e) | true/异常 | addLast(e) | NullPointerException |
| 从尾部删除 | pollLast() | null/元素 | removeLast() | NoSuchElementException |
| 从尾部获取 | peekLast() | null/元素 | getLast() | NoSuchElementException |
注:offerFirst其实是利用addFirst实现的
3. 主要用法
下面通过几个简单的例子,来看看ArrayDeque的基本使用方法。
3.1 使用ArrayDeque实现Stack
使用内置的两个方法可以轻松实现Stack的功能:
-
push:本质是调用addFirst,来看一下其中源码:
/**
* Pushes an element onto the stack represented by this deque. In other
* words, inserts the element at the front of this deque.
*
* <p>This method is equivalent to {@link #addFirst}.
*
* @param e the element to push
* @throws NullPointerException if the specified element is null
*/
public void push(E e) {
addFirst(e);
}
-
pop:本质是调用removeFirst(源码就略过)
当需要往Stack里面添加元素当时候,调用push方法,需要出栈当时候,调用pop方法,我们来看一个例子:
public void testStack(){
Deque<String> stack = new ArrayDeque<>();
stack.push("first");
stack.push("second");
//输出second
System.out.println(stack.pop());
//输出first
System.out.println(stack.pop());
}
3.2 使用ArrayDeque实现Queue
同样地,使用其中内置的两个方法,可以轻松实现Queue的功能:
-
offer:本质是调用offerLast实现往队尾插入元素
-
poll:本质是调用pollFirst从头部删除元素
来看一个简单的例子:
public void testQueue(){
Deque<String> queue = new ArrayDeque<>();
queue.offer("first");
queue.offer("second");
//输出first
System.out.println(queue.poll());
//输出second
System.out.println(queue.poll());
}
4. 实现原理简析
知道了它的简单使用办法,下面从其主要成员、实现Stack以及Queue的逻辑来说明其内部实现的原理
4.1 主要成员
ArrayDeque有三个最主要的成员变量
//基于数组实现
transient Object[] elements;
//头指针
transient int head;
//尾指针
transient int tail;
这里基本可以猜到:ArrayDeque是以数组为基础(elements),辅助两个指针(头指针head和尾指针tail)来实现基本操作,也就是插入/删除的时候移动头/尾指针,如果遇到头尾重合,则扩容数组,如下图所示:
![]()
4.2 特别的容量计算
先说结论,其容量一定是2的n次方,有兴趣可以读下面2个小节,也可以忽略。
4.2.1 容量是如何计算的
ArrayDeque的源码里面有一个函数allocateElements用于分配数组elements的大小,如下:
private void allocateElements(int numElements) {
int initialCapacity = MIN_INITIAL_CAPACITY;
// Find the best power of two to hold elements.
// 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 = new Object[initialCapacity];
}
一看这个很懵逼,先用通俗易懂的语言总结一下:
a)如果传入数组容量小于8,则数组容量就等于8
b)如果传入数组容量(就是numElements)大于8,则寻找大于numElements的min( 2^n )那个数,比如传入9,那么大于9的min(2^5, 2^4, 2^3, 2^2, 2^1)=16,传入16,则大于16的min( 2^5, 2^4, 2^3…)=32
c)min( 2^n )表示:最小的2的n次方
d)所以数组elements的大小一定是2的n次方,也就只能是8,16,32,64,128等数字的取值
4.2.2 容量计算图解
如果想知道其原理,首先需要明白>>>是表示无符号数右移,比如数字5,二进制为0000 0101:
5 = 0000 0101 >>> 1 结果为:0000 0010 = 2
5 = 0000 0101 >>> 2 结果为:0000 0001 = 1
有了这个基础,来看allocateElements函数的图解:

解释一下:
a) 右移目的:所有原来数字二进制位上全部变为1
b)initialCapacity++的目的:所有为1的变为0,并且进位一个1,比如:
0000 0111 + 1 = 0000 1000
c) 判断小于0:怕加1之后溢出,所以干脆取最大的值吧,32位的话就是2^30次方
4.3 用作Stack(栈)
push和pop 3.1小节说了其本质是调用addFirst和removeFirst,先看操作图解:

![]()

如图示:当调用push往栈里面添加一个元素的时候,是head指针移动,且head的移动方向是从后往前移动(想想为什么呢?),即addFirst里面关键的实现:
head = (head - 1) & (elements.length - 1)
当调用pop推出栈顶元素的时候,先让栈顶引用为null,然后head前移一个位置,即removeFirst/pollFirst里面的关键实现:
head = (head + 1) & (elements.length - 1);
4.4 用作Queue(队列)
offer和poll 3.2小节说了其本质是调用offerLast和pollFirst,先看操作图解:

如图示:当调用offer往队里面添加一个元素的时候,是tail指针移动,且tail移动的方向是从前往后(head这个时候是指向队首),即addLast里面的关键:
tail = (tail + 1) & (elements.length - 1)
当调用poll推出栈顶元素的时候,先让head指向的引用为null,然后head前移一个位置,即removeFirst/pollFirst里面的关键实现:
head = (head + 1) & (elements.length - 1);
5. ArrayDeque特点总结
根据上面的一些简单的实现原理,在辅助一些源码的实现,ArrayDeque有几个特殊的地方,这里总结如下:
-
它不是线程安全的
-
null元素是不行的
-
大多数的操作时间复杂度为常数(可通过序号定位)
-
所以速度快于synchronized Stack
-
所以作为队列使用,比LinkedList快
-
自动扩容的时候,element的size是直接double
本文介绍了Java中的ArrayDeque,一种双端队列实现,可以用于实现Stack和Queue。文章详细讲解了ArrayDeque的主要方法、用法、实现原理,包括成员变量、容量计算以及作为Stack和Queue的工作机制。ArrayDeque以其高效的常数时间复杂度操作和非线程安全特性,成为快速队列实现的选择。
3466

被折叠的 条评论
为什么被折叠?



