学习JDK1.8集合源码之--PriorityQueue
1. PriorityQueue简介
PriorityQueue是一种优先队列,不同于普通队列的先进先出原则,优先队列是按照元素的优先级出列,每次出列都是优先级最高的元素。优先队列的应用很多,最典型的就是线程了,例如守护线程(GC)就是优先级比较低的一个线程。
PriorityQueue底层是通过堆(完全二叉树)这种数据结构来存储数据的,每次出列的元素都是堆中最小的(最小堆),判断元素大小的依据由使用者指定,相当于指定优先级。
文章参考自:https://www.cnblogs.com/tstd/p/5125949.html
2. PriorityQueue继承关系
PriorityQueue继承自AbstractQueue,实现了java.io.Serializable接口。
AbstractQueue实现了Queue接口,并对队列的基本方法进行了实现。
实现了 java.io.Serializable 接口:可以启用其序列化功能,能通过序列化去传输。
3. PriorityQueue实现
1. 核心参数
//定义了底层数组的最大长度
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//比较器,通过比较器确定优先级
private final Comparator<? super E> comparator;
//修改次数,不可被序列化
transient int modCount = 0; // non-private to simplify nested class access
//Object数组用来存储数据,不可被序列化
transient Object[] queue; // non-private to simplify nested class access
//默认初始容量
private static final int DEFAULT_INITIAL_CAPACITY = 11;
//存储的元素个数
private int size = 0; private static final long serialVersionUID = -7720805057305804111L;
从上面可以看出,PriorityQueue底层是通过数组进行数据存储的,可是为什么说是通过堆来存储的呢?因为这是基于数组实现的二叉堆,对于数组中任意位置的n上元素,其左孩子在[2n+1]位置上,右孩子[2(n+1)]位置,它的父亲则在[(n-1)/2]上,而根的位置则是[0]。
leftNo = parentNo*2+1 rightNo = parentNo*2+2 parentNo = (nodeNo-1)/2
2. 构造函数
//无参构造,默认初始容量11,比较器为空,这里要求入队的元素必须实现Comparator接口
public PriorityQueue() {
this(DEFAULT_INITIAL_CAPACITY, null);
} //传入一个初始容量,比较器为空,这里要求入队的元素必须实现Comparator接口
public PriorityQueue(int initialCapacity) {
this(initialCapacity, null);
} //传入一个比较器,默认初始容量11
public PriorityQueue(Comparator<? super E> comparator) {
this(DEFAULT_INITIAL_CAPACITY, comparator);
} //传入初始容量和比较器
public PriorityQueue(int initialCapacity,
Comparator<? super E> comparator) {
// Note: This restriction of at least one is not actually needed,
// but continues for 1.5 compatibility
if (initialCapacity < 1)
throw new IllegalArgumentException();
this.queue = new Object[initialCapacity];
this.comparator = comparator;
} //传入一个集合
public PriorityQueue(Collection<? extends E> c) {
// 如果集合c是包含比较器Comparator的(SortedSet/PriorityQueue),则使用集合c的比较器来初始化队列的Comparator
if (c instanceof SortedSet<?>) {
SortedSet<? extends E> ss = (SortedSet<? extends E>) c;
this.comparator = (Comparator<? super E>) ss.comparator();
// 从集合c中初始化数据到队列
initElementsFromCollection(ss);
}
else if (c instanceof PriorityQueue<?>) {
PriorityQueue<? extends E> pq = (PriorityQueue<? extends E>) c;
this.comparator = (Comparator<? super E>) pq.comparator();
// 从优先队列c中初始化数据到队列
initFromPriorityQueue(pq);
}
// 如果集合c没有包含比较器,则默认比较器Comparator为空
else {
this.comparator = null;
// 从集合c中初始化数据到队列
initFromCollection(c);
}
} //传入一个优先队列
public PriorityQueue(PriorityQueue<? extends E> c) {
//使用传入队列的比较器,若是比较器为空,则需要队列内的元素实现Comparable接口
this.comparator = (Comparator<? super E>) c.comparator();
//从优先队列c中初始化数据到队列
initFromPriorityQueue(c);
} //传入一个可排序的set
public PriorityQueue(SortedSet<? extends E> c) {
//使用传入set的比较器,若是比较器为空,则需要队列内的元素实现Comparable接口
this.comparator = (Comparator<? super E>) c.comparator();
// 从集合c中初始化数据到队列
initElementsFromCollection(c);
}
3. 核心方法
//传入一个优先队列,并把当前优先队列的内容替换
private void initFromPriorityQueue(PriorityQueue<? extends E> c) {
//如果该优先队列是PriorityQueue,则直接把底层数组和大小属性替换
if (c.getClass() == PriorityQueue.class) {
this.queue = c.toArray();
this.size = c.size();
//否则调用根据集合的统一初始化方式
} else {
initFromCollection(c);
}
} //传入一个集合,并把当前优先队列的内容替换
private void initElementsFromCollection(Collection<? extends E> c) {
//把传入的集合转化为数组a
Object[] a = c.toArray();
//如果该集合内的元素不是Object类型,则拷贝一份
if (a.getClass() != Object[].class)
a = Arrays.copyOf(a, a.length, Object[].class);
int len = a.length;
//遍历数组,若其中存在null元素则抛出空指针异常
if (len == 1 || this.comparator != null)
for (int i = 0; i < len; i++)
if (a[i] == null)
throw new NullPointerException();
//将底层数组和大小属性替换
this.queue = a;
this.size = a.length;
} //传入一个集合,并把当前优先队列的内容替换,然后将数组调整为一个最小堆(时间复杂度o(n))
private void initFromCollection(Collection<? extends E> c) {
initElementsFromCollection(c);
//将数组调整为一个最小堆
heapify();
} //对底层数组进行扩容
private void grow(int minCapacity) {
//取当前数组的容量
int oldCapacity = queue.length;
//如果当前数组容量小于64则扩容1倍,否则扩容0.5倍,防止数据量太大时扩容太多造成空间浪费
int newCapacity = oldCapacity + ((oldCapacity < 64) ?
(oldCapacity + 2) :
(oldCapacity >> 1));
//当扩容后的容量大于定义的最大值时的处理
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//通过System.arraycopy进行扩容
queue = Arrays.copyOf(queue, newCapacity);
} //大容量处理
private static int hugeCapacity(int minCapacity) {
//超过int最大值,抛出内存溢出异常
if (minCapacity < 0)
throw new OutOfMemoryError();
//小于int最大值大于定义的最大值,返回int最大值;小于定以的最大值,返回定义的最大值
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
} //向队列中添加元素
public boolean add(E e) {
return offer(e);
} //按照最小堆的规则添加元素
public boolean offer(E e) {
//传入的元素为空则抛出空指针
if (e == null)
throw new NullPointerException();
//增加修改次数
modCount++;
//取当前队列的大小
int i = size;
//若队列的大小大于等于数组容量则进行扩容
if (i >= queue.length)
grow(i + 1);
//增加队列大小
size = i + 1;
//若队列大小为0,则直接插入根位置
if (i == 0)
queue[0] = e;
//否则根据元素的大小插入合适的位置,保证最小堆的结构
else
siftUp(i, e);
return true;
} //获取队列根元素的值
public E peek() {
return (size == 0) ? null : (E) queue[0];
} //获取队列中指定元素的索引位置,通过equals比较判断,不存在返回-1
private int indexOf(Object o) {
if (o != null) {
for (int i = 0; i < size; i++)
if (o.equals(queue[i]))
return i;
}
return -1;
} //将指定元素移除队列
public boolean remove(Object o) {
int i = indexOf(o);
if (i == -1)
return false;
else {
removeAt(i);
return true;
}
} //移除指定元素,仅同包类可访问
boolean removeEq(Object o) {
for (int i = 0; i < size; i++) {
if (o == queue[i]) {
removeAt(i);
return true;
}
}
return false;
} //判断队列中是否包含指定元素
public boolean contains(Object o) {
return indexOf(o) != -1;
} //通过System.arraycopy将队列转为Object数组返回
public Object[] toArray() {
return Arrays.copyOf(queue, size);
} //通过System.arraycopy将队列转为指定类型的数组返回
public <T> T[] toArray(T[] a) {
final int size = this.size;
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(queue, size, a.getClass());
System.arraycopy(queue, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
} //获取一个迭代器(内部类实现的)
public Iterator<E> iterator() {
return new Itr();
} //返回队列的大小
public int size() {
return size;
} //清空队列,通过for循环遍历将所有元素置为null
public void clear() {
modCount++;
for (int i = 0; i < size; i++)
queue[i] = null;
size = 0;
} //将队列根元素取出,并重新调整最小堆结构
public E poll() {
if (size == 0)
return null;
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 E removeAt(int i) {
// assert i >= 0 && i < size;
modCount++;
int s = --size;
//移除最后一个元素,直接移除即可
if (s == i)
queue[i] = null;
//否则需要重新调整最小堆的结构
else {
//取最后一个元素moved
E moved = (E) queue[s];
//将最后一个元素置为空
queue[s] = null;
//把moved元素从i位置开始,根据最小堆的结构向下调整
//最小堆是父节点小于等于任意一个子节点所以moved元素可能比i位置的子节点的值大
//这时候就需要把moved元素跟其中一个比较小的子节点交换位置,以此类推,直到满足最小堆的结构
siftDown(i, moved);
//在这之前,只是把队列的最后一个元素置为空并赋值给moved变量,所以这时候i位置的元素还未动过
//当i位置的值与moved值相等时,
if (queue[i] == moved) {
siftUp(i, moved);
if (queue[i] != moved)
return moved;
}
}
return null;
} //向上调整最小堆结构,k插入的位置,x为插入元素
private void siftUp(int k, E x) {
//如果指定了比较器,则使用指定比较器的规则调整
if (comparator != null)
siftUpUsingComparator(k, x);
//否则使用元素默认的比较规则
else
siftUpComparable(k, x);
} //默认比较规则上移
private void siftUpComparable(int k, E x) {
// 比较器comparator为空,需要插入的元素实现Comparable接口,用于比较大小
Comparable<? super E> key = (Comparable<? super E>) x;
//不是根节点都需要进行调整处理
while (k > 0) {
//取k节点的父节点
int parent = (k - 1) >>> 1;
//获取父节点元素e
Object e = queue[parent];
//如果插入的元素大于等于父节点,则满足最小堆的规则,终止调整,否则需要把插入的元素上移一位
if (key.compareTo((E) e) >= 0)
break;
//插入的元素小于父节点,将父元素放入插入元素的位置
queue[k] = e;
//将插入位置调整为父节点的位置
k = parent;
}
//直到调整完成后,再将要插入的元素插入最终确定的位置
queue[k] = key;
} //使用指定比较器的规则上移
private void siftUpUsingComparator(int k, E x) {
while (k > 0) {
//取k节点的父节点
int parent = (k - 1) >>> 1;
//获取父节点元素e
Object e = queue[parent];
//如果插入的元素大于等于父节点,则满足最小堆的规则,终止调整,否则需要把插入的元素上移一位
if (comparator.compare(x, (E) e) >= 0)
break;
//插入的元素小于父节点,将父元素放入插入元素的位置
queue[k] = e;
//将插入位置调整为父节点的位置
k = parent;
}
//直到调整完成后,再将要插入的元素插入最终确定的位置
queue[k] = x;
} //向下调整最小堆结构,k插入的位置,x为插入元素
private void siftDown(int k, E x) {
//如果指定了比较器,则使用指定比较器的规则调整
if (comparator != null)
siftDownUsingComparator(k, x);
//否则使用元素默认的比较规则
else
siftDownComparable(k, x);
} //使用元素默认的比较规则下移,k插入的位置,x为插入元素
private void siftDownComparable(int k, E x) {
// 比较器comparator为空,需要插入的元素实现Comparable接口,用于比较大小
Comparable<? super E> key = (Comparable<? super E>)x;
//这里主要是去掉所有的叶子节点(没有子节点),排除不需要下移的情况,当k<half说明k节点有子节点
int half = size >>> 1;
while (k < half) {
//取k节点的左子节点
int child = (k << 1) + 1;
//取k节点的左子节点元素c
Object c = queue[child];
//取k节点的右子节点
int right = child + 1;
//k<half说明k节点有子节点,但不一定有两个,即一定有左子节点,不一定有右子节点
//right < size说明k节点是有两个节点的,当左节点大于右节点时,将右节点的值赋给c,即找出k的最小子节点
if (right < size && ((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
c = queue[child = right];
//要插入的元素小于子节点的最小节点,则终止调整,否则需要下移
if (key.compareTo((E) c) <= 0)
break;
//k的最小子节点赋给k
queue[k] = c;
//将插入的置为调整为最小子节点的位置
k = child;
}
//调整完成后,将要插入的元素放入调整后的位置
queue[k] = key;
} //使用指定比较器的比较规则下移,k插入的位置,x为插入元素
private void siftDownUsingComparator(int k, E x) {
//这里主要是去掉所有的叶子节点(没有子节点),排除不需要下移的情况,当k<half说明k节点有子节点
int half = size >>> 1;
while (k < half) {
//取k节点的左子节点
int child = (k << 1) + 1;
//取k节点的左子节点元素c
Object c = queue[child];
//取k节点的右子节点
int right = child + 1;
//k<half说明k节点有子节点,但不一定有两个,即一定有左子节点,不一定有右子节点
//right < size说明k节点是有两个节点的,当左节点大于右节点时,将右节点的值赋给c,即找出k的最小子节点
if (right < size && comparator.compare((E) c, (E) queue[right]) > 0)
c = queue[child = right];
//要插入的元素小于子节点的最小节点,则终止调整,否则需要下移
if (comparator.compare(x, (E) c) <= 0)
break;
//k的最小子节点赋给k
queue[k] = c;
//将插入的置为调整为最小子节点的位置
k = child;
}
//调整完成后,将要插入的元素放入调整后的位置
queue[k] = x;
} //调整整个最小堆的结构
private void heapify() {
//因为调用的是下移的操作,所以需要去掉所有的叶子节点
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
} //返回指定的比较器
public Comparator<? super E> comparator() {
return comparator;
} //序列化队列
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
// Write out element count, and any hidden stuff
s.defaultWriteObject(); // Write out array length, for compatibility with 1.5 version
s.writeInt(Math.max(2, size + 1)); // Write out all elements in the "proper order".
for (int i = 0; i < size; i++)
s.writeObject(queue[i]);
} //反序列化序列
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in size, and any hidden stuff
s.defaultReadObject(); // Read in (and discard) array length
s.readInt(); SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, size);
queue = new Object[size]; // Read in all elements.
for (int i = 0; i < size; i++)
queue[i] = s.readObject(); // Elements are guaranteed to be in "proper order", but the
// spec has never explained what that might be.
heapify();
}
学习JDK1.8集合源码之--PriorityQueue的更多相关文章
- 学习JDK1.8集合源码之--LinkedHashSet
1. LinkedHashSet简介 LinkedHashSet继承自HashSet,故拥有HashSet的全部API,LinkedHashSet内部实现简单,核心参数和方法都继承自HashSet,只 ...
- 学习JDK1.8集合源码之--ArrayList
参考文档: https://cloud.tencent.com/developer/article/1145014 https://segmentfault.com/a/119000001857894 ...
- 学习JDK1.8集合源码之--WeakHashMap
1. WeakHashMap简介 WeakHashMap继承自AbstractMap,实现了Map接口. 和HashMap一样,WeakHashMap也是一种以key-value键值对的形式进行数据的 ...
- 学习JDK1.8集合源码之--HashMap
1. HashMap简介 HashMap是一种key-value结构存储数据的集合,是map集合的经典哈希实现. HashMap允许存储null键和null值,但null键最多只能有一个(HashSe ...
- 学习JDK1.8集合源码之--ArrayDeque
1. ArrayDeque简介 ArrayDeque是基于数组实现的一种双端队列,既可以当成普通的队列用(先进先出),也可以当成栈来用(后进先出),故ArrayDeque完全可以代替Stack,Arr ...
- 学习JDK1.8集合源码之--HashSet
1. HashSet简介 HashSet是一个不可重复的无序集合,底层由HashMap实现存储,故HashSet是非线程安全的,由于HashSet使用HashMap的Key来存储元素,而HashMap ...
- 学习JDK1.8集合源码之--Vector
1. Vector简介 Vector是JDK1.0版本就推出的一个类,和ArrayList一样,继承自AbstractList,实现了List.RandomAccess.Cloneable.java. ...
- 学习JDK1.8集合源码之--TreeMap
1. TreeMap简介 TreeMap继承自AbstractMap,实现了NavigableMap.Cloneable.java.io.Serializable接口.所以TreeMap也是一个key ...
- 学习JDK1.8集合源码之--LinkedHashMap
1. LinkedHashMap简介 LinkedHashMap继承自HashMap,实现了Map接口. LinkedHashMap是HashMap的一种有序实现(多态,HashMap的有序态),可以 ...
随机推荐
- Python基础笔记_变量类型
下面是W3C学习笔记 , , ) :] ]) :]) :]) :-]) :-]) ]) :]) :]) ) , , ]) :]) :]) ) , , , ]) :]) :]) ) ] = , ])) ...
- 容斥原理——hdu3208
和hdu2204有点像 这题要特别注意精度问题,如pow的精度需要自己搞一下,然后最大的longlong可以设为1<<31 /* 只要求[1,n]范围内的sum即可 那么先枚举幂次k[1, ...
- 解决python中import时无法识别自己写的包和模块的方法
我们用pycharm打开自己写的代码,当多个文件之间有相互依赖的关系的时候,import无法识别自己写的文件,但是我们写的文件又确实在同一个文件夹中, 这种问题可以用下面的方法解决: 1)打开File ...
- light oj 1422 区间dp
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <math.h> ...
- 条件渲染v-if
<!DOCTYPE html> <html lang="zh"> <head> <title></title> < ...
- day 40 MySQL之视图、触发器、事务、存储过程、函数
MySQL之视图.触发器.事务.存储过程.函数 阅读目录 一 视图 二 触发器 三 事务 四 存储过程 五 函数 六 流程控制 MySQL这个软件想将数据处理的所有事情,能够在mysql这个层面上 ...
- PAT甲级——A1090 Highest Price in Supply Chain
A supply chain is a network of retailers(零售商), distributors(经销商), and suppliers(供应商)-- everyone invo ...
- 恭喜"微微软"喜当爹,Github嫁入豪门。
今天是 Github 嫁入豪门的第 2 天,炒得沸沸扬扬的微软 Github 收购事件于昨天(06月04日)尘埃落定,微软最终以 75 亿美元正式收购 Github. 随后,Gitlab 趁势带了一波 ...
- Python学习day36-并发编程(2)
figure:last-child { margin-bottom: 0.5rem; } #write ol, #write ul { position: relative; } img { max- ...
- 数据库连接客户端 dbeaver 程序包以及使用说明
dbeaver 是一个基于 Eclipse 的数据库客户端,支持几乎所有常见的数据库.分为商业版和社区版,社区版可以免费使用. 官网和 GitHub https://dbeaver.io/ https ...