вопрос
(1) Что такое приоритетная очередь?
(2) Как реализовать приоритетную очередь?
(3) Является ли PriorityQueue потокобезопасным?
(4) Заказывается ли PriorityQueue?
Введение
Очередь с приоритетом представляет собой набор из 0 или более элементов, каждый элемент в наборе имеет значение веса, а элемент с наивысшим или наименьшим приоритетом извлекается каждый раз, когда он удаляется из очереди.
Как правило, приоритетные очереди реализуются с использованием кучи.
Помните знания о кучах? Прямая ссылка【Пожалуйста, не просите меня снова складывать (сортировать) в интервью!].
Так как же Java реализует приоритетную очередь через структуру данных «куча»?
Давайте учиться вместе.
Анализ исходного кода
главный атрибут
// 默认容量
private static final int DEFAULT_INITIAL_CAPACITY = 11;
// 存储元素的地方
transient Object[] queue; // non-private to simplify nested class access
// 元素个数
private int size = 0;
// 比较器
private final Comparator<? super E> comparator;
// 修改次数
transient int modCount = 0; // non-private to simplify nested class access
(1) Емкость по умолчанию — 11;
(2) очередь, элементы хранятся в массиве, что согласуется с общим использованием массивов для хранения в куче, как мы говорили ранее;
(3) компаратор, компаратор, в приоритетной очереди также есть два способа сравнения элементов, один — естественный порядок элементов, а другой — сравнение через компаратор;
(4) modCount, количество модификаций, этот атрибут указывает на то, что PriorityQueue также является отказоустойчивым;
Не знаете о быстром сбое, проверьте эту статьюпасхальные яйцачасть:【Анализ исходного кода HashSet мертвой коллекции Java].
присоединиться к команде
Существует два метода постановки в очередь, add(E e) и offer(E e), оба из которых непротиворечивы, а add(E e) также является вызовом offer(E e).
public boolean add(E e) {
return offer(e);
}
public boolean offer(E e) {
// 不支持null元素
if (e == null)
throw new NullPointerException();
modCount++;
// 取size
int i = size;
// 元素个数达到最大容量了,扩容
if (i >= queue.length)
grow(i + 1);
// 元素个数加1
size = i + 1;
// 如果还没有元素
// 直接插入到数组第一个位置
// 这里跟我们之前讲堆不一样了
// java里面是从0开始的
// 我们说的堆是从1开始的
if (i == 0)
queue[0] = e;
else
// 否则,插入元素到数组size的位置,也就是最后一个元素的下一位
// 注意这里的size不是数组大小,而是元素个数
// 然后,再做自下而上的堆化
siftUp(i, e);
return true;
}
private void siftUp(int k, E x) {
// 根据是否有比较器,使用不同的方法
if (comparator != null)
siftUpUsingComparator(k, x);
else
siftUpComparable(k, x);
}
@SuppressWarnings("unchecked")
private void siftUpComparable(int k, E x) {
Comparable<? super E> key = (Comparable<? super E>) x;
while (k > 0) {
// 找到父节点的位置
// 因为元素是从0开始的,所以减1之后再除以2
int parent = (k - 1) >>> 1;
// 父节点的值
Object e = queue[parent];
// 比较插入的元素与父节点的值
// 如果比父节点大,则跳出循环
// 否则交换位置
if (key.compareTo((E) e) >= 0)
break;
// 与父节点交换位置
queue[k] = e;
// 现在插入的元素位置移到了父节点的位置
// 继续与父节点再比较
k = parent;
}
// 最后找到应该插入的位置,放入元素
queue[k] = key;
}
(1) Пустые элементы не допускаются в очередь;
(2) Если массива недостаточно, сначала расширьте его;
(3) Если элемента еще нет, вставьте позицию нижнего индекса 0;
(4) Если есть элемент, он вставляется в позицию после последнего элемента (на самом деле он не вставляется);
(5) Скопируйте кучу снизу вверх и сравните ее с родительским узлом до самого верха;
(6) Если он меньше родительского узла, обмениваться позициями с родительским узлом, пока он не станет больше родительского узла;
(7) Видно, что PriorityQueue — это небольшая верхняя куча.
Расширение
private void grow(int minCapacity) {
// 旧容量
int oldCapacity = queue.length;
// Double size if small; else grow by 50%
// 旧容量小于64时,容量翻倍
// 旧容量大于等于64,容量只增加旧容量的一半
int newCapacity = oldCapacity + ((oldCapacity < 64) ?
(oldCapacity + 2) :
(oldCapacity >> 1));
// overflow-conscious code
// 检查是否溢出
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 创建出一个新容量大小的新数组并把旧数组元素拷贝过去
queue = Arrays.copyOf(queue, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
(1) Когда массив относительно мал (менее 64), емкость каждого расширения удваивается;
(2) Когда массив относительно велик, каждое расширение увеличивает емкость только вдвое;
вне команды
Существует два метода удаления из очереди: remove() и poll(). remove() также называется poll(), но при отсутствии элементов выдается исключение.
public E remove() {
// 调用poll弹出队首元素
E x = poll();
if (x != null)
// 有元素就返回弹出的元素
return x;
else
// 没有元素就抛出异常
throw new NoSuchElementException();
}
@SuppressWarnings("unchecked")
public E poll() {
// 如果size为0,说明没有元素
if (size == 0)
return null;
// 弹出元素,元素个数减1
int s = --size;
modCount++;
// 队列首元素
E result = (E) queue[0];
// 队列末元素
E x = (E) queue[s];
// 将队列末元素删除
queue[s] = null;
// 如果弹出元素后还有元素
if (s != 0)
// 将队列末元素移到队列首
// 再做自上而下的堆化
siftDown(0, x);
// 返回弹出的元素
return result;
}
private void siftDown(int k, E x) {
// 根据是否有比较器,选择不同的方法
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}
@SuppressWarnings("unchecked")
private void siftDownComparable(int k, E x) {
Comparable<? super E> key = (Comparable<? super E>)x;
// 只需要比较一半就行了,因为叶子节点占了一半的元素
int half = size >>> 1; // loop while a non-leaf
while (k < half) {
// 寻找子节点的位置,这里加1是因为元素从0号位置开始
int child = (k << 1) + 1; // assume left child is least
// 左子节点的值
Object c = queue[child];
// 右子节点的位置
int right = child + 1;
if (right < size &&
((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
// 左右节点取其小者
c = queue[child = right];
// 如果比子节点都小,则结束
if (key.compareTo((E) c) <= 0)
break;
// 如果比最小的子节点大,则交换位置
queue[k] = c;
// 指针移到最小子节点的位置继续往下比较
k = child;
}
// 找到正确的位置,放入元素
queue[k] = key;
}
(1) Вытолкнуть первый элемент очереди;
(2) Переместите элемент в конце очереди в начало очереди;
(3) Соберите кучу сверху вниз и сравните ее с наименьшим дочерним узлом до конца;
(4) Если он больше, чем наименьший дочерний узел, поменяйте местами и продолжите сравнение с наименьшим дочерним узлом;
(5) Если он меньше наименьшего дочернего узла, нет необходимости обмениваться позициями, и нагромождение заканчивается;
(6) Это удаление верхнего элемента в куче;
Получить первый элемент очереди
Есть два метода получения первого элемента очереди: element() и peek().
public E element() {
E x = peek();
if (x != null)
return x;
else
throw new NoSuchElementException();
}
public E peek() {
return (size == 0) ? null : (E) queue[0];
}
(1) Если есть элемент, взять элемент с индексом 0;
(3) Если элемента нет, возвращается null, а element() генерирует исключение;
Суммировать
(1) Приоритеткую представляет собой небольшую точку сваи;
(2) PriorityQueue не является потокобезопасным;
(3) PriorityQueue не упорядочена, только вершина кучи хранит наименьший элемент;
(4) Постановка в очередь — это реализация элемента вставки кучи;
(5) Dequeue — реализация удаления элементов кучи;
(6) Все еще не понимаете кучу? Взгляните на эту статью【Пожалуйста, не просите меня снова складывать (сортировать) в интервью!].
пасхальные яйца
(1) Об этих методах в Queue?
Очередь — это интерфейс верхнего уровня всех очередей, он определяет ряд методов, в чем разница между ними?
действовать | Выбросить исключение | вернуть конкретное значение |
---|---|---|
присоединиться к команде | add(e) | предложение (д) - ложь |
вне команды | remove() | опрос () - ноль |
экзамен | element() | заглянуть () - ноль |
(2) Почему метод add(e) в PriorityQueue не проверяется на наличие исключений?
Поскольку PriorityQueue — это бесконечно растущая очередь, она будет расширяться, когда элементов будет недостаточно, поэтому добавление элементов не приведет к сбою.
Добро пожаловать, чтобы обратить внимание на мою общедоступную учетную запись «Брат Тонг читает исходный код», проверить больше статей из серии исходного кода и поплавать в океане исходного кода с братом Тонгом.