JavaSE Queue集合
目录
Queue 集合是什么?有什么用?
Queue 集合用于模拟数据结构“队列”。
Queue 集合中有一个 PriorityQueue 实现类,用于模拟“优先队列”。
Queue 集合中还有一个 Deque 接口,用于模拟“双端队列”。
Queue 集合怎么用?
1 Queue 集合的继承图(常用部分)
2 Queue 接口
2-1 常用方法
Queue 接口是 Collection 接口的子接口,模拟了“队列”这个数据结构。
public interface Queue<E> extends Collection<E> {
// 作为队列使用
//将元素加入队尾
boolean offer(E e);
//删除并返回队头元素。如果队列为空,返回 null
E poll();
//获取队头元素,但不删除。如果队列为空,返回 null
E peek();
}
3 Deque 接口
3-1 常用方法
Deque 接口是 Queue 接口的子接口,模拟了“双端队列”这个数据结构。
public interface Deque<E> extends Queue<E> {
...
//1 作为双端队列使用
//在队头、队尾插入元素
boolean offerFirst(E e);
boolean offerLast(E e);
//删除并返回队头、队尾元素。如果队列为空,返回 null
E pollFirst();
E pollLast();
//返回队头、队尾元素,但不删除。如果队列为空,返回 null
E peekFirst();
E peekLast();
//2 作为栈使用
//入栈
void push(E e);
//出栈
E pop();
//查看栈顶元素
E peek();
...
}
4 ArrayDeque 类
4-1 介绍
(1)ArrayDeque 类实现了 Deque 接口,是一个基于循环数组实现的双端队列。
(2)ArrayDeque 不能添加 null。
(3)ArrayDeque 的容量总是 2^n。
4-2 底层结构
ArrayDeque 底层是一个 Object[] 数组,是一个循环数组,维护队头索引 head,队尾索引 tail。
public class ArrayDeque<E> ...{
...
transient Object[] elements;
transient int head;
transient int tail;
...
}
4-3 扩容机制
(1)使用无参构造器初始化一个 ArrayDeque 对象时,数组长度为 16。
(2)使用有参构造器初始化一个 ArrayDeque 对象时,自定义容量 numElements;如果 numElements < 8,则数组长度为 8;如果 numElements >= 8,则数组长度比 numElements 大(严格大),并且是最近的 2^n(16、32、64…)。
(3)当数组占满后,扩容为原来的 2 倍。
public class ArrayDeque<E> ...{
...
transient Object[] elements;
transient int head;
transient int tail;
private static final int MIN_INITIAL_CAPACITY = 8;
//无参构造器,初始化一个长度为 16 的 Object[] 数组
public ArrayDeque() {
elements = new Object[16];
}
//有参构造器,传入自定义容量
//如果 numElements < 8,则初始化数组容量为 8
//如果 numElements >= 8,则初始化数组容量比 numElements 大(严格大),并且是 2^n
public ArrayDeque(int numElements) {
allocateElements(numElements);
}
private void allocateElements(int numElements) {
elements = new Object[calculateSize(numElements)];
}
//如果 numElements < 8,则返回 8
//如果 numElements >= 8,则返回一个比 numElements 大的数,并且这个数是 2^n
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;
}
// 添加元素
// ArrayDeque 不能添加 null
public void addLast(E e) {
// 不能添加 null
if (e == null)
throw new NullPointerException();
elements[tail] = e;
// 因为 elements.length = 2^n, 所以 elements.length - 1 低位全是 1,高位全是 0;
// 所以 tail & elements.length - 1,相当于 tail % elements.length
if ( (tail = (tail + 1) & (elements.length - 1)) == head)
doubleCapacity();
}
// 当数组占满后,扩容为原来的 2 倍
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;
}
...
}
4-4 常用方法
public class ArrayDeque<E> ...{
// 1 构造器
// 无参构造器,初始容量为 16
public ArrayDeque() {...}
// 有参构造器,自定义容量,最终容量大于设定值,并且是 2^n
public ArrayDeque(int numElements) {...}
// 复制集合元素
public ArrayDeque(Collection<? extends E> c) {...}
// 2 作为双端队列
//在队头、队尾插入元素
boolean offerFirst(E e);
boolean offerLast(E e);
//删除并返回队头、队尾元素。如果队列为空,返回 null
E pollFirst();
E pollLast();
//返回队头、队尾元素,但不删除。如果队列为空,返回 null
E peekFirst();
E peekLast();
// 3 作为栈
//入栈
void push(E e);
//出栈
E pop();
//查看栈顶元素
E peek();
}
5 LinkedList 类
5-1 介绍
该类底层是双向链表。
该类实现了 List 接口,可以根据索引来随机访问集合中的元素(效率低)。
该类实现了 Deque 接口,可以被当成双端队列、栈来使用(不推荐)。
该类可以添加 null,元素可以重复。
该类线程不安全。
4-2 底层结构
LinkedList 底层是双向链表。
维护着指向头节点的 first 变量,指向尾节点的 last 变量。
节点类型为 Node 类,是 LinkedList 的内部类。
public class LinkedList<E> ...{
...
//指向头节点,初始为Null
transient Node<E> first;
//指向尾节点,初始为Null
transient Node<E> last;
//在链表的末尾添加元素
public boolean add(E e) {
linkLast(e);
return true;
}
//在链表的末尾添加元素
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
// 内部类 Node,作为链表节点的数据类型
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
...
}
5-3 常用方法
public class LinkedList<E> ...{
// 1 构造器
// 无参构造器
public LinkedList() {}
// 有参构造器,复制元素
public LinkedList(Collection<? extends E> c) {...}
// 2 作为链表
// 添加元素
boolean add(E e);
// 删除第一个符合条件的元素
boolean remove(Object o);
// 3 作为双端队列(不推荐)
//在队头、队尾添加节点,有返回值
public boolean offerFirst(E e) {...}
public boolean offerLast(E e) {...}
//删除并返回队头、队尾元素
public E pollFirst() {...}
public E pollLast() {...}
//返回队头、队尾元素,但不删除
public E peekFirst() {...}
public E peekLast() {...}
// 4 作为栈(不推荐)
//入栈
public void push(E e) {...}
//出栈
public E pop() {...}
//查看栈顶元素
public E peek() {...}
}
6 PriorityQueue 类
6-1 介绍
(1)PriorityQueue 类实现了 Queue 接口,模拟了“优先队列”这个数据结构。
(2)其本质是小顶堆,通过数组实现该结构。
(3)不允许插入 null。
(4)非线程安全。
(5)需要传入 Comparator,或者保存的类对象实现了 Comparable。
6-2 底层实现
PriorityQueue 类的底层使用 Object[] 数组存储数据。
用 Object[] 数组实现了完全二叉树,通过 Comparable 或者 Comparator 接口进行元素大小的比较。
public class PriorityQueue<E>...{
...
transient Object[] queue;
private final Comparator<? super E> comparator;
// 添加元素
// 不允许插入 null
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
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 siftUp(int k, E x) {
if (comparator != null)
siftUpUsingComparator(k, x);
else
siftUpComparable(k, x);
}
// 使用 Comparator 进行元素比较,插入到完全二叉树的相应位置。
private void siftUpUsingComparator(int k, E x) {
while (k > 0) {
int parent = (k - 1) >>> 1;
Object e = queue[parent];
if (comparator.compare(x, (E) e) >= 0)
break;
queue[k] = e;
k = parent;
}
queue[k] = x;
}
// 使用 Comparator 进行元素比较,插入到完全二叉树的相应位置。
private void siftUpComparable(int k, E x) {
Comparable<? super E> key = (Comparable<? super E>) x;
while (k > 0) {
int parent = (k - 1) >>> 1;
Object e = queue[parent];
if (key.compareTo((E) e) >= 0)
break;
queue[k] = e;
k = parent;
}
queue[k] = key;
}
...
}
6-3 扩容机制
(1)调用无参构造器初始化时,默认容量为 11;
(2)调用有参构造器初始化时,容量自定义,但是必须 >=2;
(3)当数组占满后,进行扩容;如果原来的容量 <64,则扩容为 (2 倍 + 2);如果原来的容量 >=64,则扩容为 1.5 倍。
public class PriorityQueue<E>...{
...
private static final int DEFAULT_INITIAL_CAPACITY = 11;
transient Object[] queue;
private final Comparator<? super E> comparator;
// 无参构造器,调用有参构造器
// 默认容量为 11
public PriorityQueue() {
this(DEFAULT_INITIAL_CAPACITY, null);
}
// 有参构造器
// 自定义容量,容量 >= 2
public PriorityQueue(int initialCapacity, Comparator<? super E> comparator) {
if (initialCapacity < 1)
throw new IllegalArgumentException();
this.queue = new Object[initialCapacity];
this.comparator = comparator;
}
// 添加元素
// 不允许插入 null
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
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;
}
// 扩容
// 如果原来的容量 <64,则扩容为 (2 倍 + 2);
// 如果原来的容量 >=64,则扩容为 1.5 倍;
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);
}
...
}
6-4 常用方法
public class PriorityQueue<E>...{
// 1 构造器
// 无参构造器,默认容量为 11。元素需要实现 Comparable 接口。
public PriorityQueue() {...}
// 有参构造器,自定义容量。元素需要实现 Comparable 接口。
public PriorityQueue(int initialCapacity) {...}
// 有参构造器,传入比较器,默认容量为 11。使用该比较器进行排序。
public PriorityQueue(Comparator<? super E> comparator) {...}
// 有参构造器,自定义容量,传入比较器。
public PriorityQueue(int initialCapacity,Comparator<? super E> comparator) {...}
// 有参构造器,复制集合。
public PriorityQueue(Collection<? extends E> c) {...}
// 2 作为队列(优先队列)
//将元素加入队尾
boolean offer(E e);
//删除并返回队头元素。如果队列为空,返回 null
E poll();
//获取队头元素,但不删除。如果队列为空,返回 null
E peek();
}
6-5 代码示例
- 建立小顶堆
PriorityQueue<String> queue = new PriorityQueue<>((s1, s2) -> {
return s1.length() - s2.length();
});
queue.offer("abc");
queue.offer("ab");
queue.offer("abcd");
queue.offer("a");
while(!queue.isEmpty()){
System.out.println(queue.poll()); // a ab abc abcd
}
- 建立大顶堆
PriorityQueue<String> queue = new PriorityQueue<>((s1, s2) -> {
return s2.length() - s1.length();
});
queue.offer("abc");
queue.offer("ab");
queue.offer("abcd");
queue.offer("a");
while(!queue.isEmpty()){
System.out.println(queue.poll()); // abcd abc ab a
}
7 不同 Queue 集合的使用场景
ArrayDeque:底层是循环数组,增删改查效率较高。可以作为双端队列、栈来使用。推荐使用。
LinkedList:底层是双向链表,增删效率高,改查效率低。可以作为双端队列、栈来使用。
PriorityQueue:底层是数组实现的小顶堆,作为优先队列使用。