ArrayDeque

数组循环队列,这个数据结构设计的挺有意思的。
据说此类很可能在用作堆栈时快于 Stack,在用作队列时快于 LinkedList。

一、容量

1.1默认容量是8=2^3

1.2指定初始化容容量

    public ArrayDeque(int numElements) {
allocateElements(numElements);
}
private void allocateElements(int numElements) {
int initialCapacity = MIN_INITIAL_CAPACITY;
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];
}
此方法是给数组分配初始容量,初始容量并不是numElements,而是大于指定长度的最小的2的幂正数
所以ArrayDeque的容量一定是2的幂整数
计算的方法是用或运算

1.3或运算的特点:

得到的结果大于等于任意一个操作数
结果有趋向每个位都为1的趋势
所以这样运算下来,运算得到的结果的二进制一定是每个位都是1,再加一个,就刚好是2的整数幂了

1.4扩展容量

当头尾指针相遇,则数组存满了
此时要扩展容量,会调用
    private void doubleCapacity() {
assert head == tail;
int p = head;
int n = elements.length;
int r = n - p; // number of elements to the right of p
int newCapacity = n << 1;//bit count faster
if (newCapacity < 0)
throw new IllegalStateException("Sorry, deque too big");
Object[] a = new Object[newCapacity];
System.arraycopy(elements, p, a, 0, r);//copy the right of head(include head)
System.arraycopy(elements, 0, a, r, p);//copy the left of the tail(exclude tail)
elements = a;
head = 0;
tail = n;
}

1.5细说doubleCapacity

注意看:
1.求两本容量,n<<1,使用位运算要快于四则运算,因为这更贴近运算器电路的设计
2.复制原来的数组到目标数组要注意顺序!
可以看到,复制分两次复制进行,第一次复制head指针右边的元素(包括head指针指向的那个),第二次复制left指针左边的元素(不包括tail指针指向的那个)
扩展为原来两本的容量,结果还是2的幂整数
为什么容量一定要是2的幂整数呢?待会说

二、头尾指针

一开始,头尾指针都在下标为0的地方,如果向队头插入数据,头指针向左移,向队尾插入数据,尾指针向右移
tail指针所在的位置,不存储数据,代表下一次addLast存储的地方

三、add

队列提供增加到队首和队尾的两种方法,注意看怎么处理指针临界状态和指针循环

3.1addLast

    public void addLast(E e) {
if (e == null)
throw new NullPointerException();
elements[tail] = e;
if ( (tail = (tail + 1) & (elements.length - 1)) == head)
doubleCapacity();
}
移动tail指针,用了与运算
与运算的特点:
结果一定小于等于任意一个操作符的值
与正数进行与运算结果一定为证
当tail+1了之后,超过数组长度,用与运算可以起到循环指针的效果,相当于(tail+1%elements.length)
因为elements.length一定是2的整数幂,当-1了之后每一位一定是1,当tile+1超过数组长度的时候,刚好是2的整数幂,则是10***00这种形式,所以与运算了之后,一定等于0

3.2addFirst

    public void addFirst(E e) {
if (e == null)
throw new NullPointerException();
elements[head = (head - 1) & (elements.length - 1)] = e;
if (head == tail)
doubleCapacity();
}
当head-1<0的时候,用与运算可以得到绝对值,并且循环指针
因为当head超过数组,head-1刚好是-1,则二进制每个都是1,与运算了之后,一定是111***111的正数,刚好是数组的最后一个位置

四、指针相遇

当tail == head的时候,首尾指针重合,此时队列已满,需要扩展队列,调用doubleCapacity

五、利用空间局部性

在方法中需要多次调用的全局变量,最好创建一个局部变量来访问
因为全局变量是在静态存储区中的,局部变量是放在栈中的,和方法的指令中同一个区域,所以访问会更快,提高程序性能
就像下面这样
    public boolean removeFirstOccurrence(Object o) {
if (o == null)
return false;
int mask = elements.length - 1;
int i = head;
Object x;
while ( (x = elements[i]) != null) {
if (o.equals(x)) {
delete(i);
return true;
}
i = (i + 1) & mask;
}
return false;
}
创建了一个mask,来存放elements.length-1

6.优化删除策略

    private boolean delete(int i) {
checkInvariants();
final Object[] elements = this.elements;
final int mask = elements.length - 1;
final int h = head;
final int t = tail;
final int front = (i - h) & mask;
final int back = (t - i) & mask; // Invariant: head <= i < tail mod circularity if (front >= ((t - h) & mask))
throw new ConcurrentModificationException(); // Optimize for least element motion
//最优化删除策略
if (front < back) {//如果要删除的元素在前半段
if (h <= i) {//如果head在要删除元素的前面
System.arraycopy(elements, h, elements, h + 1, front);//将要删除元素的前继元素往后移动一格
} else { // Wrap around
System.arraycopy(elements, 0, elements, 1, i);//把i前面的元素往后挪一格
elements[0] = elements[mask];
System.arraycopy(elements, h, elements, h + 1, mask - h);//head-数组末端(不包括数组末端) 都往后移一格
}
elements[h] = null;//帮助垃圾收集
head = (h + 1) & mask;//头指针回退一格
return false;
} else {
if (i < t) { // Copy the null tail as well
System.arraycopy(elements, i + 1, elements, i, back);
tail = t - 1;
} else { // Wrap around
System.arraycopy(elements, i + 1, elements, i, mask - i);
elements[mask] = elements[0];
System.arraycopy(elements, 1, elements, 0, t);
tail = (t - 1) & mask;
}
return true;
}
}
这段源码我觉得很值得一看,他用了最优删除策略,都适用与数组实现的数据结构
当删除的时候,先计算删除点是在队列的上半段还是下半段
如果是上半段,则上半段移动一格
这样子可以达到最少的元素移动!
对于这种循环队列,需要注意删除元素的位置,有两种特殊位置

6.1第一种特殊位置

此时删除节点在队列的上半段,但是上半段是断开的
这个时候要移动上半段的话,要分两次移动
第一次移动删除元素前面的,第二次移动头指针到数组末端

6.2第二种特殊位置

查看原文:http://blog.zswlib.com/2016/10/27/jdk%e6%ba%90%e7%a0%81%e5%88%86%e6%9e%90arraydeque/

jdk源码分析ArrayDeque的更多相关文章

  1. JDK源码分析—— ArrayBlockingQueue 和 LinkedBlockingQueue

    JDK源码分析—— ArrayBlockingQueue 和 LinkedBlockingQueue 目的:本文通过分析JDK源码来对比ArrayBlockingQueue 和LinkedBlocki ...

  2. JDK 源码分析(4)—— HashMap/LinkedHashMap/Hashtable

    JDK 源码分析(4)-- HashMap/LinkedHashMap/Hashtable HashMap HashMap采用的是哈希算法+链表冲突解决,table的大小永远为2次幂,因为在初始化的时 ...

  3. JDK源码分析(三)—— LinkedList

    参考文档 JDK源码分析(4)之 LinkedList 相关

  4. JDK源码分析(一)—— String

    dir 参考文档 JDK源码分析(1)之 String 相关

  5. JDK源码分析(2)LinkedList

    JDK版本 LinkedList简介 LinkedList 是一个继承于AbstractSequentialList的双向链表.它也可以被当作堆栈.队列或双端队列进行操作. LinkedList 实现 ...

  6. 【JDK】JDK源码分析-LinkedHashMap

    概述 前文「JDK源码分析-HashMap(1)」分析了 HashMap 主要方法的实现原理(其他问题以后分析),本文分析下 LinkedHashMap. 先看一下 LinkedHashMap 的类继 ...

  7. 【JDK】JDK源码分析-HashMap(1)

    概述 HashMap 是 Java 开发中最常用的容器类之一,也是面试的常客.它其实就是前文「数据结构与算法笔记(二)」中「散列表」的实现,处理散列冲突用的是“链表法”,并且在 JDK 1.8 做了优 ...

  8. 【JDK】JDK源码分析-TreeMap(2)

    前文「JDK源码分析-TreeMap(1)」分析了 TreeMap 的一些方法,本文分析其中的增删方法.这也是红黑树插入和删除节点的操作,由于相对复杂,因此单独进行分析. 插入操作 该操作其实就是红黑 ...

  9. 【JDK】JDK源码分析-Vector

    概述 上文「JDK源码分析-ArrayList」主要分析了 ArrayList 的实现原理.本文分析 List 接口的另一个实现类:Vector. Vector 的内部实现与 ArrayList 类似 ...

随机推荐

  1. ABAP实现屏幕自己刷新和跳转功能

    ABAP开发工程中,有时候需要让跳转出的屏幕自动实现跳转和刷新的功能,该功能的实现需要在屏幕PBO 里面调用相应的事件执行. 关键代码为: SET TITLEBAR ' 屏幕自动程序'. IF g_c ...

  2. SVN的使用

  3. WebAPI 2参数绑定方法

    简单类型参数 Example 1: Sending a simple parameter in the Url [RoutePrefix("api/values")] public ...

  4. tg2015 信息传递 (洛谷p2661)

    题目描述 有n个同学(编号为1到n)正在玩一个信息传递的游戏.在游戏里每人都有一个固定的信息传递对象,其中,编号为i的同学的信息传递对象是编号为Ti同学. 游戏开始时,每人都只知道自己的生日.之后每一 ...

  5. [翻译]Orchard如何工作

    Orchard一直是博主心中神一般的存在,由于水平比较菜,Orchard代码又比较复杂看了几次都不了了之了.这次下定决心要搞懂其工作原理,争取可以在自己的项目中有所应用.为了入门先到官网去学习一下相关 ...

  6. 解决VMWARE NAT SERVICE服务无法启动或服务消失的问题

    解决VMWARE NAT SERVICE服务无法启动或服务消失的问题 2016-02-02 11:18 2012人阅读 评论(2) 收藏 举报  分类: 网络通信(3)  今日使用VMware中的Wi ...

  7. Android 捕获异常并在应用崩溃后重启应用

    问题概述: 在Android应用开发中,偶尔会因为测试的不充分导致一些异常没有被捕获,这时应用会出现异常并强制关闭,这样会导致很不好的用户体验,为了解决这个问题,我们需要捕获相关的异常并做处理. 首先 ...

  8. 在IDEA上跑eclipse开发的J2EE项目

    Context MacOS 10.12.1 IDEA ULTIMATE 2016.2 项目使用eclipse开发 项目使用SVN进修版本管理 核心步骤 检出项目,完成基本配置 从svn检出 当项目下载 ...

  9. Enterprise Solution 进销存管理软件 C/S架构,支持64位系统 物流,资金流,信息流全面集成

          定位  Target Customers 中小型生产制造企业,批发零售类,贸易企业 主要模块 Modules 采购.销售.库存.财务账款四大模块,包含企业运作过程中销售.采购.库存各岗位需 ...

  10. jQuery之常用且重要方法梳理(siblings,nextAll,end,wrap,apply,call,each)-(二)

    1.siblings() siblings() 获得匹配集合中每个元素的同胞,通过选择器进行筛选是可选的. <body> <div><span>Hello</ ...