Collection 接口
Collection
接口提供了一系列用于操作和管理集合的方法,包括添加、删除、查询、遍历等。它是所有集合类的根接口,包括 List
、Set
、Queue
等。
Collection 接口常见方法
-
add(E element)
:向集合中添加元素。 -
addAll(Collection col)
:将 col 中的所有元素添加到集合中 -
boolean remove(Object obj)
:通过元素的equals方法判断是否是要删除的那个元素,只删除找到的第一个元素 -
boolean removeAll(Collection col)
:取两集合差集 -
boolean retain(Collection col)
:把交集的结果存在当前的集合中,不影响col -
boolean contains(Object obj)
:判断集合中是否包含指定的元素。 -
boolean containsAll(Collection col)
:调用元素的equals方法来比较的。用两个两个集合的元素逐一比较 -
size()
:返回集合中的元素个数。 -
isEmpty()
:判断集合是否为空。 -
clear()
:清空集合中的所有元素。 -
iterator()
:返回用于遍历集合的迭代器。 -
hashCode()
: 获取集合对象的哈希值 -
Object[] toArray()
:转换成对象数组
Queue 接口
队列(Queue)是一种常见的数据结构,它遵循 先进先出 FIFO 的原则。Java提供了一个 Queue
接口,它是 Collection 接口的子接口,定义了一些特定于队列数据结构的方法。
Queue 接口的实现一般会提供不同的方法来处理容量限制、是否插入成功等问题,具体的使用可以根据实现类的特点来选择适合的方法。
Queue 常用方法
以下是 Queue
接口中常用的一些方法:
-
add(E element)
:将指定的元素添加到队列的尾部。如果队列已满,则抛出一个异常。 -
offer(E element)
:将指定的元素添加到队列的尾部。如果队列已满,则返回false,否则返回true。 -
remove()
:移除并返回队列头部的元素。如果队列为空,则抛出一个异常。 -
poll()
:移除并返回队列头部的元素。如果队列为空,则返回null。 -
element()
:返回队列头部的元素,但不移除。如果队列为空,则抛出一个异常。 -
peek()
:返回队列头部的元素,但不移除。如果队列为空,则返回null。
Queue 实现类
Queue
接口有几种常见的实现类,包括 ArrayQueue
(自定义实现)、LinkedList
、ArrayDeque
、PriorityQueue
等。
-
ArrayQueue:基于数组实现的队列,访问和移除队首元素较快,但可能需要在队列满或半空时进行数组的复制操作。
-
LinkedListQueue:基于双向链表实现的队列,添加和移除元素非常快,因为只需要更新指针即可。
-
PriorityQueue:基于小顶堆实现的队列,队列中的元素会自动排序,最小的元素位于队首。适合需要优先级排序的场景。
-
ArrayDeque:基于数组实现的双端队列,可以作为队列或栈使用。两端的插入和移除操作都非常快,因为只需要更新指针即可。
根据具体的应用场景选择合适的队列类型非常重要。例如:
-
如果需要优先级排序的队列,可以选择
PriorityQueue
-
如果需要高效的两端操作,可以选择
ArrayDeque
-
如果需要简单快速的队列操作,可以选择
ArrayQueue
或LinkedListQueue
。
ArrayQueue
-
ArrayQueue
使用数组(如Object[]
或泛型数组T[]
)来存储队列中的元素。 -
由于数组的大小是固定的,因此
ArrayQueue
需要一种机制来处理队列的扩容,当元素数量超过数组容量时,能够动态地增加数组的大小。 -
为了避免在数组前端添加元素时导致的大量元素移动(即“假溢出”问题),
ArrayQueue
可能会使用循环数组(也称为环形队列)的实现方式。
ArrayQueue 扩容机制
在 ArrayQueue
中,扩容通常发生在以下两种情况:
- 队列已满:当队列中的元素数量达到数组的最大容量时。
- 队列半空:当队列中的元素数量减少到一定比例时,为了节省内存空间。
ArrayQueue
的扩容机制是动态的,可以根据需要自动增加或减少容量。这种机制使得 ArrayQueue
能够有效地管理内存,并且在大多数情况下提供了良好的性能。ArrayQueue
会采取两种扩容策略:
-
默认扩容比例:通常情况下,
ArrayQueue
会在队列满时将容量扩大为原来的两倍。 -
缩容策略:当队列中的元素数量减少到一定程度时,比如减少到最大容量的四分之一,
ArrayQueue
可能会将容量减小到一半。
下面通过代码示例来说明 ArrayQueue
的扩容过程:
import java.util.NoSuchElementException;
public class ArrayQueue<T> {
private T[] elements;
private int head;
private int tail;
private int size;
public ArrayQueue(int capacity) {
elements = (T[]) new Object[capacity];
head = 0;
tail = 0;
size = 0;
}
public void enqueue(T element) {
if (size == elements.length) {
resize(elements.length * 2); // 扩容为原来的两倍
}
elements[tail] = element;
tail = (tail + 1) % elements.length;
size++;
}
public T dequeue() {
if (isEmpty()) {
throw new NoSuchElementException("Queue is empty.");
}
T removedElement = elements[head];
elements[head] = null; // 帮助垃圾回收
head = (head + 1) % elements.length;
size--;
if (size > 0 && size == elements.length / 4) {
resize(elements.length / 2); // 缩容为原来的一半
}
return removedElement;
}
private void resize(int newCapacity) {
T[] newElements = (T[]) new Object[newCapacity];
for (int i = 0; i < size; i++) {
newElements[i] = elements[(i + head) % elements.length];
}
elements = newElements;
head = 0;
tail = size;
}
// 其他方法省略
}
代码解析:
-
enqueue 方法:当尝试添加新元素时,如果队列已满(
size == elements.length
),则调用resize
方法进行扩容。 -
resize 方法:在
resize
方法中,创建一个新的数组,并将旧数组中的元素复制到新数组中。新数组的大小取决于扩容还是缩容的情况。 -
dequeue 方法:当队列中的元素数量减少到最大容量的四分之一时,
ArrayQueue
会调用resize
方法进行缩容。
ArrayQueue 使用示例
import java.util.NoSuchElementException;
public class ArrayQueueExample {
public static void main(String[] args) {
// 创建一个 ArrayQueue
ArrayQueue<Integer> queue = new ArrayQueue<>(10);
// 添加元素
queue.enqueue(1);
queue.enqueue(2);
queue.enqueue(3);
// 访问队首元素
System.out.println("Peek: " + queue.peek()); // 输出: Peek: 1
// 移除队首元素
System.out.println("Dequeued: " + queue.dequeue()); // 输出: Dequeued: 1
// 再次访问队首元素
System.out.println("New peek: " + queue.peek()); // 输出: New peek: 2
// 检查队列是否为空
System.out.println("Is the queue empty? " + queue.isEmpty()); // 输出: Is the queue empty? false
// 获取队列的大小
System.out.println("Size of the queue: " + queue.size()); // 输出: Size of the queue: 2
// 遍历队列中的所有元素
while (!queue.isEmpty()) {
System.out.println(queue.dequeue()); // 依次输出: 2, 3
}
}
}
LinkedListQueue
LinkedListQueue
(基于链表 LinkedList
)内部使用双向链表来存储元素。每个节点包含数据部分、指向前一个节点的指针(prev)和指向后一个节点的指针(next)。这种结构使得在链表的两端添加或删除元素时,操作的时间复杂度为O(1)。
特点
-
线程安全性:
LinkedList
不是线程安全的,如果多个线程同时访问一个LinkedList
实例,并且至少有一个线程从结构上修改了列表,那么它必须保持外部同步。 -
容量:由于
LinkedList
是基于链表的,它不需要在创建时指定容量,并且可以根据需要动态地增长和缩小。 -
灵活性:
LinkedList
不仅可以用作队列,还可以用作栈、双端队列等,因为它实现了Deque
接口。 -
当需要频繁地在集合的两端进行添加或删除操作时;或当集合的大小经常变化,且不需要随机访问元素时,
LinkedList
是一个很好的选择。
LinkedListQueue 使用示例
public class LinkedListQueueExample {
public static void main(String[] args) {
// 创建一个 LinkedListQueue
LinkedListQueue<Integer> queue = new LinkedListQueue<>();
// 添加元素
queue.enqueue(1);
queue.enqueue(2);
queue.enqueue(3);
// 访问队首元素
System.out.println("Peek: " + queue.peek()); // 输出: Peek: 1
// 移除队首元素
System.out.println("Dequeued: " + queue.dequeue()); // 输出: Dequeued: 1
// 再次访问队首元素
System.out.println("New peek: " + queue.peek()); // 输出: New peek: 2
// 检查队列是否为空
System.out.println("Is the queue empty? " + queue.isEmpty()); // 输出: Is the queue empty? false
// 获取队列的大小
System.out.println("Size of the queue: " + queue.size()); // 输出: Size of the queue: 2
// 遍历队列中的所有元素
while (!queue.isEmpty()) {
System.out.println(queue.dequeue()); // 依次输出: 2, 3
}
}
}
PriorityQueue
PriorityQueue
是一个基于优先级堆的无界优先级队列。优先级队列的元素按照其自然顺序进行排序,或者根据构造队列时所提供的 Comparator
进行排序,具体取决于所使用的构造方法。
构造方法
- 创建一个初始容量为 11 的空优先级队列,元素将按照自然顺序进行排序。
PriorityQueue()
- 创建一个指定初始容量的空优先级队列,元素将按照自然顺序进行排序。
PriorityQueue(int initialCapacity)
- 创建一个指定初始容量和自定义比较器的空优先级队列,根据比较器进行元素排序。
PriorityQueue(int Capacity, Comparator<? super E> comparator)
- 创建一个包含指定集合元素的优先级队列,元素将按照自然顺序进行排序。
PriorityQueue(Collection<? extends E> collection)
- 创建一个包含指定优先级队列所有元素的新优先级队列,元素将按照自然顺序进行排序。
PriorityQueue(PriorityQueue<? extends E> queue)
- 创建一个包含指定排序集合元素的优先级队列,元素将按照集合的顺序进行排序。
PriorityQueue(SortedSet<? extends E> set)
特点
-
优先级:
PriorityQueue
中的元素可以使用自然排序或通过Comparator
进行自定义排序,队列会根据元素的优先级将它们插入到合适的位置。如果多个元素具有相同的优先级,则它们之间是没有顺序保证的。 -
不允许
null
元素:PriorityQueue
不允许插入null
元素。 -
队列容量:
PriorityQueue
的容量可以是固定的,也可以是动态扩展的(默认为 11)。 -
不保证插入顺序:
PriorityQueue
不保证插入的顺序,只保证出队顺序为优先级高的元素先出队。 -
队列的头部是按指定排序方式确定的最小元素(队列中的最小元素总是位于队列的头部)。当元素被添加或移除时,队列会自动重新排序以保持最小元素在前的性质
PriorityQueue 使用示例
import java.util.PriorityQueue;
public class PriorityQueueExample {
public static void main(String[] args) {
// 创建一个 PriorityQueue
PriorityQueue<Integer> queue = new PriorityQueue<>();
// 添加元素
queue.offer(1);
queue.offer(2);
queue.offer(3);
queue.offer(4);
queue.offer(5);
// 移除队首元素(最小值)
System.out.println("Removed: " + queue.poll()); // 输出: Removed: 1
// 访问队首元素(最小值)
System.out.println("Peek: " + queue.peek()); // 输出: Peek: 2
// 再次移除队首元素
System.out.println("Removed: " + queue.poll()); // 输出: Removed: 2
// 检查队列是否为空
System.out.println("Is the queue empty? " + queue.isEmpty()); // 输出: Is the queue empty? false
// 获取队列的大小
System.out.println("Size of the queue: " + queue.size()); // 输出: Size of the queue: 3
// 遍历队列中的所有元素
while (!queue.isEmpty()) {
System.out.println(queue.poll()); // 依次输出: 3, 4, 5
}
}
}
PriorityQueue 自定义比较
如果需要根据自定义的规则对元素进行排序,可以提供一个 Comparator
对象给 PriorityQueue
的构造函数。下面是一个示例,展示了如何根据整数的绝对值对元素进行排序:
import java.util.Comparator;
import java.util.PriorityQueue;
public class CustomPriorityQueueExample {
public static void main(String[] args) {
// 创建一个 PriorityQueue 并提供自定义的 Comparator
PriorityQueue<Integer> queue = new PriorityQueue<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return Math.abs(o1) - Math.abs(o2);
}
});
// 添加元素
queue.offer(-10);
queue.offer(5);
queue.offer(15);
queue.offer(1);
// 访问队首元素
System.out.println("Peek: " + queue.peek()); // 输出: Peek: 1
// 移除队首元素
System.out.println("Removed: " + queue.poll()); // 输出: Removed: 1
// 再次访问队首元素
System.out.println("New peek: " + queue.peek()); // 输出: New peek: 5
// 获取队列的大小
System.out.println("Size of the queue: " + queue.size()); // 输出: Size of the queue: 3
// 遍历队列中的所有元素
while (!queue.isEmpty()) {
System.out.println(queue.poll()); // 依次输出: 5, -10, 15
}
}
}
Deque 接口
Deque
(双端队列)是一种特殊的队列数据结构,它允许在队列的两端进行元素的插入和移除操作。Java 提供了一个 Deque
接口,它是 Queue 接口的子接口,并扩展了 Queue 中定义的方法。
Deque 接口既可以作为队列使用,也可以作为栈使用。它提供了常见的队列和栈操作的方法,例如添加元素、移除元素、获取元素等。具体的使用方式取决于我们如何使用这些方法来实现我们的需求。
常用方法
-
addFirst(E element)
:将指定的元素插入到双端队列的头部。如果插入操作成功,则将返回 true;否则,将抛出异常。 -
addLast(E element)
:将指定的元素插入到双端队列的尾部。如果插入操作成功,则将返回 true;否则,将抛出异常。 -
offerFirst(E element)
:将指定的元素插入到双端队列的头部。如果插入操作成功,则将返回 true;否则,将返回 false。 -
offerLast(E element)
:将指定的元素插入到双端队列的尾部。如果插入操作成功,则将返回 true;否则,将返回 false。 -
removeFirst()
:移除并返回双端队列的头部元素。如果队列为空,则将抛出异常。 -
removeLast()
:移除并返回双端队列的尾部元素。如果队列为空,则将抛出异常。 -
pollFirst()
:移除并返回双端队列的头部元素。如果队列为空,则返回 null。 -
pollLast()
:移除并返回双端队列的尾部元素。如果队列为空,则返回 null。 -
getFirst()
:返回双端队列的头部元素,但不会移除它。如果队列为空,则将抛出异常。 -
getLast()
:返回双端队列的尾部元素,但不会移除它。如果队列为空,则将抛出异常。 -
peekFirst()
:返回双端队列的头部元素,但不会移除它。如果队列为空,则返回 null。 -
peekLast()
:返回双端队列的尾部元素,但不会移除它。如果队列为空,则返回 null。
Deque 使用示例 (使用 ArrayDeque)
import java.util.ArrayDeque;
import java.util.Deque;
public class DequeExample {
public static void main(String[] args) {
// 创建一个 Deque (使用 ArrayDeque)
Deque<Integer> deque = new ArrayDeque<>();
// 添加元素
deque.offerFirst(1);
deque.offerLast(2);
deque.offerLast(3);
// 访问队首元素
System.out.println("First element: " + deque.peekFirst()); // 输出: First element: 1
// 访问队尾元素
System.out.println("Last element: " + deque.peekLast()); // 输出: Last element: 3
// 移除队首元素
System.out.println("Removed first: " + deque.pollFirst()); // 输出: Removed first: 1
// 再次访问队首元素
System.out.println("New first element: " + deque.peekFirst()); // 输出: New first element: 2
// 检查队列是否为空
System.out.println("Is the deque empty? " + deque.isEmpty()); // 输出: Is the deque empty? false
// 获取队列的大小
System.out.println("Size of the deque: " + deque.size()); // 输出: Size of the deque: 2
// 遍历队列中的所有元素
while (!deque.isEmpty()) {
System.out.println(deque.pollFirst()); // 依次输出: 2, 3
}
}
}
总结
Queue
接口是Java集合框架中的一个重要组成部分,它定义了一种先进先出(FIFO, First-In-First-Out)的数据结构,用于存储和管理元素。Queue
接口继承自 Collection
接口,并提供了一系列方法来支持队列的基本操作,如添加元素、移除元素和检查队列的状态。
队列的一个关键特性是只能在一端添加元素(队尾),而在另一端移除元素(队首)。Queue
接口有多种实现类,包括 LinkedList
、ArrayDeque
和 PriorityQueue
等,每种实现都有其特定的用途和性能特点。