«1. Обзор

В этой статье мы рассмотрим конструкции EvictingQueue и MinMaxPriorityQueue из библиотеки Guava. EvictingQueue — это реализация концепции циклического буфера. MinMaxPriorityQueue дает нам доступ к наименьшему и наибольшему элементу с помощью предоставленного компаратора.

2. EvictingQueue

Давайте начнем с построения — при создании экземпляра очереди нам нужно указать максимальный размер очереди в качестве аргумента.

Когда мы хотим добавить новый элемент в EvictingQueue, а очередь заполнена, он автоматически вытесняет элемент из своей головы.

По сравнению со стандартным поведением очереди добавление элемента в полную очередь не блокирует, а удаляет головной элемент и добавляет новый элемент в хвост.

Мы можем представить EvictingQueue как кольцо, в которое мы вставляем элементы только для добавления. Если есть элемент в позиции, в которую мы хотим добавить новый элемент, мы просто переопределяем существующий элемент в данной позиции.

Давайте создадим экземпляр EvictingQueue с максимальным размером 10. Затем мы добавим в него 10 элементов:

Queue<Integer> evictingQueue = EvictingQueue.create(10);

IntStream.range(0, 10)
  .forEach(evictingQueue::add);

assertThat(evictingQueue)
  .containsExactly(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);

Если бы у нас была стандартная реализация очереди, добавление нового элемента в полную очередь было бы заблокировать производителя.

Это не относится к реализации EvictingQueue. Добавление к ней нового элемента приведет к удалению из нее головы, а новый элемент будет добавлен к хвосту:

evictingQueue.add(100);

assertThat(evictingQueue)
  .containsExactly(1, 2, 3, 4, 5, 6, 7, 8, 9, 100);

Используя EvictingQueue в качестве кольцевого буфера, мы можем создавать очень эффективные параллельные программы.

3. MinMaxPriorityQueue

MinMaxPriorityQueue обеспечивает постоянный доступ к своим наименьшим и наибольшим элементам.

Чтобы получить наименьший элемент, нам нужно вызвать метод peekFirst(). Чтобы получить наибольший элемент, мы можем вызвать метод peekLast(). Обратите внимание, что они не удаляют элементы из очереди, а только извлекают их.

Упорядочивание элементов осуществляется компаратором, который необходимо передать конструктору этой очереди.

Допустим, у нас есть класс CustomClass с полем значений целочисленного типа:

class CustomClass {
    private Integer value;

    // standard constructor, getters and setters
}

Давайте создадим MinMaxPriorityQueue, который будет использовать компаратор для типов int. Далее мы добавим в очередь 10 объектов типа CustomClass:

MinMaxPriorityQueue<CustomClass> queue = MinMaxPriorityQueue
  .orderedBy(Comparator.comparing(CustomClass::getValue))
  .maximumSize(10)
  .create();

IntStream
  .iterate(10, i -> i - 1)
  .limit(10)
  .forEach(i -> queue.add(new CustomClass(i)));

В силу особенностей MinMaxPriorityQueue и переданного Компаратора элемент в начале очереди будет равен 1, а элемент в начале хвост очереди будет равен 10:

assertThat(
  queue.peekFirst().getValue()).isEqualTo(1);
assertThat(
  queue.peekLast().getValue()).isEqualTo(10);

Поскольку вместимость нашей очереди 10, а мы добавили 10 элементов, очередь заполнена. Добавление к нему нового элемента приведет к удалению последнего элемента в очереди. Добавим CustomClass со значением, равным -1:

queue.add(new CustomClass(-1));

После этого действия последний элемент в очереди будет удален, а новый элемент в хвосте будет равен 9. Новая голова будет быть -1, так как это новый наименьший элемент в соответствии с Компаратором, который мы передали при построении нашей очереди:

assertThat(
  queue.peekFirst().getValue()).isEqualTo(-1);
assertThat(
  queue.peekLast().getValue()).isEqualTo(9);

Согласно спецификации MinMaxPriorityQueue, в случае, если очередь заполнена, добавление текущий наибольший элемент удалит тот же самый элемент, фактически игнорируя его.

Давайте добавим число 100 и проверим, находится ли этот элемент в очереди после этой операции:

queue.add(new CustomClass(100));
assertThat(queue.peekFirst().getValue())
  .isEqualTo(-1);
assertThat(queue.peekLast().getValue())
  .isEqualTo(9);

Как мы видим, первый элемент в очереди по-прежнему равен -1, а последний равен 9. Поэтому , добавление целого числа было проигнорировано, так как оно больше самого большого элемента в очереди.

4. Заключение

В этой статье мы рассмотрели конструкцию EvictingQueue и MinMaxPriorityQueue из библиотеки Guava.

Мы увидели, как использовать EvictingQueue в качестве кольцевого буфера для реализации очень эффективных программ.

Мы использовали MinMaxPriorityQueue в сочетании с компаратором, чтобы иметь постоянный доступ к его наименьшему и наибольшему элементу.

«Важно помнить характеристики обеих представленных очередей, так как добавление в них нового элемента переопределит элемент, который уже находится в очереди. Это противоречит стандартной реализации очереди, где добавление нового элемента в полную очередь блокирует поток производителя или вызывает исключение.

Реализацию всех этих примеров и фрагментов кода можно найти в проекте GitHub — это проект Maven, поэтому его должно быть легко импортировать и запускать как есть.