Android ArrayDeque 分析
简介
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竖立起来看待了。其原型图:
总结
- ArrayDeque 采用数组方式实现的双端队列,通过内部变量 head和tail来控制位置的偏移
- ArrayDeque容量不足时会进行扩容,每次扩容的容量在原有基础上扩大一倍
- Array还可以当做栈来直接使用
Android ArrayDeque 分析相关推荐
- android逆向分析概述_Android存储概述
android逆向分析概述 Storage is this thing we are all aware of, but always take for granted. Not long ago, ...
- Android JNI入门第五篇——Android.mk分析
转载请标明出处: http://blog.csdn.net/michael1112/article/details/56671708 江东橘子的博客 Android.mk文件是在使用NDK编译C代码时 ...
- Android多线程分析之二:Thread的实现
Android多线程分析之二:Thread的实现 罗朝辉 (http://www.cnblogs.com/kesalin/) CC 许可,转载请注明出处 在前文<Android多线程分析之一:使 ...
- Android内存分析和调优(上)
Android内存分析和调优(上) Android内存分析和调优(上) Android内存分析工具(四):adb命令 posted on 2017-09-25 19:29 时空观察者9号 阅读(... ...
- Android Telephony分析(七) ---- 接口扩展(异步转同步)
本文是基于上一篇<Android Telephony分析(六) -- 接口扩展(实践篇)>来写的. 上一篇介绍的接口扩展的方法需要实现两部分代码: 1. 从APP至RIL,发送请求: ...
- Android Telephony分析(六) ---- 接口扩展(实践篇)
本文将结合前面五篇文章所讲解的知识,综合起来,实现一个接口扩展的功能. 如果还没有阅读过前面五篇文章的内容,请先阅读: <Android Telephony分析(一) - Phone详解 & ...
- Android Telephony分析(五) ---- TelephonyRegistry详解
本文紧接着上一篇文章<Android Telephony分析(四) -- TelephonyManager详解 >的1.4小节. 从TelephonyRegistry的大部分方法中: ...
- Android Telephony分析(三) ---- RILJ详解
前言 本文主要讲解RILJ工作原理,以便更好地分析代码,分析业务的流程. 这里说的RILJ指的是RIL.java (frameworks\opt\telephony\src\java\com\And ...
- Android Telephony分析(二) ---- RegistrantList详解
前言 本文主要讲解RegistrantList的原理,以及如何快速分析RegistrantList相关的代码流程. 在Telephony模块中,在RIL.Tracker(ServiceStateTr ...
最新文章
- 练习5-3 数字金字塔 (15 分)
- 对云计算的忽视是 银行的战略失误
- echart 折线图设置y轴单位_echarts折线图有两个图例时如何实现分别采用两个不同单位的y轴...
- Android画图学习总结(四)——Animation(中)
- Windows系统帮助中心程序的0day漏洞
- python用什么软件编程-python开发用什么编辑器
- angularJs的spa页面切换以及ngRoute模块
- PAT (Basic Level) Practice1003 我要通过!
- python爬虫百度文库源码_Python爬取百度文库学习
- Hough变换圆检测定位
- 摄影基础知识——光学变焦和数码变焦
- jquery获取所有选中的checkbook
- 电脑每次开机都要硬盘自检percent complete
- C++实现空间中两个三角形位置关系(相交、平行)的判断
- coursera课程下载方法
- PHP系统常量及判断某常量是否被定义
- AES实现加解密-Java
- 详细到吐血 —— 树莓派驱动开发入门:从读懂框架到自己写驱动
- 开放式可插拔规范 (OPS)
- 打造新一代计算平台,STEPVR将在2022开启元宇宙“大门”