Перейти к основному содержимому

Руководство по Guava MinMaxPriorityQueue и EvictingQueue

· 4 мин. чтения

1 . Обзор

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

2. Выселение из очереди

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

Когда мы хотим добавить новый элемент в 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 обеспечивает постоянный доступ к своим наименьшим и наибольшим элементам.

Чтобы получить наименьший элемент, нам нужно вызвать метод 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, поэтому его должно быть легко импортировать и запускать как есть.