说明:

一 队列:

队列是一种特殊的线性表,它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列。
这与我们生活中的队列是一致的,前面消费,后边插入

二 双端队列:

双端队列,顾名思义,两端都能操作(操作一下吗?),两端都可以进行插入和删除的操作,即:即可在表的前端,进行插入和删除操作,又可以在表后端进行插入和删除操作

三 栗子与应用

在OkHttp3.4.1的源码中,看到了如下的应用

public final class Dispatcher {
  private int maxRequests = 64;
  private int maxRequestsPerHost = 5;

  /** Executes calls. Created lazily. */
  private ExecutorService executorService;

  /** Ready async calls in the order they'll be run. */
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

  /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

  /** Running synchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

  public Dispatcher(ExecutorService executorService) {
    this.executorService = executorService;
  }

可见,OkHttp,用ArrayDeque 作为了请求队列,下面,我们来介绍ArrayDeque

1 继承关系
public class ArrayDeque<E> extends AbstractCollection<E> implements Deque<E>, Cloneable, Serializable

实现了Deque、Cloneable和Serializable接口。实现Cloneable和Serializable接口的主要目的是为了实现克隆和序列化,
继承了AbstractCollection 抽象类

2 成员变量:

ArrayDeque 类中,定义了4个成员变量,代码如下:

/**
     * The array in which the elements of the deque are stored.
     * The capacity of the deque is the length of this array, which is
     * always a power of two. The array is never allowed to become
     * full, except transiently within an addX method where it is
     * resized (see doubleCapacity) immediately upon becoming full,
     * thus avoiding head and tail wrapping around to equal each
     * other.  We also guarantee that all array cells not holding
     * deque elements are always null.
     */
    transient Object[] elements; // non-private to simplify nested class access

    /**
     * The index of the element at the head of the deque (which is the
     * element that would be removed by remove() or pop()); or an
     * arbitrary number equal to tail if the deque is empty.
     */
    transient int head;

    /**
     * The index at which the next element would be added to the tail
     * of the deque (via addLast(E), add(E), or push(E)).
     */
    transient int tail;

    /**
     * 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;
  • Object[] elements;
存储deque元素的数组。
deque的容量是这个数组的长度,它总是2的幂。
该数组永远不会变为满,除了在addX方法中暂时调整大小(请参阅doubleCapacity),当它变为满时,
从而避免头部和尾部缠绕在彼此相等。
我们也保证所有不带deque元素的数组单元都是空的。

简言之: Object[] elements 存储元素的 数组
  • transient int head;
在队列head 处的元素的index(索引),即,接下来调用remove() 或 pop()方法来移除的元素的索引,
如果,对列为空,这个值 = tail的值
  • transient int tail;
接下来,调用addlast() 或者 add()方法,加入队尾的元素的index(索引)
  • private static final int MIN_INITIAL_CAPACITY = 8;
我们将用于新创建的deque的最小容量。 必须是2的力量。

总结:
其中elements是元素集合数组,head是指向队首的下标,tail是指向队尾的下一个位置的下标。MIN_INITIAL_CAPACITY定义了在创建队列是,如果有指定长度,而且指定长度小于MIN_INITIAL_CAPACITY,则使用MIN_INITIAL_CAPACITY作为数组的最小长度。

3 构造函数:

构造函数有3,

  • 1 无参构造:
public ArrayDeque() {
        elements = new Object[16];
    }

默认元素,数组大小 16

  • 2 指定大小的构造:
public ArrayDeque(int numElements) {
        allocateElements(numElements);
    }

调用了allocateElements(numElements)方法,

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 < 0)    // Too many elements, must back off
                initialCapacity >>>= 1; // Good luck allocating 2^30 elements
        }
        elements = new Object[initialCapacity];
    }

制定了:元素数组的大小,如果传入的大小,小于MIN_INITIAL_CAPACITY,则使用MIN_INITIAL_CAPACITY作为数组的最小长度。

  • 2 指定元素构造:
public ArrayDeque(Collection<? extends E> c) {
        allocateElements(c.size());
        addAll(c);
    }

传入一个元素集合,这个构造内部完成了:构造Object[] 数组,和填充元素的操作

4 常用方法:

这里要从Queue接口开始写起:

5 . Queue

在java5中新增加了Java.util.Queue接口,用以支持队列的常见操作。该接口扩展了java.util.Collection接口。

public interface Queue<E> extends Collection<E> {
      //...
}

所以除了基本的Collection操作,队列还支持:插入,提取和检查操作

  • 类结构

队列(二):Deque_接口

队列(二):Deque_队列_02

:抛异常:

:返回特殊值:

插入

add(e)

offer(e)

移除

remove()

pool()

检查

element()

peek()

队列通常(但并非一定)以 FIFO(first in first out 先进先出)的方式排序各个元素。不过优先级队列和 LIFO(last in first out 后进先出) 队列(或堆栈)例外,优先级队列根据提供的比较器或元素的自然顺序对元素进行排序,LIFO队列按 LIFO的方式对元素进行排序。

在 FIFO 队列中,所有的新元素都插入队列的末尾,移除元素从队列头部移除。

Queue使用时要尽量避免Collectionadd()remove()方法,而是要使用offer()来加入元素,使用poll()来获取并移出元素。它们的优点是通过返回值可以判断成功与否,add()和remove()方法在失败的时候会抛出异常如果要使用前端而不移出该元素,使用element()或者peek()方法。

返回值

方法名

说明

增加

boolean

add(E e)

是Collection接口里的方法,将指定元素插入此队列的tail(如果立即可执行,且不会违反容量限制),在成功时返回true,如果当前没有可用空间,则抛出IllegalStateException

boolean

offer(E e)

是Queue接口里的方法,将指定元素插入此队列的tail(如果立即可执行,且不会违反容量限制),返回true,否则返回false, 当使用有容量限制的队列时,此方法通常要优于add(E),add(e)方法可能无法插入,抛出异常,

检查

E

element()

是Queue接口里的方法,返回head(队头),处的元素,但不移除此元素,如果队列empty,则抛出异常

E

peek()

是Queue接口里的方法,返回head(队头),处的元素,但不移除此元素,如果队列empty,则返回null

移除

E

remove()

是Queue接口里的方法,(注意,Collection里的remove(E e )方法,是有参的),获取并移除此队列的head,如果队列empty,则抛出异常NoSuchElementException

E

poll()

是Queue接口里的方法,(注意,Collection里的remove(E e )方法,是有参的),获取并移除此队列的head,如果队列empty,则返回null

说明:

offer 方法可插入一个元素,否则返回 false。这与 Collection.add 方法不同,该方法只能通过抛出未经检查的异常使添加元素失败。

remove() 和 poll() 方法可移除和返回队列的头。到底从队列中移除哪个元素是队列排序策略的功能,而该策略在各种实现中是不同的。remove() 和 poll() 方法仅在队列为空时其行为有所不同:remove() 方法抛出一个异常,而 poll() 方法则返回 null。

element() 和 peek() 返回,但不移除,队列的头。

Queue 实现通常不允许插入 null 元素,尽管某些实现(如 LinkedList)并不禁止插入 null。即使在允许 null 的实现中,也不应该将 null 插入到 Queue 中,因为 null 也用作 poll 方法的一个特殊返回值,表明队列不包含元素。

值得注意的是LinkedList类实现了Queue接口,因此我们可以把LinkedList当成Queue来用。

private void testDeque() {

        Deque<String> tmpDeque = new LinkedList<>();
        tmpDeque.offer("Hello");
        tmpDeque.offer("World");
        tmpDeque.offer("你好!");
        Log.d(TAG, "tmpDeque.size():" + tmpDeque.size());
        String str = null;
        while ((str = tmpDeque.poll()) != null) {
            Log.d(TAG, "str : " + str);
        }
        Log.d(TAG, "tmpDeque.size():" + tmpDeque.size());
    }
D/DequeActivity: tmpDeque.size():3
 D/DequeActivity: str : Hello
 D/DequeActivity: str : World
 D/DequeActivity: str : 你好!
 D/DequeActivity: tmpDeque.size():0

6 . Deque

public interface Deque<E> extends Queue<E> {
          //....
}

Deque 接口继承了Queue接口,所以支持Queue 的所有操作,间接也支持Collection的所以操作

类节构:

队列(二):Deque_队列_03

Deque是一个线性 collection,支持在两端插入和移除元素。
名称 deque 是“double ended queue(双端队列)”的缩写,通常读为“deck”。

大多数 Deque 实现对于它们能够包含的元素数没有固定限制,但此接口既支持有容量限制的双端队列,也支持没有固定大小限制的双端队列。

队列(二):Deque_queue_04

此接口定义在双端队列两端访问元素的方法。提供插入、移除和检查元素的方法。因为此接口继承了队列接口Queue,所以其每种方法也存在两种形式:一种形式在操作失败时抛出异常,另一种形式返回一个特殊值(null 或 false,具体取决于操作)。

  • 将Deque 用作单纯的队列(FIFO,尾进头出,先进先出)

在将双端队列用作队列时,将得到 FIFO(先进先出)行为。将元素添加到双端队列的末尾,从双端队列的开头移除元素。从 Queue 接口继承的方法完全等效于 Deque 方法,如下表所示:

Queue的方法

等效Deque的方法

描述

add(E e)

addLast(E e)

添加至tail,失败,则抛异常

offer()

offerLast()

添加至tail,失败,则返回false

remove()

removeFirst()

移除head,失败,则抛异常

poll()

pollFirst()

移除head,失败,返回null

element()

getFirst()

检查head,不移除,失败,则抛异常

peek()

peekFirst()

检查head,不移除,失败,则返回null

队列(二):Deque_java_05

  • 将Deque 用作栈(LIFO,头进头出,后进先出,比如activity的栈)

在将双端队列用作 LIFO(后进先出)堆栈。应优先使用此接口而不是遗留 Stack 类。在将双端队列用作堆栈时,元素被推入双端队列的开头并从双端队列开头弹出。堆栈方法完全等效于 Deque 方法,如下表所示:

堆栈方法

等效Deque的方法

描述

push(e)

addFirst(e)

添加至头部,失败,则抛异常

offerFirst(e)

添加至头部,失败,则返回false

pop()

removeFirst()

移除头部,失败,则抛异

pollFirst()

移除头部,失败,则返回false

peek()

getFirst()

检查头部,失败,则抛异常

peekFirst()

检查头部,失败,则返回null

队列(二):Deque_接口_06