发布时间:2023-02-25 14:30
栈结构类型,表示对象的后进先出堆栈。Stack
继承自 Vector
,并拓展了五个允许将容器视为栈结构的操作。
包括常见的 pop
和 push
操作、以及查看栈顶元素的方法、检查栈是否为空的方法以及从栈顶向下进行搜索某个元素,并获取该元素在栈内深度的方法。
Deque 接口及其实现提供了一组更完整的 LIFO 堆栈操作能力,应该优先考虑使用 Deque 及其实现。例如:
Dequestack = new ArrayDeque ();
Queue
接口定义了队列的能力,它继承自 Collection
,除了基本的集合操作外,队列提供了额外的插入、获取、检查等操作。
public interface Queueextends Collection { // 在不违反容量限制的情况下立即将指定元素插入此队列,成功时返回 true,如果当前没有可用空间则抛出 IllegalStateException。 boolean add(E e); // 在不违反容量限制的情况下立即将指定元素插入此队列。 在使用容量受限的队列中,一般最好使用这种方法添加,仅通过抛出异常可能无法插入元素。 boolean offer(E e); // 检索并删除此队列的头部。 此方法与 poll 的不同之处仅在于如果此队列为空,它将引发异常。 E remove(); // 检索并移除此队列的头部,如果此队列为空,则返回 null。 E poll(); // 检索但不删除此队列的头部。 此方法与 peek 的不同之处仅在于如果此队列为空,它将引发异常。 E element(); // 检索但不删除此队列的头部,如果此队列为空,则返回 null。 E peek(); }
可以看出,每一种操作都存在两种定义,一种是在操作失败的情况下强制抛出异常的,另一种则是返回 null
不强制抛出异常的。
Throws exception | Returns special value | |
---|---|---|
Insert | add(e) | offer(e) |
Remove | remove() | poll() |
Examine | element() | peek() |
队列通常是先进先出的,所以对元素的排序方式也是以先进先出的顺序的,但是有特殊情况,例如,优先级队列是根据比较元素的优先级进行排序的;另一种例子是后进先出队列,即栈结构。
无论使用哪种排序,队列的头部元素都是通过调用 remove
或 poll
删除的。在 FIFO 队列中,所有的新元素都是插入到队列的尾部的,其他类型的队列可能使用不同的插入规则,每个 Queue
的实现都必须明确指定其排序方式。
队列的另一个特性是不允许插入 null
,尽管在某些实现中,例如 LinkedList
,允许存储 null
。但在这种实现中,也不应该将 null
插入到队列中,因为 null 被 poll
方法作为特殊返回值,以表明队列不包含任何元素。
队列的实现通常不定义 hashCode
和 equals
,因为对于具有相同的元素,但具有不同的排序方式的队列,基于元素的相等并不是很好的定义方式。
Deque
继承自 Queue
,是一种支持两端元素插入和移除的顺序结构集合,简称 “双端队列” 。大多数双端队列对容量没有固定限制。
Deque
接口主要拓展了对于双端队列两端元素操作的方法,也是两种形式,一种抛出异常,一种返回特殊值 null 的形式。
Deque
可以当作 FIFO 队列使用,所以它的一些拓展的方法等效于 Queue
的一些方法:
Deque
作为 LIFO 队列使用时(栈结构),它也会有一些等价于 Stack
的方法:
与 List
接口不同,此接口不支持对元素的索引访问。
另外虽然没有严格要求 Deque
的实现禁止插入 null
元素,但在使用中的确应该尽量避免插入 null
,因为 null
元素被各种操作方法作为特殊的返回值,这里和 Queue
一样。
另一个与 Queue
相同的是,它也没有定义需要实现 hash
和 equals
方法,理由和 Queue
是一样的。
BlockingQueue
是 Queue
接口的另一个重要子接口,它用来代表一个可阻塞的队列。支持在检索元素是,等待队列变为非空再返回数据。
BlockingQueue
有四种类型,用来解决当前不能立刻处理,需要再未来某个时间点下满足指定条件才执行的操作。
以下这张表是同一个行为在不同类型的方法表:
BlockingQueue
不接受空元素。在尝试添加、放置或提供 null
时,实现会抛出 NullPointerException
。 null
用作标记值以指示轮询操作失败。Collection
接口。BlockingQueue
的操作,会发生在下一个其他线程对 BlockingQueue
中的该元素读取或移除之前。PriorityQueue
是一个基于优先级堆的无界限优先级队列。它是 Queue
的直接实现类,优先级队列的元素排序有两种情况:
Comparator
进行比较后排序排序方式取决于具体的构造函数的参数 Comparator
。
public PriorityQueue(Comparator super E> comparator) { this(DEFAULT_INITIAL_CAPACITY, comparator); }
优先级队列不允许 null
元素,依赖于自然排序的优先级队列也不允许插入不可比较的对象,否则会导致 ClassCastException
。
底层实现是通过数组来保存数据的:
transient Object[] queue;
不是线程安全的。
优先级队列的容量是没有限制的,但是内部存储元素的结构实际上是一个数组,它的扩容机制是有规律的。
初始化默认容量 11 ,后续扩容总是扩容到与队列元素数量相同大小,这一点和 ArrayList
基本一致。
public boolean offer(E e) { if (e == null) throw new NullPointerException(); // 不允许 null 元素 modCount++; int i = size; if (i >= queue.length) grow(i + 1); // 真正的扩容方法 size = i + 1; if (i == 0) queue[0] = e; else siftUp(i, e); return true; }
private void grow(int minCapacity) { int oldCapacity = queue.length; // Double size if small; else grow by 50% 默认扩容量 int newCapacity = oldCapacity + ((oldCapacity < 64) ? (oldCapacity + 2) : (oldCapacity >> 1)); // overflow-conscious code if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); queue = Arrays.copyOf(queue, newCapacity); }
hugeCapacity()
计算容量:private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
MAX_ARRAY_SIZE
。数组双端队列,是一种可调整大小的数组实现,没有容量限制。它会根据需要自动扩容。
public class ArrayDequeextends AbstractCollection implements Deque , Cloneable, Serializable
AbstractCollection
:提供了一些集合接口能力的基本封装。Object.clone()
方法快速复制底层数据结构是一个数组:
transient Object[] elements; transient int head; transient int tail;
通过 head
和 tail
作为索引来支持双端队列的特性。
public void addFirst(E e) { if (e == null) throw new NullPointerException(); // 不允许 null elements[head = (head - 1) & (elements.length - 1)] = e; // 防止下标越界处理 if (head == tail) // 检查空间是否够用, == 说明空间不够了 doubleCapacity(); // 扩容 }
这里先插入,后扩容。tail
总是指向下一个可插入的空位,这个意思就是 数组中至少留有一个空位置,所以元素可以先插入。
head = (head - 1) & (elements.length - 1)
这行代码比较难以理解。语义是:取余操作,同时解决了head
为负值的情况。 elements.length
必定是 2 的指数倍。 elements.length - 1
的二进制低位必为 1 , 与head - 1
进行与操作后,起到了取模的作用(取余数)。如果 head - 1
为负数(只可能是 -1),则相当于对其取相对于 elements.length
的补码。
真正的扩容机制在 doubleCapacity()
:
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]; System.arraycopy(elements, p, a, 0, r); System.arraycopy(elements, 0, a, r, p); elements = a; head = 0; tail = n; }
这个函数的作用相当于扩容 100% 后,将原数组,分段复制进去:
首先复制 head
右侧的元素,然后再把左边的复制过来。即虽然是往队列头部插值,但实际还是在尾部插完值后,分段移动进行排序,最后组成了新数组。
addLast
则是直接把新元素存入tail
位置上,然后在重新计算 tail
,检查是否需要扩容。
public void addLast(E e) { if (e == null) throw new NullPointerException(); elements[tail] = e; //赋值 if ( (tail = (tail + 1) & (elements.length - 1)) == head) //下标越界处理 doubleCapacity(); //扩容 }
特点:
null
元素。Stack
快;作为队列使用时, 比 LinkedList
快。常见的队列除了 ArrayQueue
、PriorityQuueue
和 BlockingQueue
,还有一个可以当作队列的 LinkedList
,关于它在上一节的 List 体系中有详细的讲解。
ArrayQueue
,更适用于当作双端队列或栈结构。Stack
已不推荐使用,建议使用 LinkedList
或 ArrayQueue
替代。PriorityQueue
用来处理优先级,多了一个可自定义优先级条件的能力。BlockingQueue
常用于实现 生产者/消费者队列,但线程安全需要外部自己去实现。LinkedList
,底层的数据结构都是数组。Queue
的有些实现没有强制要求不允许存 null
,但最好不要存 null
。到此这篇关于Java 集合框架 Queue 和 Stack 体系的文章就介绍到这了,更多相关Java 集合框架 内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!