自学内容网 自学内容网

Java集合Queue——针对实习面试

Java集合Queue

在这里插入图片描述

Queue接口的特点是什么?

Queue接口在Java中是一个特殊的集合,它具有以下特点:

  1. 先进先出(FIFO)原则
    Queue接口遵循FIFO(先进先出)的原则,这意味着元素被添加到队列的末尾,并从队列的前端移除。

  2. 元素访问
    Queue提供了对队列头部和尾部元素的访问。peek()方法允许查看队列头部的元素而不移除它,而element()方法也用于获取队头元素,但与peek()不同,如果队列为空,element()会抛出NoSuchElementException异常。

  3. 添加和移除元素
    Queue接口提供了add(E e)remove()方法来添加和移除元素,这些方法在队列满或空时会抛出异常。为了更安全地处理这些情况,Queue还提供了offer(E e)poll()方法,它们在无法添加或移除元素时返回falsenull,而不是抛出异常。

  4. 容量限制
    某些Queue实现(如ArrayDequeLinkedBlockingQueue)有容量限制,而其他实现(如LinkedList)则没有容量限制。

  5. 双端队列(Deque)
    Queue可以扩展为Deque接口,这允许元素从队列的两端进行插入和移除操作,使得Queue既可以作为队列使用,也可以作为栈使用。

  6. 线程安全性
    Queue接口本身不提供线程安全的保证,但Java提供了BlockingQueue接口,它是Queue的子接口,提供了线程安全的队列实现,适用于多线程环境。

  7. 泛型支持
    Queue接口支持泛型,这意味着你可以指定队列中元素的类型,从而在编译时期提供类型安全。

  8. 优先级队列
    Queue的一个子接口PriorityQueue提供了优先级队列的实现,元素根据其自然顺序或通过提供的Comparator进行排序。

  9. 非阻塞和阻塞操作
    对于非阻塞操作,Queue提供了poll()offer()方法;对于阻塞操作,BlockingQueue提供了take()put()方法,这些方法在无法进行操作时会阻塞调用线程。

了解Queue接口的这些特点对于在Java中正确使用队列至关重要,无论是在单线程还是多线程环境中。

Queue和Deque的区别?

QueueDeque都是Java集合框架中的接口,它们都可以用来存储元素,但它们之间存在一些关键的区别:

  1. 顺序不同

    • Queue接口是先进先出(FIFO)的数据结构,元素从队尾添加,从队头移除。
    • Deque接口是双端队列,支持从两端添加和移除元素,既可以实现FIFO(队列)的行为,也可以实现后进先出(LIFO,即栈)的行为。
  2. 方法不同

    • Queue提供了add, offer, remove, poll, element, peek等方法。
    • Deque除了包含Queue的所有方法外,还提供了addFirst, addLast, offerFirst, offerLast, pollFirst, pollLast, removeFirst, removeLast, getFirst, getLast, peekFirst, peekLast等方法,这些方法允许从两端进行操作。
  3. 行为不同

    • Queue的行为更受限,只能从队头移除元素,从队尾添加元素。
    • Deque的行为更灵活,可以作为队列、栈或两者的组合来使用。
  4. 实现类不同

    • Queue的常见实现类有LinkedList, PriorityQueue, ArrayDeque(也是Deque的实现)。
    • Deque的常见实现类有ArrayDequeLinkedList
  5. 性能考虑

    • 对于QueueLinkedList是一个常见的选择,因为它允许快速的插入和删

ArrayDeque和LinkedList的区别?

ArrayDequeLinkedList都是Java中的双端队列(Deque)实现,但它们在内部数据结构和性能特性上有所不同。以下是它们的主要区别:

  1. 内部数据结构

    • ArrayDeque是基于动态数组实现的,这意味着它使用一个可扩展的数组来存储元素。
    • LinkedList是基于双向链表实现的,每个元素都包含对前一个和后一个元素的引用。
  2. 随机访问性能

    • ArrayDeque支持快速的随机访问,因为它的元素存储在数组中,可以通过索引直接访问。
    • LinkedList不支持快速随机访问,因为它需要从头或尾开始遍历链表才能到达特定位置。
  3. 内存占用

    • ArrayDeque的内存占用通常比LinkedList少,因为它不需要为每个元素存储额外的前驱和后继指针。
    • LinkedList的每个节点都需要额外的空间来存储两个指针(指向前一个和后一个元素),这增加了内存开销。
  4. 扩容操作

    • ArrayDeque在需要时会进行扩容操作,这涉及到创建一个新的数组并复制旧数组中的元素,这是一个相对昂贵的操作,但通常比链表操作快。
    • LinkedList不需要扩容操作,因为它总是能够通过创建新的节点来动态地添加元素。
  5. 插入和删除性能

    • ArrayDeque在两端的插入和删除操作上通常比LinkedList快,因为它不需要像链表那样维护前后节点的链接。
    • LinkedList在两端的插入和删除操作上也很快,因为它只需要改变几个节点的指针,但随机位置的插入和删除操作会慢一些。
  6. 线程安全性

    • 两者都不是线程安全的,但可以通过Collections.synchronizedDeque方法来创建线程安全的ArrayDequeLinkedList
  7. 使用场景

    • ArrayDeque适合于需要快速随机访问元素的场景,以及作为栈或队列使用时,元素数量相对固定的情况。
    • LinkedList适合于需要在列表中间频繁插入和删除元素的场景,或者作为队列使用时,元素数量频繁变化的情况。
  8. 实现的接口

    • ArrayDeque实现了Deque接口和Queue接口。
    • LinkedList实现了List接口、Deque接口和Queue接口。
  9. 性能特性

    • ArrayDeque在并发环境下的性能通常优于LinkedList,因为它的扩容操作和随机访问操作更快。

在选择ArrayDequeLinkedList时,需要根据具体的应用场景和性能要求来决定使用哪一个。如果需要频繁的随机访问,或者队列的大小相对固定,ArrayDeque可能是更好的选择。如果需要在列表中间频繁地进行插入和删除操作,或者队列的大小频繁变化,LinkedList可能更合适。

什么是PriorityQueue?

PriorityQueue是Java中的一个类,它实现了Queue接口,并且是java.util包的一部分。以下是PriorityQueue的一些关键特性:

  1. 排序

    • PriorityQueue是一个优先级队列,其中元素根据它们的自然顺序或者通过提供的Comparator进行排序。队列的头部(队首)总是队列中具有最高优先级的元素。
  2. 无界队列

    • 默认情况下,PriorityQueue是一个无界队列,这意味着它可以无限增长,除非内存不足。
  3. 线程不安全

    • PriorityQueue不是线程安全的。如果需要线程安全的优先级队列,可以使用PriorityBlockingQueue
  4. 元素唯一性

    • PriorityQueue不允许插入null元素,并且根据使用的比较器(Comparator),可能也不支持元素重复。
  5. 插入和删除操作

    • PriorityQueue提供了offer(E e)方法来添加元素,和poll()方法来移除并返回队列头部的元素。这些操作的时间复杂度是O(log(n))
  6. 元素访问

    • 可以通过peek()方法来查看但不移除队列头部的元素,如果队列为空,则返回null
  7. 自定义排序

    • 在创建PriorityQueue实例时,可以提供一个Comparator来定义元素的排序规则。
  8. 迭代器

    • PriorityQueue提供了一个迭代器,允许按优先级顺序遍历队列中的元素。
  9. 堆的实现

    • PriorityQueue通常使用堆数据结构(通常是二叉堆)来存储元素,以保证高效的插入和删除操作。
  10. 使用场景

    • PriorityQueue适用于需要根据优先级处理元素的场景,例如任务调度、事件驱动模拟等。

下面是一个简单的PriorityQueue使用示例:

import java.util.PriorityQueue;

public class PriorityQueueExample {
    public static void main(String[] args) {
        PriorityQueue<Integer> pq = new PriorityQueue<>(); // 默认自然顺序排序
        pq.offer(3);
        pq.offer(1);
        pq.offer(2);

        System.out.println(pq.poll()); // 输出 1
        System.out.println(pq.poll()); // 输出 2
        System.out.println(pq.poll()); // 输出 3
    }
}

在这个例子中,整数被添加到优先级队列中,并按照自然顺序(升序)排序。使用poll()方法可以按优先级顺序移除元素。

什么是BlockingQueue?

BlockingQueue是Java并发包java.util.concurrent中的一个接口,它继承自java.util.Queue接口。BlockingQueue提供了线程安全的队列实现,主要用于多线程之间的数据交换。以下是BlockingQueue的一些关键特性:

  1. 线程安全

    • BlockingQueue的所有实现都是线程安全的,这意味着它们可以被多个线程安全地访问而不需要额外的同步。
  2. 阻塞操作

    • BlockingQueue提供了阻塞的插入(put)和移除(take)操作。当队列满时,插入操作会阻塞,直到队列中有空间;当队列空时,移除操作会阻塞,直到队列中有元素。
  3. 可选的公平性

    • 一些BlockingQueue实现(如LinkedBlockingQueue)允许构造函数中指定公平性(fairness)。如果设置为公平性模式,线程将根据它们等待的时间来访问队列,而不公平的队列可能允许饥饿现象发生。
  4. 容量限制

    • 某些BlockingQueue实现(如ArrayBlockingQueueLinkedBlockingQueue)有固定容量,而其他实现(如LinkedBlockingQueue的无界版本)可以有无限容量。
  5. 非阻塞操作

    • 除了阻塞操作外,BlockingQueue还提供了非阻塞的插入(offer)和移除(poll)操作,这些操作在无法立即完成时会立即返回。
  6. 支持超时

    • BlockingQueue提供了带有超时的插入(offer(E e, long timeout, TimeUnit unit))和移除(poll(long timeout, TimeUnit unit))操作,允许线程在等待一定时间后放弃。
  7. 支持中断

    • BlockingQueue的阻塞操作可以通过中断来响应中断信号,允许线程在等待时响应中断,提高程序的响应性。
  8. 常见实现

    • ArrayBlockingQueue:一个由数组支持的有界阻塞队列。
    • LinkedBlockingQueue:一个由链表支持的可选有界阻塞队列。
    • PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
    • SynchronousQueue:一个不存储元素的阻塞队列,每个插入操作必须等待一个移除操作,反之亦然。
  9. 使用场景

    • BlockingQueue适用于生产者-消费者问题,其中生产者线程将元素放入队列,消费者线程从队列中取出元素。

下面是一个简单的BlockingQueue使用示例:

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

public class BlockingQueueExample {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<Integer> queue = new LinkedBlockingQueue<>();

        // 生产者线程
        Thread producer = new Thread(() -> {
            try {
                queue.put(1); // 将元素放入队列
                System.out.println("Produced: 1");
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        // 消费者线程
        Thread consumer = new Thread(() -> {
            try {
                int element = queue.take(); // 从队列中取出元素
                System.out.println("Consumed: " + element);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        producer.start();
        consumer.start();

        producer.join();
        consumer.join();
    }
}

在这个例子中,生产者线程将一个元素放入LinkedBlockingQueue,而消费者线程从队列中取出该元素。如果队列为空,take方法将阻塞消费者线程,直到队列中有元素可用。


原文地址:https://blog.csdn.net/weixin_73527957/article/details/143699869

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!