Java 入门指南:Queue 接口

Collection 接口

Collection 接口提供了一系列用于操作和管理集合的方法,包括添加、删除、查询、遍历等。它是所有集合类的根接口,包括 ListSetQueue 等。

![[Collection UML.png]]

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 接口中常用的一些方法:

  1. add(E element):将指定的元素添加到队列的尾部。如果队列已满,则抛出一个异常。

  2. offer(E element):将指定的元素添加到队列的尾部。如果队列已满,则返回false,否则返回true。

  3. remove():移除并返回队列头部的元素。如果队列为空,则抛出一个异常。

  4. poll():移除并返回队列头部的元素。如果队列为空,则返回null。

  5. element():返回队列头部的元素,但不移除。如果队列为空,则抛出一个异常。

  6. peek():返回队列头部的元素,但不移除。如果队列为空,则返回null。

Queue 实现类

Queue 接口有几种常见的实现类,包括 ArrayQueue(自定义实现)、LinkedListArrayDequePriorityQueue 等。

  • ArrayQueue:基于数组实现的队列,访问和移除队首元素较快,但可能需要在队列满或半空时进行数组的复制操作。

  • LinkedListQueue:基于双向链表实现的队列,添加和移除元素非常快,因为只需要更新指针即可。

  • PriorityQueue:基于小顶堆实现的队列,队列中的元素会自动排序,最小的元素位于队首。适合需要优先级排序的场景。

  • ArrayDeque:基于数组实现的双端队列,可以作为队列或栈使用。两端的插入和移除操作都非常快,因为只需要更新指针即可。

根据具体的应用场景选择合适的队列类型非常重要。例如:

  • 如果需要优先级排序的队列,可以选择 PriorityQueue

  • 如果需要高效的两端操作,可以选择 ArrayDeque

  • 如果需要简单快速的队列操作,可以选择 ArrayQueueLinkedListQueue

ArrayQueue
  • ArrayQueue 使用数组(如 Object[] 或泛型数组 T[])来存储队列中的元素。

  • 由于数组的大小是固定的,因此 ArrayQueue 需要一种机制来处理队列的扩容,当元素数量超过数组容量时,能够动态地增加数组的大小。

  • 为了避免在数组前端添加元素时导致的大量元素移动(即“假溢出”问题),ArrayQueue 可能会使用循环数组(也称为环形队列)的实现方式。

ArrayQueue 扩容机制

ArrayQueue 中,扩容通常发生在以下两种情况:

  1. 队列已满:当队列中的元素数量达到数组的最大容量时。
  2. 队列半空:当队列中的元素数量减少到一定比例时,为了节省内存空间。

ArrayQueue 的扩容机制是动态的,可以根据需要自动增加或减少容量。这种机制使得 ArrayQueue 能够有效地管理内存,并且在大多数情况下提供了良好的性能。ArrayQueue 会采取两种扩容策略:

  1. 默认扩容比例:通常情况下,ArrayQueue 会在队列满时将容量扩大为原来的两倍。

  2. 缩容策略:当队列中的元素数量减少到一定程度时,比如减少到最大容量的四分之一,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;
    }

    // 其他方法省略
}

代码解析:

  1. enqueue 方法:当尝试添加新元素时,如果队列已满(size == elements.length),则调用 resize 方法进行扩容。

  2. resize 方法:在 resize 方法中,创建一个新的数组,并将旧数组中的元素复制到新数组中。新数组的大小取决于扩容还是缩容的情况。

  3. 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 进行排序,具体取决于所使用的构造方法。

构造方法
  1. 创建一个初始容量为 11 的空优先级队列,元素将按照自然顺序进行排序。
PriorityQueue()
  1. 创建一个指定初始容量的空优先级队列,元素将按照自然顺序进行排序。
PriorityQueue(int initialCapacity)
  1. 创建一个指定初始容量和自定义比较器的空优先级队列,根据比较器进行元素排序。
PriorityQueue(int Capacity, Comparator<? super E> comparator)
  1. 创建一个包含指定集合元素的优先级队列,元素将按照自然顺序进行排序。
PriorityQueue(Collection<? extends E> collection)
  1. 创建一个包含指定优先级队列所有元素的新优先级队列,元素将按照自然顺序进行排序。
PriorityQueue(PriorityQueue<? extends E> queue)
  1. 创建一个包含指定排序集合元素的优先级队列,元素将按照集合的顺序进行排序。
PriorityQueue(SortedSet<? extends E> set)
特点
  1. 优先级:PriorityQueue 中的元素可以使用自然排序或通过 Comparator 进行自定义排序,队列会根据元素的优先级将它们插入到合适的位置。如果多个元素具有相同的优先级,则它们之间是没有顺序保证的。

  2. 不允许 null 元素:PriorityQueue 不允许插入 null 元素。

  3. 队列容量:PriorityQueue 的容量可以是固定的,也可以是动态扩展的(默认为 11)。

  4. 不保证插入顺序:PriorityQueue 不保证插入的顺序,只保证出队顺序为优先级高的元素先出队

  5. 队列的头部是按指定排序方式确定的最小元素(队列中的最小元素总是位于队列的头部)。当元素被添加或移除时,队列会自动重新排序以保持最小元素在前的性质

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 接口既可以作为队列使用,也可以作为栈使用。它提供了常见的队列和栈操作的方法,例如添加元素、移除元素、获取元素等。具体的使用方式取决于我们如何使用这些方法来实现我们的需求。

常用方法
  1. addFirst(E element):将指定的元素插入到双端队列的头部。如果插入操作成功,则将返回 true;否则,将抛出异常。

  2. addLast(E element):将指定的元素插入到双端队列的尾部。如果插入操作成功,则将返回 true;否则,将抛出异常。

  3. offerFirst(E element):将指定的元素插入到双端队列的头部。如果插入操作成功,则将返回 true;否则,将返回 false。

  4. offerLast(E element):将指定的元素插入到双端队列的尾部。如果插入操作成功,则将返回 true;否则,将返回 false。

  5. removeFirst():移除并返回双端队列的头部元素。如果队列为空,则将抛出异常。

  6. removeLast():移除并返回双端队列的尾部元素。如果队列为空,则将抛出异常。

  7. pollFirst():移除并返回双端队列的头部元素。如果队列为空,则返回 null。

  8. pollLast():移除并返回双端队列的尾部元素。如果队列为空,则返回 null。

  9. getFirst():返回双端队列的头部元素,但不会移除它。如果队列为空,则将抛出异常。

  10. getLast():返回双端队列的尾部元素,但不会移除它。如果队列为空,则将抛出异常。

  11. peekFirst():返回双端队列的头部元素,但不会移除它。如果队列为空,则返回 null。

  12. 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 接口有多种实现类,包括 LinkedListArrayDequePriorityQueue 等,每种实现都有其特定的用途和性能特点。

  • 9
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值