问题

(1)什么是双端队列?

(2)ArrayDeque是怎么实现双端队列的?

(3)ArrayDeque是线程安全的吗?

(4)ArrayDeque是有界的吗?

简介

双端队列是一种特殊的队列,它的两端都可以进出元素,故而得名双端队列。

ArrayDeque是一种以数组方式实现的双端队列,它是非线程安全的。

继承体系

通过继承体系可以看,ArrayDeque实现了Deque接口,Deque接口继承自Queue接口,它是对Queue的一种增强。

public interface Deque extends Queue {

// 添加元素到队列头

void addFirst(E e);

// 添加元素到队列尾

void addLast(E e);

// 添加元素到队列头

boolean offerFirst(E e);

// 添加元素到队列尾

boolean offerLast(E e);

// 从队列头移除元素

E removeFirst();

// 从队列尾移除元素

E removeLast();

// 从队列头移除元素

E pollFirst();

// 从队列尾移除元素

E pollLast();

// 查看队列头元素

E getFirst();

// 查看队列尾元素

E getLast();

// 查看队列头元素

E peekFirst();

// 查看队列尾元素

E peekLast();

// 从队列头向后遍历移除指定元素

boolean removeFirstOccurrence(Object o);

// 从队列尾向前遍历移除指定元素

boolean removeLastOccurrence(Object o);

// *** 队列中的方法 ***

// 添加元素,等于addLast(e)

boolean add(E e);

// 添加元素,等于offerLast(e)

boolean offer(E e);

// 移除元素,等于removeFirst()

E remove();

// 移除元素,等于pollFirst()

E poll();

// 查看元素,等于getFirst()

E element();

// 查看元素,等于peekFirst()

E peek();

// *** 栈方法 ***【本篇文章由公众号“彤哥读源码”原创】

// 入栈,等于addFirst(e)

void push(E e);

// 出栈,等于removeFirst()

E pop();

// *** Collection中的方法 ***

// 删除指定元素,等于removeFirstOccurrence(o)

boolean remove(Object o);

// 检查是否包含某个元素

boolean contains(Object o);

// 元素个数

public int size();

// 迭代器

Iterator iterator();

// 反向迭代器

Iterator descendingIterator();

}

Deque中新增了以下几类方法:

(1)*First,表示从队列头操作元素;

(2)*Last,表示从队列尾操作元素;

(3)push(e),pop(),以栈的方式操作元素的方法;

源码分析

主要属性

// 存储元素的数组

transient Object[] elements; // non-private to simplify nested class access

// 队列头位置

transient int head;

// 队列尾位置

transient int tail;

// 最小初始容量

private static final int MIN_INITIAL_CAPACITY = 8;

从属性我们可以看到,ArrayDeque使用数组存储元素,并使用头尾指针标识队列的头和尾,其最小容量是8。

主要构造方法

// 默认构造方法,初始容量为16

public ArrayDeque() {

elements = new Object[16];

}

// 指定元素个数初始化

public ArrayDeque(int numElements) {

allocateElements(numElements);

}

// 将集合c中的元素初始化到数组中

public ArrayDeque(Collection extends E> c) {

allocateElements(c.size());

addAll(c);

}

// 初始化数组

private void allocateElements(int numElements) {

elements = new Object[calculateSize(numElements)];

}

// 计算容量,这段代码的逻辑是算出大于numElements的最接近的2的n次方且不小于8

// 比如,3算出来是8,9算出来是16,33算出来是64

private static int calculateSize(int numElements) {

int initialCapacity = MIN_INITIAL_CAPACITY;

// Find the best power of two to hold elements.

// Tests "<=" because arrays aren't kept full.

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

}

return initialCapacity;

}

通过构造方法,我们知道默认初始容量是16,最小容量是8。

入队

入队有很多方法,我们这里主要分析两个,addFirst(e)和addLast(e)。

// 从队列头入队

public void addFirst(E e) {

// 不允许null元素

if (e == null)

throw new NullPointerException();

// 将head指针减1并与数组长度减1取模

// 这是为了防止数组到头了边界溢出

// 如果到头了就从尾再向前

// 相当于循环利用数组【本篇文章由公众号“彤哥读源码”原创】

elements[head = (head - 1) & (elements.length - 1)] = e;

// 如果头尾挨在一起了,就扩容

// 扩容规则也很简单,直接两倍

if (head == tail)

doubleCapacity();

}

// 从队列尾入队

public void addLast(E e) {

// 不允许null元素

if (e == null)

throw new NullPointerException();

// 在尾指针的位置放入元素

// 可以看到tail指针指向的是队列最后一个元素的下一个位置

elements[tail] = e;

// tail指针加1,如果到数组尾了就从头开始

if ( (tail = (tail + 1) & (elements.length - 1)) == head)

doubleCapacity();

}

(1)入队有两种方式,从队列头或者从队列尾;

(2)如果容量不够了,直接扩大为两倍;

(3)通过取模的方式让头尾指针在数组范围内循环;

(4)x & (len - 1) = x % len,使用&的方式更快;

扩容

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;

// 判断是否溢出

if (newCapacity < 0)

throw new IllegalStateException("Sorry, deque too big");

// 新建新数组

Object[] a = new Object[newCapacity];

// 将旧数组head之后的元素拷贝到新数组中

System.arraycopy(elements, p, a, 0, r);

// 将旧数组下标0到head之间的元素拷贝到新数组中

System.arraycopy(elements, 0, a, r, p);

// 赋值为新数组

elements = a;

// head指向0,tail指向旧数组长度表示的位置

head = 0;

tail = n;

}

扩容这里迁移元素可能有点绕,请看下面这张图来理解。

出队

出队同样有很多方法,我们主要看两个,pollFirst()和pollLast()。

// 从队列头出队

public E pollFirst() {

int h = head;

@SuppressWarnings("unchecked")

// 取队列头元素

E result = (E) elements[h];

// 如果队列为空,就返回null

if (result == null)

return null;

// 将队列头置为空

elements[h] = null; // Must null out slot

// 队列头指针右移一位

head = (h + 1) & (elements.length - 1);

// 返回取得的元素

return result;

}

// 从队列尾出队

public E pollLast() {

// 尾指针左移一位

int t = (tail - 1) & (elements.length - 1);

@SuppressWarnings("unchecked")

// 取当前尾指针处元素

E result = (E) elements[t];

// 如果队列为空返回null

if (result == null)

return null;

// 将当前尾指针处置为空

elements[t] = null;

// tail指向新的尾指针处

tail = t;

// 返回取得的元素

return result;

}

(1)出队有两种方式,从队列头或者从队列尾;

(2)通过取模的方式让头尾指针在数组范围内循环;

(3)出队之后没有缩容哈哈^^

前面我们介绍Deque的时候说过,Deque可以直接作为栈来使用,那么ArrayDeque是怎么实现的呢?

public void push(E e) {

addFirst(e);

}

public E pop() {

return removeFirst();

}

是不是很简单,入栈出栈只要都操作队列头就可以了。

总结

(1)ArrayDeque是采用数组方式实现的双端队列;

(2)ArrayDeque的出队入队是通过头尾指针循环利用数组实现的;

(3)ArrayDeque容量不足时是会扩容的,每次扩容容量增加一倍;

(4)ArrayDeque可以直接作为栈使用;

彩蛋

双端队列与双重队列?

双端队列(Deque)是指队列的两端都可以进出元素的队列,里面存储的是实实在在的元素。

双重队列(Dual Queue)是指一种队列有两种用途,里面的节点分为数据节点和非数据节点,它是LinkedTransferQueue使用的数据结构。

还记得LinkedTransferQueue吗?点击链接直达【死磕 java集合之LinkedTransferQueue源码分析】。

欢迎关注我的公众号“彤哥读源码”,查看更多源码系列文章, 与彤哥一起畅游源码的海洋。

java arraydeque_死磕 java集合之ArrayDeque源码分析相关推荐

  1. 死磕 java集合之ArrayDeque源码分析

    问题 (1)什么是双端队列? (2)ArrayDeque是怎么实现双端队列的? (3)ArrayDeque是线程安全的吗? (4)ArrayDeque是有界的吗? 简介 双端队列是一种特殊的队列,它的 ...

  2. 【死磕 Java 集合】— LinkedTransferQueue源码分析

    [死磕 Java 集合]- LinkedTransferQueue源码分析 问题 (1)LinkedTransferQueue是什么东东? (2)LinkedTransferQueue是怎么实现阻塞队 ...

  3. 死磕Java集合之BitSet源码分析(JDK18)

    死磕Java集合之BitSet源码分析(JDK18) 文章目录 死磕Java集合之BitSet源码分析(JDK18) 简介 继承体系 存储结构 源码解析 属性 构造方法 set(int bitInde ...

  4. Java并发编程笔记之 CountDownLatch闭锁的源码分析

    转 自: Java并发编程笔记之 CountDownLatch闭锁的源码分析 ​ JUC 中倒数计数器 CountDownLatch 的使用与原理分析,当需要等待多个线程执行完毕后在做一件事情时候 C ...

  5. java ee是什么_死磕 java集合之HashSet源码分析

    问题 (1)集合(Collection)和集合(Set)有什么区别? (2)HashSet怎么保证添加元素不重复? (3)HashSet是否允许null元素? (4)HashSet是有序的吗? (5) ...

  6. 死磕 java集合之ConcurrentSkipListMap源码分析——发现个bug

    前情提要 点击链接查看"跳表"详细介绍. 拜托,面试别再问我跳表了! 简介 跳表是一个随机化的数据结构,实质就是一种可以进行二分查找的有序链表. 跳表在原有的有序链表上面增加了多级 ...

  7. java condition_死磕 java同步系列之ReentrantLock源码解析(二)

    (手机横屏看源码更方便) 问题 (1)条件锁是什么? (2)条件锁适用于什么场景? (3)条件锁的await()是在其它线程signal()的时候唤醒的吗? 简介 条件锁,是指在获取锁之后发现当前业务 ...

  8. Java集合:Hashtable源码分析

    1. 概述 上次讨论了HashMap的结构,原理和实现,本文来对Map家族的另外一个常用集合HashTable进行介绍.HashTable和HashMap两种集合非常相似,经常被各种面试官问到两者的区 ...

  9. java linkedlist源码_Java集合之LinkedList源码分析

    一.LinkedList简介 LinkedList是一种可以在任何位置进行高效地插入和移除操作的有序序列,它是基于双向链表实现的. ps:这里有一个问题,就是关于实现LinkedList的数据结构是否 ...

最新文章

  1. 点分十进制IP校验、转换,掩码校验
  2. CentOS 6.5系统安装配置图解教程(详细图文)
  3. Sqlplus导出excel文件
  4. 这些年我对微服务的理解
  5. java内存泄漏案例_寻找内存泄漏:一个案例研究
  6. 【LA3415 训练指南】保守的老师 【二分图最大独立集,最小割】
  7. Java笔记03-Constructor Override
  8. Databricks文档05----使用 Azure Databricks 连接SQL Server查询数据
  9. 1. 搭建scapy
  10. 云数据库MySQL的发展史
  11. ASCⅡ码与字符的相互转化
  12. 工作站Linux双显卡BIOS设置,在BIOS Setup里面设置双显卡机型的双显卡模式常见方式介绍...
  13. 机器学习之二分类模型评价指标
  14. FFmpeg-Python 给视频添加文字
  15. 资料汇总更新|软件安装包、书籍、源码、技术文档、手册……
  16. 关于graphql快速入门
  17. 期货开户手续费的秘密成了透明
  18. 专项---APP安全---Android APP安全测试内容
  19. C++ GDAL/OGR 库创建多边形类型的图层
  20. 2020 最受 IT 公司欢迎的 30 款开源软件

热门文章

  1. JavaScript基础精讲
  2. Set Covering, Packing and Partitioning Problems
  3. MSAA 的基本原理
  4. 简单讲讲设计四大原则 - 前端读《写给大家看的设计书》收获
  5. 运行ORB-SLAM2
  6. ceph存储的安装和使用
  7. Macbook搭建vue开发环境
  8. HTML分页的效果实现
  9. 【UEFI实战】FSP简介
  10. 告别996,几款可以提高工作效率还免费的办公软件