ArrayDeque使用&实现原理分析
ArrayDeque使用&实现原理分析
学习Okhttp实现源码时,发现其任务分发时用到了ArrayDeque
。因此了解一下ArrayDeque
的使用方式
和实现原理
。
一、Deque
deque(double-ended queue
)双端队列,是一种具有队列和栈的性质的数据结构。
双端队列中的元素可以从两端弹出,其限定插入和删除操作在表的两端进行。假设两端分别为端点A和端点B,在实际应用中:
- 可以有输出受限的双端队列(即端点A允许插入和删除,端点B只允许插入的双端队列);
- 可以有输入受限的双端队列(即端点A允许插入和删除,端点B只允许删除的双端队列);
- 如果限定双端队列从某个端点插入的元素只能从该端点删除,则该双端队列就蜕变为两个栈底相邻的栈;
deque(double-ended queue
)也是一种队列,了解deque
时首先需要回忆一下queue
的使用和实现原理。
对于Queue
(先进先出)的详细使用方式和实现原理,可参考文章:
BlockingQueue使用方式 及 源码阅读
https://blog.csdn.net/xiaxl/article/details/80774479
Deque实现了以下功能方法,供开发者使用:
// 源码来自:android-29/java/util/Deque
public interface Deque<E> extends java.util.Queue<E> {
/**
* 添加元素
*/
// 在队列前边 添加元素,返回是否添加成功
boolean offerFirst( E e);
// 在队列后边 添加元素,返回是否添加成功
boolean offerLast( E e);
// 在队列前边 添加元素
void addFirst(E e);
// 在队列后边添加元素
void addLast( E e);
/**
* 删除元素
*/
// 删除第一个元素,返回删除元素的值;如果元素为null,将返回null;
E pollFirst();
// 删除最后一个元素,返回删除元素的值;如果为null,将返回null;
E pollLast();
// 删除第一个元素,返回删除元素的值;如果元素为null,将抛出异常;
E removeFirst();
// 删除最后一个元素,返回删除元素的值;如果为null,将抛出异常;
E removeLast();
// 删除第一次出现的指定元素
boolean removeFirstOccurrence( java.lang.Object o);
// 删除最后一次出现的指定元素
boolean removeLastOccurrence( java.lang.Object o);
/**
* 取数据
*/
// 获取第一个元素,没有返回null;
E peekFirst();
// 获取最后一个元素,没有返回null;
E peekLast();
// 获取第一个元素,如果没有则抛出异常;
E getFirst();
// 获取最后一个元素,如果没有则抛出异常;
E getLast();
/**
* 队列方法------------------------------------------
*/
// 向队列中添加一个元素。若添加成功则返回true;若因为容量限制添加失败则返回false是。
boolean offer(E e);
// 删除队列头的元素,如果队列为空,则返回null;
E poll();
// 返回队列头的元素,如果队列为空,则返回null;
E peek();
// 向队列中添加一个元素。若添加成功则返回true;若因为容量限制添加失败,则抛出IllegalStateException异常。
boolean add(E e);
// 删除队列头的元素,如果队列为空,则抛出异常;
E remove();
// 返回队列头的元素,如果队列为空,将抛异常;
E element();
/**
* 堆栈方法------------------------------------------
*/
// 栈顶添加一个元素
void push( E e);
// 移除栈顶元素,如果栈顶没有元素将抛出异常
E pop();
}
二、ArrayDeque 源码学习
2.1、变量说明
// 源码来自:android-29/java/util/ArrayDeque
// 用数组存放队列元素;
transient Object[] elements;
// 头部index
transient int head;
// 下一个要添加到尾步的index (除tail=0时,当前的尾部为tail-1);
transient int tail;
head 与 tail 对应的index位置示例如下图所示:
- addFirst 方法:在队列前面 添加元素;
代码实现中:从数组的末尾向前添加数据
,如上图添加顺序为 E1、E2、...; - addLast方法:在队列后面 添加元素;
代码实现中:在数组的前边向后添加数据
,如上图添加顺序为 Ea、Eb、...; - head 为当前头部的index;
- tail 为下一个要添加的尾部的index;
2.2、构造方法
// 源码来自:android-29/java/util/ArrayDeque
// 构造方法:默认数组长度为8
public ArrayDeque() {
// 创建一个长度为16的数组
elements = new Object[16];
}
// 构造方法:根据自定义长度 分配数组空间
public ArrayDeque(int numElements) {
// 根据输入长度 分配数组空间
allocateElements(numElements);
}
// 找到大于需要长度的最小的2的幂整数
private void allocateElements(int numElements) {
// MIN_INITIAL_CAPACITY为8
int initialCapacity = MIN_INITIAL_CAPACITY;
// 假设用户输入的为9
if (numElements >= initialCapacity) {
// initialCapacity = 9;
initialCapacity = numElements;
// initialCapacity = 9 | ( 9 >>> 1)
// initialCapacity = ( 1001 ) | ( 0100 ) = 1101 = 13;
initialCapacity |= (initialCapacity >>> 1);
// initialCapacity = 13 | ( 13 >>> 2)
// initialCapacity = ( 1101 ) | ( 0011 ) = 1111 = 15;
initialCapacity |= (initialCapacity >>> 2);
// initialCapacity = 15 | ( 15 >>> 4)
// initialCapacity = ( 1111 ) | ( 0000 ) = 1111 = 15;
initialCapacity |= (initialCapacity >>> 4);
// initialCapacity = 15 | ( 15 >>> 8)
// initialCapacity = ( 1111 ) | ( 0000 ) = 1111 = 15;
initialCapacity |= (initialCapacity >>> 8);
initialCapacity |= (initialCapacity >>> 16);
// 15+1 = 16;
initialCapacity++;
if (initialCapacity < 0) // Too many elements, must back off
initialCapacity >>>= 1; // Good luck allocating 2^30 elements
}
// 创建数组(数组的长度为:大于需要长度的最小的2的幂整数)
elements = new Object[initialCapacity];
}
以上两个构造方法实现中:
- 第一个构造方法,创建一个默认长度为8的数组;
- 第二个构造方法,如代码中注释举例,其会通过allocateElements(numElements)将数组的长度定义为2的倍数(
找到大于需要长度的最小的2的幂整数
); allocateElements(numElements)
方法:可以将一个任意的初始值转化为2^n的值。
如果本身传进来的值就是2^n 的值,那么经过转化会变成2^(n+1);
如果传入的值大于等于2^30,那么经过转化会变成负值,即< 0,此时会把初始值设置为2^30, 即最大的容量只有2^30;
2.3、队列首部添加元素
offerFirst(E e)、addFirst(E e) 源码实现
// 源码来自:android-29/java/util/ArrayDeque
// 在队列前边 添加元素,返回是否添加成功
public boolean offerFirst(E e) {
addFirst(e);
return true;
}
// 在队列前边 添加元素
public void addFirst(E e) {
// 存入空数据时,抛出异常NullPointerException
if (e == null)
throw new NullPointerException();
// 这样写 当head=0时,添加到的位置为elements.length - 1
// 从数组的末尾 向前 依次添加数据
elements[head = (head - 1) & (elements.length - 1)] = e;
// 空间不足
if (head == tail)
doubleCapacity(); // 扩容
}
offerFirst(E e)、addFirst(E e) 在数组前面添加元素:源码实现中,从数组的末尾向前依次添加数据元素
;
2.4、队列首部删除元素
pollFirst()、removeFirst()源码实现
// 源码来自:android-29/java/util/ArrayDeque
// 删除第一个元素,返回删除元素的值;如果元素为null,将抛出异常;
public E removeFirst() {
E x = pollFirst();
// 若为空,则抛出异常;
if (x == null)
throw new NoSuchElementException();
return x;
}
// 删除第一个元素,返回删除元素的值;如果元素为null,将返回null;
public E pollFirst() {
// 数组
final Object[] elements = this.elements;
// 头部index
final int h = head;
// 头部元素
E result = (E) elements[h];
// 如果头部元素为null,则返回null
if (result != null) {
// 数据元素出队列
elements[h] = null; // Must null out slot
// 指向下一个元素
head = (h + 1) & (elements.length - 1);
}
return result;
}
执行pollFirst()、removeFirst()方法,进行数据出队列时,其数据的删除示意图如下图所示:
2.5、队列尾部添加元素
offerLast(E e)、addLast(E e) 源码实现
// 源码来自:android-29/java/util/ArrayDeque
// 在数组后添加元素,返回是否添加成功
public boolean offerLast(E e) {
addLast(e);
return true;
}
// 在数组后面添加元素
public void addLast(E e) {
// 存入空数据时,抛出异常NullPointerException
if (e == null)
throw new NullPointerException();
// 存入数据
elements[tail] = e;
// (tail + 1) == head 空间已满
if ( (tail = (tail + 1) & (elements.length - 1)) == head)
// 扩容
doubleCapacity();
}
offerLast(E e)、addLast(E e) 在数组后添加元素: 源码实现中,从数组的前端向后依次添加数据元素
;
2.6、队列尾部移除元素
pollLast()、removeLast()源码实现
// 源码来自:android-29/java/util/ArrayDeque
// 删除最后一个元素,返回删除元素的值;如果为null,将抛出异常;
public E removeLast() {
E x = pollLast();
if (x == null)
throw new NoSuchElementException();
return x;
}
// 删除最后一个元素,返回删除元素的值;如果为null,返回null;
public E pollLast() {
// 数组元素
final Object[] elements = this.elements;
// tail-1
final int t = (tail - 1) & (elements.length - 1);
// 获取当前元素
E result = (E) elements[t];
if (result != null) {
// 当前元素置空
elements[t] = null;
// 将tail-1 赋值给 tail
tail = t;
}
return result;
}
执行pollLast()、removeLast()方法,进行数据出队列时,其数据的删除示意图如下图所示:
2.7、扩容 doubleCapacity()
向数组中添加元素时,若数组容量不足,会调用doubleCapacity()方法,扩容为当前容量的两倍:
// 源码来自:android-29/java/util/ArrayDeque
// 空间不足:扩容
private void doubleCapacity() {
assert head == tail;
// 头部 index
int p = head;
// 数组长度
int n = elements.length;
//
int r = n - p;
// 容量扩展为当前的2倍
int newCapacity = n << 1;
// 新的容量超过2^30,抛出异常
if (newCapacity < 0)
throw new IllegalStateException("Sorry, deque too big");
// 创建一个新的数组
Object[] a = new Object[newCapacity];
// 拷贝原数组从 head位置到结束的数据
System.arraycopy(elements, p, a, 0, r);
// 拷贝原数组从 开始到head的数据
System.arraycopy(elements, 0, a, r, p);
// elements置空,保证其中元素可被垃圾回收
Arrays.fill(elements, null);
// elements被重新赋值
elements = a;
// header 和tail重新赋值
head = 0;
tail = n;
}
扩容后的数组为原始数组空间的两倍:
三、ArrayDeque 队列应用
3.1 入队列
// 向队列中添加一个元素。若添加成功则返回true;若因为容量限制添加失败则返回false是。
boolean offer(E e);
ArrayDeque入队列时:offer
会调用offerLast
实现入队列
3.2 出队列
// 删除队列头的元素,如果队列为空,则返回null;
E poll();
ArrayDeque出队列时:poll
会调用pollFirst
实现出队列
四、ArrayDeque 堆栈应用
4.1 入堆栈
// 栈顶添加一个元素
void push( E e);
ArrayDeque入堆栈时:push
会调用offerLast
实现入堆栈
4.2 出堆栈
// 移除栈顶元素,如果栈顶没有元素将抛出异常
E pop();
ArrayDeque出堆栈时:poll
会调用pollFirst
实现出堆栈
参考
百度百科deque
https://baike.baidu.com/item/deque/849385?fr=aladdin
ArrayDeque使用&实现原理分析的更多相关文章
- Handler系列之原理分析
上一节我们讲解了Handler的基本使用方法,也是平时大家用到的最多的使用方式.那么本节让我们来学习一下Handler的工作原理吧!!! 我们知道Android中我们只能在ui线程(主线程)更新ui信 ...
- Java NIO使用及原理分析(1-4)(转)
转载的原文章也找不到!从以下博客中找到http://blog.csdn.net/wuxianglong/article/details/6604817 转载自:李会军•宁静致远 最近由于工作关系要做一 ...
- 原子类java.util.concurrent.atomic.*原理分析
原子类java.util.concurrent.atomic.*原理分析 在并发编程下,原子操作类的应用可以说是无处不在的.为解决线程安全的读写提供了很大的便利. 原子类保证原子的两个关键的点就是:可 ...
- Android中Input型输入设备驱动原理分析(一)
转自:http://blog.csdn.net/eilianlau/article/details/6969361 话说Android中Event输入设备驱动原理分析还不如说Linux输入子系统呢,反 ...
- 转载:AbstractQueuedSynchronizer的介绍和原理分析
简介 提供了一个基于FIFO队列,可以用于构建锁或者其他相关同步装置的基础框架.该同步器(以下简称同步器)利用了一个int来表示状态,期望它能够成为实现大部分同步需求的基础.使用的方法是继承,子类通过 ...
- Camel运行原理分析
Camel运行原理分析 以一个简单的例子说明一下camel的运行原理,例子本身很简单,目的就是将一个目录下的文件搬运到另一个文件夹,处理器只是将文件(限于文本文件)的内容打印到控制台,首先代码如下: ...
- NOR Flash擦写和原理分析
NOR Flash擦写和原理分析 1. NOR FLASH 的简单介绍 NOR FLASH 是很常见的一种存储芯片,数据掉电不会丢失.NOR FLASH支持Execute On Chip,即程序可以直 ...
- 使用AsyncTask异步更新UI界面及原理分析
概述: AsyncTask是在Android SDK 1.5之后推出的一个方便编写后台线程与UI线程交互的辅助类.AsyncTask的内部实现是一个线程池,所有提交的异步任务都会在这个线程池中的工作线 ...
- (转)Android 系统 root 破解原理分析
现在Android系统的root破解基本上成为大家的必备技能!网上也有很多中一键破解的软件,使root破解越来越容易.但是你思考过root破解的 原理吗?root破解的本质是什么呢?难道是利用了Lin ...
随机推荐
- 1058 A+B in Hogwarts (20分)
1058 A+B in Hogwarts (20分) 题目: If you are a fan of Harry Potter, you would know the world of magic h ...
- js 获取URL后面传的参数
function GetRequest() { var url = location.search; //获取url中"?"符后的字串 var theRequest = new O ...
- 使用github作为maven仓库存放发布自己的jar包依赖 实现多个项目公共部分代码的集中,避免团队中多个项目之间代码的复制粘贴
使用github作为maven仓库存放发布自己的jar包依赖 实现多个项目公共部分代码的集中,避免团队中多个项目之间代码的复制粘贴. 1.首先在本地maven位置的配置文件setting.xml(没有 ...
- react-debug
最近练习react的时候遇到一些问题: 在redux模式下,同一个api依据参数获取不同data的时候,返回的data相同 原因:多次调用该接口时,action的type相同,导致对应于该接口的每个r ...
- promise对象里resolve和reject状态讲解及Promise.all()的使用
首先来说下同步异步与阻塞非阻塞的概念,同步异步与阻塞非阻塞并没有关系.同步异步主要是事情做完以后,如何进行处理.或者说关注的是一种消息通信机制. 同步的情况下,是由处理消息者自己去等待消息是否被触发: ...
- PostgreSQL 安装PYTHON扩展,访问页面或者第三方程序
应用场景当数据库中relation表中有数据插入.更新.删除操作,postgresql 调用第三方接口,进行处理.这里用pgsq 中python的扩展插件来实现. 1.安装PostgreSQL中的Py ...
- JDK安装与配置环境变量
1.JDK的安装 (1).为什么安装jdk? JDK是java软件开发包的简称,要想开发java程序就必须安装JDK.没有JDK的话,无法编译Java程序. (2).开始安装jdk 1.官网下载jdk ...
- python基本数据类型;字符串及其方法三:
###################判断类型################### ######################################################### ...
- GYM100962A ABBA
题目链接:https://vjudge.net/problem/Gym-100962A 题目大意: 给出一个 \(h \times w\) 的目标矩阵.定义一种 \(h \times w\) 的矩阵, ...
- Hystrix入门
hystrix对应的中文名字是“豪猪”,豪猪周身长满了刺,能保护自己不受天敌的伤害,代表了一种防御机制,这与hystrix本身的功能不谋而合,因此Netflix团队将该框架命名为Hystrix,并使用 ...