下面是allocateElements方法的实现:

Java代码  

private void allocateElements(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

initialCapacity >>>= 1;// Good luck allocating 2 ^ 30 elements

}

elements = (E[]) new Object[initialCapacity];

}

这部分代码里最让人困惑的地方就是对initialCapacity做的这一大堆移位和或运算。首先通过无符号右移1位,与原来的数字做或运算,然后在右移2、4、8、16位。这么做的目的是使得最后生成的数字尽可能每一位都是1。而且很显然,如果这个数字是每一位都为1,后面再对这个数字加1的话,则生成的数字肯定为2的若干次方。而且这个数字也肯定是大于我们的numElements值的最小2的指数值。这么说来有点绕。我们前面折腾了大半天,就为了求一个2的若干次方的数字,使得它要大于我们指定的数字,而且是最接近这个数字的数。这样子到底是为什么呢?因为我们后面要扩展数组长度的话,有了它这个基础我们就可以判断这个数字是不是到了2的多少多少次方,它增长下去最大的极限也不过是2的31次方。这样他每次的增长刚好可以把数组可以允许的长度给覆盖了,不会出现空间的浪费。比如说,我正好有一个数组,它的长度比Integer.MAX_VALUE的一半要大几个元素,如果我们这个时候设置的值不是让它为2的整数次方,那么直接对它空间翻倍就导致空间不够了,但是我们完全可以设置足够空间来容纳的。

我们现在再来看doubleCapacity方法:

Java代码  

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 <

if (newCapacity

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 = (E[])a;

head = 0;

tail = n;

}

有了前面的讨论,它只要扩展空间容量的时候左移一位,这就相当于空间翻倍了。如果长度超出了允许的范围,就会发生溢出,返回的结果就会成为一个负数。这就是为什么有 if (newCapacity < 0)这一句来抛异常。

添加元素

我们先看看两个主要添加元素的方法add和offer:

Java代码  

public boolean add(E e) {

addLast(e);

return true;

}

public void addLast(E e) {

if (e == null)

throw new NullPointerException();

elements[tail] = e;

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

doubleCapacity();

}

public boolean offer(E e) {

return offerLast(e);

}

public boolean offerLast(E e) {

addLast(e);

return true;

}

很显然,他们两个方法的底层实现实际上是一样的。这里要注意的一个地方就是我们由于不断的入队和出队,可能head和tail都会移动到超过数组的末尾。这个时候如果有空闲的空间,我们会把头或者尾跳到数组的头开始继续移动。所以添加元素并确定元素的下标是一个将元素下标值和数组长度进行求模运算的过程。addLast方法通过和当前数组长度减1求与运算来得到最新的下标值。它的效果相当于tail = (tail + 1) % elements.length;

Java代码  

public void addFirst(E e) {

if (e == null)

throw new NullPointerException();

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

if (head == tail)

doubleCapacity();

}

public boolean offerFirst(E e) {

addFirst(e);

return true;

}

addFirst和offerFirst是在head元素的之前插入元素,所以他们的位置为 (head - 1) & (elements.length - 1)。

取元素

获取元素主要包括如下几个方法:

Java代码  

public E element() {

return getFirst();

}

public E getFirst() {

E x = elements[head];

if (x == null)

throw new NoSuchElementException();

return x;

}

public E peek() {

return peekFirst();

}

public E peekFirst() {

return elements[head]; // elements[head] is null if deque empty

}

public E getLast() {

E x = elements[(tail - 1) & (elements.length - 1)];

if (x == null)

throw new NoSuchElementException();

return x;

}

public E peekLast() {

return elements[(tail - 1) & (elements.length - 1)];

}

这部分代码算是最简单的,无非就是取tail元素或者head元素的值。

关于队列的几种运算方法定义的特别杂乱,很容易让人搞混。如果从一个最简单的单向队列角度来看的话,我们可以把Queue中的enqueue方法对应到addLast方法,因为我们每次添加元素就是在队尾增加。deque方法则对应到removeFirst方法。虽然也可以用其他的方法来实现,不过具体的实现细节和他们基本上是一样的。

LinkedList

现在,我们在来看看LinkedList对应Queue的实现部分。在前面一篇文章中,已经讨论过LinkedList里面Node的结构。它本身包含元素值,prev、next两个引用。对链表的增加和删除元素的操作不像数组,不存在要考虑下标的问题,也不需要扩展数组空间,因此就简单了很多。先看查找元素部分:

Java代码  

public E getFirst() {

final Node f = first;

if (f == null)

throw new NoSuchElementException();

return f.item;

}

public E getLast() {

final Node l = last;

if (l == null)

throw new NoSuchElementException();

return l.item;

}

这里唯一值得注意的一点就是last引用是指向队列最末尾和元素,和前面ArrayDeque的情况不一样。

添加元素的方法如下:

Java代码  

public boolean add(E e) {

linkLast(e);

return true;

}

public boolean offer(E e) {

return add(e);

}

void linkLast(E e) {

final Node l = last;

final Node newNode = new Node<>(l, e, null);

last = newNode;

if (l == null)

first = newNode;

else

l.next = newNode;

size++;

modCount++;

}

linkLast的方法在前一篇文章里已经分析过,就不再重复。

删除元素的方法主要有remove():

Java代码  

public boolean remove(Object o) {

if (o == null) {

for (Node x = first; x != null; x = x.next) {

if (x.item == null) {

unlink(x);

return true;

}

}

} else {

for (Node x = first; x != null; x = x.next) {

if (o.equals(x.item)) {

unlink(x);

return true;

}

}

}

return false;

}

E unlink(Node x) {

// assert x != null;

final E element = x.item;

final Node next = x.next;

final Node prev = x.prev;

if (prev == null) {

first = next;

} else {

prev.next = next;

x.prev = null;

}

if (next == null) {

last = prev;

} else {

next.prev = prev;

x.next = null;

}

x.item = null;

size--;

modCount++;

return element;

}

这部分的代码看似比较长,实际上是遍历整个链表,如果找到要删除的元素,则移除该元素。这部分的难点在unlink方法里面。我们分别用要删除元素的前面和后面的引用来判断各种当prev和next为null时的各种情况。虽然不是很复杂,但是很繁琐。

总结

从我们实际中的考量来看,Queue和Deque他们本身不仅定义了作为一个队列需要的基本功能。同时因为队列也是属于整个集合类这一个大族里面的,所以他们也必须要具备集合类的一些常用功能,比如元素查找,删除,迭代器等。我们读一些集合类的代码时,尤其是一些接口的定义,会发现一个比较有意思的事情。就是通常一些子接口把父接口的方法又重新定义了一遍。这样似乎违背了面向对象里继承的原则。后来经过一些讨论,发现主要原因是一些jdk版本的更新,有的新类是后面新增加的。这些新的接口有的是为了保持兼容,有的是为了保证后续生成文档里方便用户知道它也有同样的功能而不需要再去查它的父类,就直接把父类的东西给搬过来了。比较有意思,读代码还读出点历史感了。

queue java 判断重复值_java集合类深入分析之Queue篇(Q,DQ)相关推荐

  1. java 判断object类型_Java学习-方法与多态的学习心得

    一 1.什么是方法重写 方法的重写或方法的覆盖(overriding) 子类根据需求对从父类继承的方法进行重新编写 重写时,可以用super.方法的方式来保留父类的方法 构造方法不能被重写 2.方法重 ...

  2. java 判断 中文字符_java中判断字符串中是否有中文字符

    package com.meritit.test; public class TestChart { public static void main(String[] args) throws Exc ...

  3. java判断list相等_java 判断两个list是否相等

    /** * 队列比较 * @param * @param a * @param b * @return */ public static > boolean compare(List a, Li ...

  4. java判断long相等_java判断long类型字符是否相等的方法

    java判断long类型字符是否相等的方法 发布时间:2020-04-30 14:00:27 来源:亿速云 阅读:158 作者:小新 java判断long类型字符是否相等的方法?相信有很多人都不太了解 ...

  5. java判断字符长度_java判断中文字符串长度的简单实例

    话不多说,上代码: /** * 获取字符串的长度,如果有中文,则每个中文字符计为2位 * @param value 指定的字符串 * @return 字符串的长度 */ public static i ...

  6. java判断总共天数_Java判断两个日期相差天数的方法

    本文实例讲述了Java判断两个日期相差天数的方法.分享给大家供大家参考.具体如下: import java.util.Calendar; public class DateDifferent{ pub ...

  7. java 接口 返回值_java api返回值的标准化详解

    api返回值的标准化 例如 {"status":200,"message":"操作成功","data":"{\ ...

  8. java 判断图片格式_Java判断上传图片格式的实例代码

    先给大家介绍下java判断上传图片格式. 由于客户上传图片将png的图片的后缀名改为jpg,所以通过后缀名判断不行,用下面这个方法可以 //判断是否是JPG格式 log.info("-1-- ...

  9. java判断数字大小写_java判断字符串是否全部由数字,大小写字母,特殊符号组成...

    直接上代码,经过验证. package javaTest; import java.util.regex.Matcher; import java.util.regex.Pattern; public ...

最新文章

  1. GitHub开源的ImageAI 库:几行代码可实现目标对象识别
  2. Javascript - prototype、__proto__、constructor
  3. AngularJS的稍复杂form验证
  4. 计算机安全模型研究与应用,软件哨兵安全动态检测模型的研究与实现-计算机应用研究.PDF...
  5. 一堆数据中将某一个值相同的数据进行分组
  6. 罗永浩开了一家直播界的新东方
  7. 新年发财专属红包封面,高端大气上档次!
  8. Servlet学习笔记(四)之请求转发与重定向(RequestDispatcher与sendRedirect)
  9. centos7 go yum 安装_Centos7.6从头开始安装nvidiadocker
  10. java 环境变量配置
  11. Ubuntu Kvm USB重定向问题解决
  12. 【电力电子】【2019.12】工业用三相全SIC PWM整流器
  13. java 转换tif图片为jpg,解决转换后颜色异常问题
  14. An Analysis of Scale Invariance in Object Detection – SNIP
  15. 使用JAVA将m3u8转换为mp4格式
  16. 51单片机(五)独立键盘检测与矩阵键盘检测
  17. ansible 配置使用大全资料
  18. MySQL完美卸载-奇怪的小知识
  19. G-S稳定匹配算法详解
  20. 职业经理人七项修炼-转自栖息谷

热门文章

  1. IE浏览器里无法运行脚本(script)常见的解决方法!
  2. 最大子段和问题分析和总结
  3. 百度地图报错:APP Referer校验失败
  4. nginx只允许域名访问,禁止ip访问
  5. Office 365系列之四:添加自定义域
  6. Linux(RHEL7.0)下安装nginx-1.10.2
  7. Weex 版扫雷游戏开发
  8. 快速搭建samba服务
  9. 2014年Q1中国.BIZ域名总量增1.5万 注册情况稳定
  10. 玻璃质感_他的玻璃质感让人佩服的五体投地