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

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

1. Overviewс

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

2. EvictingQueueс

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

Когда мы хотим добавить новый элемент вEvictingQueue иthe queue is full, it automatically evicts an element from its head.

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

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

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

Queue 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(). Обратите внимание, что они не удаляют элементы из очереди, они только извлекают ее.

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

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

class CustomClass {
    private Integer value;

    // standard constructor, getters and setters
}

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

MinMaxPriorityQueue 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 и переданногоComparator, элемент в начале очереди будет равен 1, а элемент в конце очереди будет равен 10:

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

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

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

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

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

Согласно спецификацииMinMaxPriorityQueue,in case the queue is full, adding an element that is greater than the currently greatest element will remove that same element – effectively ignoring it.

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

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

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