ArrayDeque解析
Queue接口
public abstract boolean add(E paramE);
public abstract boolean offer(E paramE); // 加入元素
public abstract E remove();
public abstract E poll(); // 获取元素
Queue接口提供了以上几个方法。
Queue使用时要尽量避免Collection的add()和remove()方法,而是要使用offer()来加入元素,使用poll()来获取并移出元素。
它们的优点是通过返回值可以判断成功与否,add()和remove()方法在失败的时候会抛出异常。
如果要使用前端而不移出该元素,使用element()或者peek()方法。
ArrayDeque
ArrayDeque的特点
- 大小自增长的队列
- 内部使用数组存储数据
- 线程不安全
- 内部数组长度为8、16、32….. 2的n次方
- 头指针head从内部数组的末尾开始,尾指针tail从0开始,在头部插入数据时,head减一,在尾部插入数据时,tail加一。当head==tail时说明数组的容量满足不了当前的情况,此时需要扩大容量为原来的二倍。
核心思想图

代码解析
- 分配数组大小
private void allocateElements(int numElements) {
int initialCapacity = MIN_INITIAL_CAPACITY;
// 如果指定的数组长度大于最小的值,需要计算数组大小。
// 算法利用或运算和右移运算,计算结果始终为2的n次方,比如numElements的值为4、5、6、7时,initialCapacity的结果为8;numElements的值为8、9、10、11、12、13、14、15时,initialCapacity的结果为16。
// 设计数组的长度为2的n次方是为循环链表考虑的,后面会解释。
if (numElements >= initialCapacity) {
initialCapacity = numElements;
initialCapacity |= (initialCapacity >>> 1);
initialCapacity |= (initialCapacity >>> 2);
initialCapacity |= (initialCapacity >>> 4);
initialCapacity |= (initialCapacity >>> 8);
initialCapacity |= (initialCapacity >>> 16);
initialCapacity++;
// 如果值太大溢出(即值小于0),需要缩小2倍
if (initialCapacity < 0) // Too many elements, must back off
initialCapacity >>>= 1;// Good luck allocating 2 ^ 30 elements
}
elements = new Object[initialCapacity];
}
- 扩大数组长度
private void doubleCapacity() {
// assert head == tail;
int p = head;
int n = elements.length;
// 头指针右边的长度
int r = n - p;
// 扩大2倍
int 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;
// 初始化头指针为0
head = 0;
// 初始化尾指针为n
tail = n;
}
- 在头部插入数据
public void addFirst(E e) {
if (e == null)
throw new NullPointerException("e == null");
// 在头部插入数据,头指针向左移动1,即减一,elements.length为2的n次方,所以elements.lenght-1的二进制表示为1111....1111。
// 特殊情况:假设数组的长度为16,初始化时head为0,那么(head - 1) & (elements.length - 1)就是-1 & 15的值,负数的二进制为对应正数的二进制的补码,-1即为11111....1111,那么-1 & 15 = 15,如上图,当首次向队列头部插入数据时,head在数组的末尾。
elements[head = (head - 1) & (elements.length - 1)] = e;
// 头指针和尾指针相等时(即相遇),表示当前的数组长度不够,需要扩大数组长度
if (head == tail)
doubleCapacity();
}
- 在尾部插入数据
public void addLast(E e) {
if (e == null)
throw new NullPointerException("e == null");
elements[tail] = e;
// tail向右移动,如果头指针和尾指针相等时(即相遇),表示当前的数组长度不够,需要扩大数组长度
if ( (tail = (tail + 1) & (elements.length - 1)) == head)
doubleCapacity();
}
- 遍历
private class DeqIterator implements Iterator<E> {
// 从头指针位置开始遍历
private int cursor = head;
private int fence = tail;
private int lastRet = -1;
public boolean hasNext() {
return cursor != fence;
}
public E next() {
if (cursor == fence)
throw new NoSuchElementException();
@SuppressWarnings("unchecked") E result = (E) elements[cursor];
if (tail != fence || result == null)
throw new ConcurrentModificationException();
lastRet = cursor;
// 每次加一,利用与运算,形成循环。比如,数组的长度为16,当cursor递增到15时,下面的计算应为16 & 15 = 0,cursor的值变为了0.
cursor = (cursor + 1) & (elements.length - 1);
return result;
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
if (delete(lastRet)) { // if left-shifted, undo increment in next()
cursor = (cursor - 1) & (elements.length - 1);
fence = tail;
}
lastRet = -1;
}
}
有以下几点总结:
1)ArrayDeque有两个类属性,head和tail,两个指针。
2)ArrayDeque通过一个数组作为载体,其中的数组元素在add等方法执行时不移动,发生变化的只是head和tail指针,而且指针是循环变化,数组容量不限制。
3)offer方法和add方法都是通过其中的addLast方法实现,每添加一个元素,就把元素加到数组的尾部,此时,head指针没有变化,而tail指针加一,因为指针是循环加的,
所以当tail追上head((this.tail = this.tail + 1 & this.elements.length - 1) == this.head)时,数组容量翻一倍,继续执行。
4)remove方法和poll方法都是通过其中的pollFirst方法实现,每移除一个元素,该元素所在位置变成null,此时,tail指针没有变化,而head指针加一,当数组中没有数据时,返回null。
5)因为ArrayDeque不是线程安全的,所以,用作堆栈时快于 Stack,在用作队列时快于 LinkedList。
ArrayDeque解析的更多相关文章
- 给jdk写注释系列之jdk1.6容器(11)-Queue之ArrayDeque源码解析
前面讲了Stack是一种先进后出的数据结构:栈,那么对应的Queue是一种先进先出(First In First Out)的数据结构:队列. 对比一下Stack,Queue是一种先进先出的容 ...
- Java容器解析系列(7) ArrayDeque 详解
ArrayDeque,从名字上就可以看出来,其是通过数组实现的双端队列,我们先来看其源码: /** 有自动扩容机制; 不是线程安全的; 不允许添加null; 作为栈使用时比java.util.Stac ...
- Android AsyncTask 源码解析
1. 官方介绍 public abstract class AsyncTask extends Object java.lang.Object ↳ android.os.AsyncTask&l ...
- Android AsyncTask完全解析,带你从源码的角度彻底理解
转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/11711405 我们都知道,Android UI是线程不安全的,如果想要在子线程里进 ...
- 给jdk写注释系列之jdk1.6容器(12)-PriorityQueue源码解析
PriorityQueue是一种什么样的容器呢?看过前面的几个jdk容器分析的话,看到Queue这个单词你一定会,哦~这是一种队列.是的,PriorityQueue是一种队列,但是它又是一种什么样的队 ...
- AsyncTask 解析
[转载自 http://blog.csdn.net/yanbober ] 1 背景 Android异步处理机制一直都是Android的一个核心,也是应用工程师面试的一个知识点.前面我们分析了Handl ...
- Android -- AsyncTask源码解析
1,前段时间换工作的时候,关于AsyncTask源码这个点基本上大一点的公司都会问,所以今天就和大家一起来总结总结.本来早就想写这篇文章的,当时写<Android -- 从源码解析Handle+ ...
- Android源码解析——AsyncTask
简介 AsyncTask 在Android API 3引入,是为了使UI线程能被正确和容易地使用.它允许你在后台进行一些操作,并且把结果带到UI线程中,而不用自己去操纵Thread或Handler.它 ...
- 沉淀再出发:java中线程池解析
沉淀再出发:java中线程池解析 一.前言 在多线程执行的环境之中,如果线程执行的时间短但是启动的线程又非常多,线程运转的时间基本上浪费在了创建和销毁上面,因此有没有一种方式能够让一个线程执行完自己的 ...
随机推荐
- springmvc 开涛 数据验证
两种方式:编程和声明. 编程需要:验证器,控制器,servlet.xml,错误码设置 声明需要:加jar包,控制器,跟孔浩讲得类似 错误消息设置的两种方式:硬编码:从资源文件中读取(默认,自定义).
- java web前端easyui(layout+tree+双tabs)布局+树+2个选项卡tabs
1.列出要实现的样式: 2.实现的代码: 分三大部分: 1):页面主体部分:mian.vm <html> <head> <title>Ks UI</title ...
- SyntaxError: Non-ASCII character '\xe5' in file test.py on line 1, but no encoding declared; see http://www.python.org/peps/pep-0263.html for details
python的默认编码文件是用的ASCII码,而你的python文件中使用了中文等非英语字符. 在Python源文件的最开始一行,加入一句: # coding=UTF-8
- delphi 安卓程序如何读取外部配置文件
1)编辑一个config.txt文件,填写配置系统. 2)有外部加载文件时,安卓发布需要另行指定文件发布目录位置 比如加载config.txt需要在 首先利用Project->Deploymen ...
- 序列化Json时遇到的大小写问题及解决方法
最近在一个webapi2项目中遇到了一个问题:C#编码规范中规定属性的首字母是大写的(大多数公司采用这种编码风格),但是从其它系统中接收到的json对象的属性却是小写的(大多数公司采用这种编码风格), ...
- Excel 帮助无法正常工作的解决方法
Excel 中出现错误:帮助无法正常工作,但您仍可以转到 Office.com,以获得最新和最好的文章.视频和培训课程. 英文消息:Help isn't working, but you can st ...
- MLLib实践Naive Bayes
引言 本文基于Spark (1.5.0) ml库提供的pipeline完整地实践一次文本分类.pipeline将串联单词分割(tokenize).单词频数统计(TF),特征向量计算(TF-IDF),朴 ...
- c# 锁 Interlocked 操作
//定义原子变量 ; //原子级别+1值,如果>=0,说明当前锁为空,可以执行,避免重复执行 ) { if (_serverThread == null || (_serverThread.Is ...
- F#语言入门之什么是F#语言
F#是一种函数式编程语言,可以轻松编写正确且可维护的代码. F#编程主要涉及定义类型推断和自动泛化的类型和函数. 这使您可以将焦点保留在问题域上并操纵其数据,而不是编程的细节. open System ...
- .NET Core 玩一玩 Ocelot API网关
.net 这几年国内确实不好过. 很多都选择转行.不过.net Core跨平台 开源之后 .社区的生态在慢慢建立.往好的趋势发展. 对于坚守在.NET战线的开发者来说 是个挺不错的消息. 特别是微软 ...