文章目录

  • Pre
  • 概述
  • Queue
  • Deque
  • ArrayDeque
    • 一览
    • 构造函数
    • 属性
    • 方法
      • addFirst()
      • addLast()
      • pollFirst()
      • pollLast()
      • peekFirst()
      • peekLast()


Pre

Java Review - ArrayList 源码解读

Java Review - LinkedList源码解读


概述

Java中有Stack类,却没有叫做Queue的类,它是个接口的名字。当需要使用栈时,Java已不推荐使用Stack,而是推荐使用更高效的ArrayDeque;

既然Queue只是一个接口,当需要使用队列时也就首选ArrayDeque了,次选LinkedList。


Queue

Queue接口继承自Collection接口,除了最基本的Collection的方法之外,它还支持额外的insertion, extraction和inspection操作。

这里有两组格式,共6个方法,

  • 一组是抛出异常的实现;
  • 另外一组是返回值的实现(没有则返回null)。
抛出异常的方法 带有返回值的方法
Insert add(e) offer(e)
Remove remove poll()
Examine element() peek

Deque

  • Deque是"double ended queue", 表示双向的队列,英文读作"deck".

  • Deque 继承自 Queue接口,除了支持Queue的方法之外,还支持insert, remove和examine操作

  • 由于Deque是双向的,所以可以对队列的头和尾都进行操作 . 同时也支持两组格式,一组是抛出异常的实现;另外一组是返回值的实现(没有则返回null)。共12个方法如下:

  • 当把Deque当做FIFO的queue来使用时,元素是从deque的尾部添加,从头部进行删除的; 所以deque的部分方法是和queue是等同的。如下
    -

  • Deque的含义是“double ended queue”,即双端队列,它既可以当作栈使用,也可以当作队列使用。下表列出了Deque与Queue相对应的接

  • Deque与Stack对应的接口如下:

上面两个表共定义了Deque的12个接口。

添加,删除,取值都有两套接口,它们功能相同,区别是对失败情况的处理不同。

一组接口遇到失败就会抛出异常

另一组遇到失败会返回特殊值(false或null)。

除非某种实现对容量有限制,大多数情况下,添加操作是不会失败的。虽然Deque的接口有12个之多,但无非就是对容器的两端进行操作,或添加,或删除,或查看。

ArrayDeque

一览

ArrayDeque和LinkedList是Deque的两个通用实现,由于官方更推荐使用AarryDeque用作栈和队列,着重讲解ArrayDeque的具体实现。

  • 从名字可以看出ArrayDeque底层通过数组实现,为了满足可以同时在数组两端插入或删除元素的需求,该数组还必须是循环的,即循环数组(circular array),也就是说数组的任何一点都可能被看作起点或者终点。

  • ArrayDeque是非线程安全的(not thread-safe),当多个线程同时使用的时候,需要手动同步;

  • ArrayDeque不允许放入null元素


上图中我们看到,head指向首端第一个有效元素,tail指向尾端第一个可以插入元素的空位。因为是循环数组,所以head不一定总等于0,tail也不一定总是比head大.


构造函数

/*** Constructs an empty array deque with an initial capacity* sufficient to hold 16 elements.*/public ArrayDeque() {elements = new Object[16];}/*** Constructs an empty array deque with an initial capacity* sufficient to hold the specified number of elements.** @param numElements  lower bound on initial capacity of the deque*/public ArrayDeque(int numElements) {allocateElements(numElements);}/*** Constructs a deque containing the elements of the specified* collection, in the order they are returned by the collection's* iterator.  (The first element returned by the collection's* iterator becomes the first element, or <i>front</i> of the* deque.)** @param c the collection whose elements are to be placed into the deque* @throws NullPointerException if the specified collection is null*/public ArrayDeque(Collection<? extends E> c) {allocateElements(c.size());addAll(c);}
   /*** Allocates empty array to hold the given number of elements.** @param numElements  the number of elements to hold*/private void allocateElements(int numElements) {elements = new Object[calculateSize(numElements)];}
    /*** The minimum capacity that we'll use for a newly created deque.* Must be a power of 2.*/private static final int MIN_INITIAL_CAPACITY = 8;// ******  Array allocation and resizing utilities ******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 offinitialCapacity >>>= 1;// Good luck allocating 2 ^ 30 elements}return initialCapacity;}

三个构造函数

  1. 申请默认大小为16的数组
  2. 提供需要空间大小的有参构造器:利用allocateElements申请空间
  3. 利用现有集合的有参构造器:同样利用allocateElements申请空间,再将现有集合中的元素拷贝到数组中

allocateElements(int numElements) :数组最小空间为8, 如果需要空间小于8,则申请数组大小为8,如果需要空间大于等于8,进行一定的容量扩大,而不只是提供需要数量的空间,防止下一次操作时又要进行扩容


属性

ArrayDeque提供了两个变量来操作数组:head 、 tail.

  • head指向队列的头,tail指向队列尾的下一个位置,队列满的条件就是head == tail.

  • 扩容操作的执行时机:每次在向队列中添加元素以后,不论是在头部还是在尾部添加。

  • 扩容策略:空间是原空间的两倍大,将原来数组中元素拷贝到新数组中,因为是循环队列,可能出现head在tail后面的情况,拷贝到新数组时,从head指向开始拷贝,直到tail,也就是说,拷贝完成后,head指向新数组起始位置,tail指向最后一个元素的下一个位置。


方法

addFirst()

    /*** Inserts the specified element at the front of this deque.** @param e the element to add* @throws NullPointerException if the specified element is null*/public void addFirst(E e) {if (e == null) //不允许放入nullthrow new NullPointerException();elements[head = (head - 1) & (elements.length - 1)] = e; //2.下标是否越界if (head == tail)//1.空间是否够用doubleCapacity();//扩容}

addFirst(E e)的作用是在Deque的首端插入元素,也就是在head的前面插入元素,在空间足够且下标没有越界的情况下,只需要将elements[–head] = e即可 。

上述代码我们看到,空间问题是在插入之后解决的,因为tail总是指向下一个可插入的空位,也就意味着elements数组至少有一个空位,所以插入元素的时候不用考虑空间问题。

下标越界的处理 ,head = (head - 1) & (elements.length - 1)就可以了,这段代码相当于取余,同时解决了head为负值的情况。

因为elements.length必需是2的指数倍,elements - 1就是二进制低位全1,跟head - 1相与之后就起到了取模的作用,如果head - 1为负数(其实只可能是-1),则相当于对其取相对于elements.length的补码。

接下来看扩容的逻辑

    /*** Doubles the capacity of this deque.  Call only when full, i.e.,* when head and tail have wrapped around to become equal.*/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  .head右边元素的个数int newCapacity = n << 1; //原空间的2倍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;}

其逻辑是申请一个更大的数组(原数组的两倍),然后将原数组复制过去。


图中我们看到,复制分两次进行,第一次复制head右边的元素,第二次复制head左边的元素。


addLast()

则调用doubleCapacity()进行扩容。

    /*** Inserts the specified element at the end of this deque.** <p>This method is equivalent to {@link #add}.** @param e the element to add* @throws NullPointerException if the specified element is null*/public void addLast(E e) {if (e == null) //不允许放入nullthrow new NullPointerException();elements[tail] = e; //赋值if ( (tail = (tail + 1) & (elements.length - 1)) == head) //下标越界处理doubleCapacity();//扩容}

addLast(E e)的作用是在Deque的尾端插入元素,也就是在tail的位置插入元素,由于tail总是指向下一个可以插入的空位,因此只需要elements[tail] = e;即可。插入完成后再检查空间,如果空间已经用光调用doubleCapacity()进行扩容。


pollFirst()

public E pollFirst() {E result = elements[head];if (result == null)//null值意味着deque为空return null;elements[h] = null;//let GC workhead = (head + 1) & (elements.length - 1);//下标越界处理return result;
}

pollFirst()的作用是删除并返回Deque首端元素,也即是head位置处的元素。

如果容器不空,只需要直接返回elements[head]即可,当然还需要处理下标的问题。由于ArrayDeque中不允许放入null,当elements[head] == null时,意味着容器为空。


pollLast()

 public E pollLast() {int t = (tail - 1) & (elements.length - 1);//tail的上一个位置是最后一个元素E result = elements[t];if (result == null)//null值意味着deque为空return null;elements[t] = null;//let GC worktail = t;return result;
}

pollLast()的作用是删除并返回Deque尾端元素,也即是tail位置前面的那个元素。


peekFirst()

public E peekFirst() {return elements[head]; // elements[head] is null if deque empty
}

peekFirst()的作用是返回但不删除Deque首端元素,也即是head位置处的元素,直接返回elements[head]即可


peekLast()

public E peekLast() {return elements[(tail - 1) & (elements.length - 1)];
}

peekLast()的作用是返回但不删除Deque尾端元素,也即是tail位置前面的那个元素。

Java Review - Queue和Stack 源码解读相关推荐

  1. java连接mongodb_java连接mongodb源码解读

    用mongdb也大半年了,一直是业务上的逻辑实现了就ok.然而这样并不能进步--因此今天查了查java连接mongodb驱动的源码,搜到的各种信息整合一下,方便以后深入的使用. 先贴连接数据库代码Li ...

  2. C++ STL: 超详细 容器 deque 以及 适配器queue 和 stack 源码分析

    文章目录 前言 deque 实现 deque类 _Deque_iterator 类 deque 的元素插入 insert函数 deque如何模拟空间连续 queue 实现 stack 的实现 前言 C ...

  3. Java Review - PriorityQueue源码解读

    文章目录 Pre PriorityQueue 概述 PriorityQueue 继承关系 PriorityQueue通过用数组表示的小顶堆实现 时间复杂度 构造函数 方法 add()和offer() ...

  4. Java Review - LinkedList源码解读

    文章目录 Pre 概述 底层数据结构-双向链表 源码解析 构造函数 方法源码分析 getFirst() getLast() remove相关方法 remove(e) remove(index) rem ...

  5. Java Review - LinkedHashMap LinkedHashSet 源码解读

    文章目录 Pre 概述 数据结构 类继承关系 构造函数 方法 get() put() remove() LinkedHashSet 使用案例 - FIFO策略缓存 Pre Java Review - ...

  6. Java Review - HashMap HashSet 源码解读

    文章目录 概述 HashMap结构图 构造函数 重点方法源码解读 (1.7) put() get() remove() 1.8版本 HashMap put resize() 扩容 get HashSe ...

  7. hystrix 源码 线程池隔离_“池”的思想:从java线程池到数据库连接池的源码解读(1)...

    一. java线程池 带着问题: 线程是什么时候被创建的? 线程会一直循环取任务任务吗?怎么做的? 线程取不到任务会怎么样? 线程会被Runnable和Callable的异常干掉吗? 线程怎么干掉自己 ...

  8. Java集合Stack源码深入解析

    概要 学完Vector了之后,接下来我们开始学习Stack.Stack很简单,它继承于Vector.学习方式还是和之前一样,先对Stack有个整体认识,然后再学习它的源码:最后再通过实例来学会使用它. ...

  9. aqs java 简书,Java AQS源码解读

    1.先聊点别的 说实话,关于AQS的设计理念.实现.使用,我有打算写过一篇技术文章,但是在写完初稿后,发现掌握的还是模模糊糊的,模棱两可. 痛定思痛,脚踏实地重新再来一遍.这次以 Java 8源码为基 ...

最新文章

  1. Docker创建Docker-Registry-私服
  2. 2014年12月日本語能力試験N3聴解部分
  3. 一次ctf中代码审计分析
  4. 使用C#开发一个简单的P2P应用
  5. 基于node.js+MongoDB+elementui的分页接口以及页面实现
  6. 阿里云开源的Blink,计算能力很疯狂:一眨眼,全部都算好!
  7. “ create-react-app”和创建React应用程序的未来
  8. npm 卸载_前端基础学习(一)--npm
  9. hsrp+route-map 解决多路由器多isp
  10. 12306 模拟登录
  11. 微信小程序点击事件传递自定义参数的方法和跨页面传递数据
  12. 周末不知道学什么?这份 Android 优秀技术文章清单请收下
  13. 使用unity3d 接入anySDK的总结1
  14. 使用downloadm3u8和ffmpeg下载m3u8格式视频
  15. Dynamic ODT
  16. 美国贝勒大学计算机科学专业怎么样,美国贝勒大学好吗
  17. 科学计算机复利现值怎么计算公式,复利现值计算公式
  18. 环保在线监控·水处理设备远程在线监控系统
  19. matlab bsxfun memory,matlab函数bsxfun浅谈(转载)
  20. 深入浅出C语言:(三)C 语言数组指针(指向数组的指针)

热门文章

  1. linux平台性能监控系统,Linux系统性能监控
  2. 电脑计算机无法找到脚本文件夹,我的电脑开机为什么出现无法找到脚本呢?
  3. 镗孔指令g76格式_钻孔、镗孔、攻丝,11个固定循环详解!
  4. 指针数组概念 和 函数指针数组实战 和指针函数的概念和实战
  5. tf.sparse.SparseTensor
  6. 如何在Spyder中运行spark
  7. Git 笔记 上传文件至github
  8. Linux疑难杂症解决方案100篇(十八)-Linux 或 Windows 上实现端口映射
  9. mapreduce编程实例(1)-统计词频
  10. Hadoop学习之以伪分布模式部署Hadoop及常见问题