简介

ArrayDeque是JDK容器中的一个双端队列实现,不过它内部使用的是数组来对元素进行操作,不允许存储null值,同时可以当做队列,双端队列,栈来进行使用。上篇文章的时候我们分析过LinkedList也是双端队列,不过用的是双向链表结构实现的。

使用示例

public static void main(String[] args) {ArrayDeque<String> deque = new ArrayDeque<>();//添加一个元素(内部也是添加一个元素到尾部)deque.add("adbc");deque.add("123");//在最后一个位置添加元素deque.addLast("last Element");deque.add("456");//对队列的最后一个位置添加元素deque.offer("yahoo");//往队列的最后一个位置添加元素deque.offerLast("tencent");deque.add("789");//往队列中的第一个位置插入一个元素deque.push("Hello");//在第一个位置添加元素deque.addFirst("first Element");//删除第一个元素,并且返回该删除的元素deque.poll();String data = deque.poll();
}

类结构图

注意: 我们知道ArrayDeque内部是由数组实现的,但是我们 知道数组是从左边到右边的。由此我们可知 tail就是数组的第0个元素,head就是数组中的第 length-1个元素

代码分析

通过上面的学习以及类结构图,我们能很清楚的知道ArrayDeque基本使用以及复用关系,它的内部api方法其实很多之间也都是复用的。比如说 add() 和 offer()、offerLast()最后都是调用的 addLast()方法来进行实现的;push()和 offerFirst()最后都是调用的addFirst()方法进行实现的。所以我们只需要分析核心方法,其他找到他们的调用关系就行。

  • 初始化
public class ArrayDeque {//队列内部存储元素的 数组transient Object[] elements;//用于记录 队列 头部 位置信息transient int head;//用于记录 队列 尾部 位置信息transient int tail;private static final int MIN_INITIAL_CAPACITY = 8;public ArrayDeque() {elements = new Object[16];}。。。。。。。
}

根据我们最简单的构造方法,发现首先会创建一个长度为16的数组,同时还有两个很重要的属性head和tail用来指向数组的头和尾巴,同时我们也可以自定义数组的长度

//我们也可以传入我们自定义的容量大小,同时jdk帮我们内部创建一个数组。
public ArrayDeque(int numElements) {allocateElements(numElements);
}/*** 这段代码的核心是找到一个数字 大于等于 numElements同时也是 2的n次幂的数字,同时创建的数组的大小就以这个数字为大小,举个例子,如果我们传入29的话,这个时候会进行运算同时找到32这个数字,同时创建一个大小为32的数组。*/
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.//我们传进来的数字小于8的话,那么就创建一个大小为8的数组。否则就找到最接近于numElements(同时还需要大于或者等于numElements),同时还要是2的n次幂的数字。if (numElements >= initialCapacity) {initialCapacity = numElements;//这里的 >>> 是无符号向右移动多少位然后再跟原来的数字进行 或 运算的。initialCapacity |= (initialCapacity >>>  1);initialCapacity |= (initialCapacity >>>  2);initialCapacity |= (initialCapacity >>>  4);initialCapacity |= (initialCapacity >>>  8);initialCapacity |= (initialCapacity >>> 16);initialCapacity++;//最大只能创建 2的30次幂大小的数组。if (initialCapacity < 0)    // Too many elements, must back offinitialCapacity >>>= 1; // Good luck allocating 2^30 elements}elements = new Object[initialCapacity];
}

上面主要分析的时候了构造方法里面创建数组时的一些注意问题,如果是调用者自己传入大小的话,内部代码还会做一个转换,就是会找到最接近于我们传入这个数字,同时还需要是 2^n = numElements,

  • 添加元素

尾部添加元素

public void addLast(E e) {//从这个代码中我们可以看出该队列不可以添加 null元素if (e == null)throw new NullPointerException();//在初始化的时候 tail为0,所以直接在[0] 添加元素就行elements[tail] = e;//我们知道 elements length = 2 ^ n,所以 length - 1转换为 二进制的话,所有的数字都是1的。比如说 32 - 1转换为2进制的话就是 11111的,所以 (tail + 1) & 31的话还是原来的数字。这里相当于是 tail + 1,也就是 尾部的位置想右边移动了一个位置。//这里有一个非常关键的地方就是 tail = tail + 1同时要判断跟head的位置是否重合,如果重合的话,就需要进行扩容了。if ( (tail = (tail + 1) & (elements.length - 1)) == head)doubleCapacity();
}

头部添加元素

public void addFirst(E e) {if (e == null)throw new NullPointerException();//通过上面的结构图中我们知道head默认指向的是数组的最后一个数字。当 head = 0时 head - 1 为 -1,这个时候 -1 & 31的话还是31,也就是指向数组的最后一个元素。同时 head 指向31。如果再往数组的头部添加元素的话,数组的位置依次向左边移动,相当于 index--的实现。elements[head = (head - 1) & (elements.length - 1)] = e;if (head == tail)doubleCapacity();
}

数组扩容

private void doubleCapacity() {assert head == tail;int p = head;int n = elements.length;int r = n - p; // number of elements to the right of pint newCapacity = n << 1;if (newCapacity < 0)throw new IllegalStateException("Sorry, deque too big");Object[] a = new Object[newCapacity];System.arraycopy(elements, p, a, 0, r);System.arraycopy(elements, 0, a, r, p);elements = a;head = 0;tail = n;
}
  • 删除元素

删除头部元素

public E pollFirst() {final Object[] elements = this.elements;//将head索引赋值给 hfinal int h = head;//然后获取数组 elements 数组的h位置的值E result = (E) elements[h];//如果 elements[h] 不为空的话,表示数组中存在该数据,需要将该位置数据置为null。if (result != null) {elements[h] = null; // Must null out slot//head的位置需要向右边移动一位(head = head + 1)。然后返回该元素head = (h + 1) & (elements.length - 1);}return result;
}

删除尾部元素

public E pollLast() {final Object[] elements = this.elements;//首先我们需要将 tail 位置向 左边移动一位(也就说 tail = tail - 1)。final int t = (tail - 1) & (elements.length - 1);//然后获取数组中的元素E result = (E) elements[t];if (result != null) {//需要将 tail - 1位置的元素置为nullelements[t] = null;//同时 tail - 1 赋值给tailtail = t;}return result;
}

其实上面的不管在首尾添加数据还是在首尾删除数据,其实跟我们现实中的逻辑都是一样的,就是通过控制 head和tail位置来操纵数组,对数组进行增删操作的。如果我们要判断某个元素是否在队列中,则需要去遍历数组,然后每个元素进行比对匹配。

  • 清除所有元素
public void clear() {int h = head;int t = tail;//首先判断首尾不相等。if (h != t) {head = tail = 0;//然后 head赋值 iint i = h;int mask = elements.length - 1;do {elements[i] = null;//这里有个需要注意的是,如果 i+1 大于 elements.lenth以后,再 & mask的话,是循环从mask 到 tail的。其实我很好奇为啥不直接 遍历数组,从0开始遍历到 length -1 呢?i = (i + 1) & mask;} while (i != t);}
}

在文章的开头的时候我们讲过可以当做栈来进行使用

//在栈顶添加元素public void push(E e) {addFirst(e);
}//弹出栈顶的元素
public E poll() {return pollFirst();
}

通过上面的代码我们可以分析的出来, head是作为栈底的,而tail是在栈的最上面。这个时候我们就需要把把 ArrayDeque竖立起来看待了。其原型图:

总结

  1. ArrayDeque 采用数组方式实现的双端队列,通过内部变量 head和tail来控制位置的偏移
  2. ArrayDeque容量不足时会进行扩容,每次扩容的容量在原有基础上扩大一倍
  3. Array还可以当做栈来直接使用

Android ArrayDeque 分析相关推荐

  1. android逆向分析概述_Android存储概述

    android逆向分析概述 Storage is this thing we are all aware of, but always take for granted. Not long ago, ...

  2. Android JNI入门第五篇——Android.mk分析

    转载请标明出处: http://blog.csdn.net/michael1112/article/details/56671708 江东橘子的博客 Android.mk文件是在使用NDK编译C代码时 ...

  3. Android多线程分析之二:Thread的实现

    Android多线程分析之二:Thread的实现 罗朝辉 (http://www.cnblogs.com/kesalin/) CC 许可,转载请注明出处 在前文<Android多线程分析之一:使 ...

  4. Android内存分析和调优(上)

    Android内存分析和调优(上) Android内存分析和调优(上) Android内存分析工具(四):adb命令 posted on 2017-09-25 19:29 时空观察者9号 阅读(... ...

  5. Android Telephony分析(七) ---- 接口扩展(异步转同步)

    本文是基于上一篇<Android Telephony分析(六) -- 接口扩展(实践篇)>来写的.  上一篇介绍的接口扩展的方法需要实现两部分代码:  1. 从APP至RIL,发送请求:  ...

  6. Android Telephony分析(六) ---- 接口扩展(实践篇)

    本文将结合前面五篇文章所讲解的知识,综合起来,实现一个接口扩展的功能.  如果还没有阅读过前面五篇文章的内容,请先阅读:  <Android Telephony分析(一) - Phone详解 & ...

  7. Android Telephony分析(五) ---- TelephonyRegistry详解

    本文紧接着上一篇文章<Android Telephony分析(四) -- TelephonyManager详解 >的1.4小节.  从TelephonyRegistry的大部分方法中:  ...

  8. Android Telephony分析(三) ---- RILJ详解

    前言 本文主要讲解RILJ工作原理,以便更好地分析代码,分析业务的流程.  这里说的RILJ指的是RIL.java (frameworks\opt\telephony\src\java\com\And ...

  9. Android Telephony分析(二) ---- RegistrantList详解

    前言 本文主要讲解RegistrantList的原理,以及如何快速分析RegistrantList相关的代码流程.  在Telephony模块中,在RIL.Tracker(ServiceStateTr ...

最新文章

  1. 练习5-3 数字金字塔 (15 分)
  2. 对云计算的忽视是 银行的战略失误
  3. echart 折线图设置y轴单位_echarts折线图有两个图例时如何实现分别采用两个不同单位的y轴...
  4. Android画图学习总结(四)——Animation(中)
  5. Windows系统帮助中心程序的0day漏洞
  6. python用什么软件编程-python开发用什么编辑器
  7. angularJs的spa页面切换以及ngRoute模块
  8. PAT (Basic Level) Practice1003 我要通过!
  9. python爬虫百度文库源码_Python爬取百度文库学习
  10. Hough变换圆检测定位
  11. 摄影基础知识——光学变焦和数码变焦
  12. jquery获取所有选中的checkbook
  13. 电脑每次开机都要硬盘自检percent complete
  14. C++实现空间中两个三角形位置关系(相交、平行)的判断
  15. coursera课程下载方法
  16. PHP系统常量及判断某常量是否被定义
  17. AES实现加解密-Java
  18. 详细到吐血 —— 树莓派驱动开发入门:从读懂框架到自己写驱动
  19. 开放式可插拔规范 (OPS)
  20. 打造新一代计算平台,STEPVR将在2022开启元宇宙“大门”

热门文章

  1. IDEA中设置注释模板
  2. hbase 协处理器 部署_HBase协处理器加载的三种方式
  3. folly SpinLock源码分析
  4. 阻止PotPlayer播放器自动更新
  5. centos7使用firewall实现限制某个网段只开放单个ip地址访问本地6379端口
  6. Windows server 2012 CA认证
  7. 投资新手理解Anzocapital昂首资本分析3点,翻倍不是梦
  8. C#高级--反射详解
  9. Multisim14.0安装(宝宝级步骤)
  10. 一个简单的网站首页制作