Java Queue接口指南

2023/06/07

1.概述

在本教程中,我们将讨论Java的队列接口。

首先,我们将了解Queue的作用及其一些核心方法。接下来,我们将深入研究Java作为标准提供的一些实现。

最后,我们将在结束之前讨论线程安全。

2.可视化队列

让我们从一个简单的类比开始。

想象一下,我们刚刚开了我们的第一家公司——一家热狗摊。我们希望以最有效的方式为我们的小企业服务我们的新潜在客户;一次一个。首先,我们请他们在我们的展台前排好队,新顾客排在后面。由于我们的组织能力,我们现在可以公平地分发美味的热狗。

Java中的队列以类似的方式工作。在我们声明我们的Queue之后,我们可以在后面添加新元素,并从前面删除它们。

事实上,我们在Java中遇到的大多数队列都以先进先出的方式工作——通常缩写为FIFO。

但是,有一个例外我们稍后会谈

3.核心方法

Queue声明了许多需要由所有实现类编码的方法。现在让我们概述一些更重要的:

  1. offer()–在队列中插入一个新元素
  2. poll()–从队列的前面移除一个元素
  3. peek()–检查队列前面的元素而不删除它

4.抽象队列

AbstractQueue是Java提供的最简单的Queue实现。它包括一些Queue接口方法的框架实现,不包括offer

当我们创建一个扩展AbstractQueue类的自定义队列时,我们必须提供一个不允许插入null元素的offer方法的实现。

此外,我们必须提供方法peek、poll、size和java.util的迭代器。

让我们使用AbstractQueue组合一个简单的Queue实现。

首先,让我们用一个LinkedList来定义我们的类来存储我们队列的元素:

public class CustomBaeldungQueue<T> extends AbstractQueue<T> {

    private LinkedList<T> elements;

    public CustomBaeldungQueue() {
      this.elements = new LinkedList<T>();
    }

}

接下来,让我们覆盖所需的方法并提供代码:

@Override
public Iterator<T> iterator() {
    return elements.iterator();
}

@Override
public int size() {
    return elements.size();
}

@Override
public boolean offer(T t) {
    if(t == null) return false;
    elements.add(t);
    return true;
}

@Override
public T poll() {
    Iterator<T> iter = elements.iterator();
    T t = iter.next();
    if(t != null){
        iter.remove();
        return t;
    }
    return null;
}

@Override
public T peek() {
    return elements.getFirst();
}

太好了,让我们通过快速单元测试检查它是否有效:

customQueue.add(7);
customQueue.add(5);

int first = customQueue.poll();
int second = customQueue.poll();

assertEquals(7, first);
assertEquals(5, second);

4.子接口

Queue接口一般由3个主要的子接口继承。阻塞队列、传输队列和双端队列。

这3个接口共同由绝大多数Java可用队列实现。让我们快速浏览一下这些接口的用途。

4.1.阻塞队列

BlockingQueue接口支持额外的操作,这些操作强制线程根据当前状态在Queue上等待。尝试检索时,线程可能会等待队列非空,或者在添加新元素时等待队列变为空。

标准阻塞队列包括LinkedBlockingQueue、SynchronousQueue和ArrayBlockingQueue。

有关更多信息,请转至我们关于阻塞队列的文章。

4.2.传输队列

TransferQueue接口扩展了BlockingQueue接口,但针对生产者-消费者模式进行了定制。它控制从生产者到消费者的信息流,在系统中产生背压。

Java附带了TransferQueue接口的一种实现,LinkedTransferQueue

4.3.德奎斯

Deque是Double-EndedQueue的缩写,类似于一副纸牌——可以从Deque的开头和结尾获取元素。与传统的Queue非常相似,Deque提供了添加、检索和查看位于顶部和底部的元素的方法。

有关Deque工作原理的详细指南,请查看我们的ArrayDeque文章

5.优先队列

我们之前看到,我们在Java中遇到的大多数队列都遵循FIFO原则。

此规则的一个例外是PriorityQueue。当新元素被插入到优先队列中时,它们会根据它们的自然顺序进行排序,或者通过我们构建优先队列时提供的定义的比较器进行排序。

让我们通过一个简单的单元测试来看看它是如何工作的:

PriorityQueue<Integer> integerQueue = new PriorityQueue<>();

integerQueue.add(9);
integerQueue.add(2);
integerQueue.add(4);

int first = integerQueue.poll();
int second = integerQueue.poll();
int third = integerQueue.poll();

assertEquals(2, first);
assertEquals(4, second);
assertEquals(9, third);

尽管我们的整数被添加到PriorityQueue的顺序,我们可以看到检索顺序根据数字的自然顺序而改变。

我们可以看到同样适用于Strings:

PriorityQueue<String> stringQueue = new PriorityQueue<>();

stringQueue.add("blueberry");
stringQueue.add("apple");
stringQueue.add("cherry");

String first = stringQueue.poll();
String second = stringQueue.poll();
String third = stringQueue.poll();

assertEquals("apple", first);
assertEquals("blueberry", second);
assertEquals("cherry", third);

6.线程安全

将项目添加到队列在多线程环境中特别有用。队列可以在线程之间共享,并用于在空间可用之前阻止进程——帮助我们克服一些常见的多线程问题。

例如,从多个线程写入单个磁盘会造成资源争用,并可能导致写入时间变慢。使用BlockingQueue创建单个写入线程可以缓解此问题并大大提高写入速度。

幸运的是,Java提供了ConcurrentLinkedQueue、ArrayBlockingQueue和ConcurrentLinkedDeque,它们是线程安全的,非常适合多线程程序。

七、总结

在本教程中,我们深入探讨了JavaQueue接口。

首先,我们探讨了Queue的作用,以及Java提供的实现。

接下来,我们了解了队列通常的FIFO原则,以及顺序不同的PriorityQueue。

最后,我们探讨了线程安全以及如何在多线程环境中使用队列。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章