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. 【干货分享】流程DEMO-费用报销

    流程名: 费用报销 业务描述: 流程发起时,要选择需要关联的事务审批单,会检查是否超申请,如果不超申请,可以直接发起流程,如果超了申请,需要检查预算,如果预算不够,将不允许发起报销申请,如果预算够用, ...

  2. JAVA的内存模型(变量的同步)

    一个线程中变量的修改可能不会立即对其他线程可见,事实上也许永远不可见. 在代码一中,如果一个线程调用了MyClass.loop(),将来的某个时间点,另一个线程调用了MyClass.setValue( ...

  3. 如玫瑰一般的PHP与C#混合编程

    故事背景是这样的,有一套项目,服务器端是用C#写的,为了完成某种事情,它需要使用到一个组件,这个组件很小但很重要,很不巧的是,这个这个组件是用PHP语言写的,如果为了使用这个组件而专门搭建一个PHP的 ...

  4. 深入浅出聊优化:从Draw Calls到GC

    前言: 刚开始写这篇文章的时候选了一个很土的题目...<Unity3D优化全解析>.因为这是一篇临时起意才写的文章,而且陈述的都是既有的事实,因而给自己“文(dou)学(bi)”加工留下的 ...

  5. .net使用正则表达式校验、匹配字符工具类

    开发程序离不开数据的校验,这里整理了一些数据的校验.匹配的方法: /// <summary> /// 字符(串)验证.匹配工具类 /// </summary> public c ...

  6. 【WCF】自定义地址头的筛选器

    前面的文章中,老周已向大伙伴们介绍了如何在终结点上使用地址头,只要服务是沿着该终结点调用的,那么每一次调用都会自动把地址头插入到SOAP消息的Header列表中. 而通过前一篇文章中的示例,大家也看到 ...

  7. J2EE 项目读写分离

    先回答下 1.为啥要读写分离? 大家都知道最初开始,一个项目对应一个数据库,基本是一对一的,但是由于后来用户及数据还有访问的急剧增多, 系统在数据的读写上出现了瓶颈,为了让提高效率,想读和写不相互影响 ...

  8. Hadoop入门学习笔记---part3

    2015年元旦,好好学习,天天向上.良好的开端是成功的一半,任何学习都不能中断,只有坚持才会出结果.继续学习Hadoop.冰冻三尺,非一日之寒! 经过Hadoop的伪分布集群环境的搭建,基本对Hado ...

  9. Android ScrollView监听滑动到顶部和底部的两种方式(你可能不知道的细节)

    Android ScrollView监听滑动到顶部和底部,虽然网上很多资料都有说,但是不全,而且有些细节没说清楚 使用场景: 1. 做一些复杂动画的时候,需要动态判断当前的ScrollView是否滚动 ...

  10. 关于css清除浮动,解决内容溢出的问题

    以前在布局的时候总会遇到这样的问题,比如我想让整体的内容居中,所以会这样写, .main-content{ width:960px:height:300px;margin:0px auto; } 然后 ...