0 前言
在理解WPF的 Dispatcher 的过程中,在其源码中发现一行代码:
private PriorityQueue<DispatcherOperation> _queue;
从命名的字面意思理解PriorityQueue<DispatcherOperation>
是一个DispatcherOperation
对象的优先级队列。虽然PriorityQueue<T>
类被标记为internal
,但感觉这个数据结构还是值得学习一下。
1 结构分析
1.1 UML 图
优先级队列主要涉及到三个类:PriorityQueue<T>
、PriorityItem<T>
和PriorityChain<T>
。为了能够从对它们有一个整体上的理解,我简单画了一下UML类图。
不难发现,PriorityQueue<T>
内部由链表实现(头元素引用为_head
,尾元素为_tail
),链表的基本元素为PriorityItem<T>
。
而PriorityItem<T>
看起来有些复杂,其内部有两对元素引用{SequentialPrev
,SequentialNext
} 和{PriorityPrev
,PriorityNext
} ,以及一个PriorityChain<T>
类型的属性 Chain
。
从字面意思来看,PriorityItem<T>
元素似乎同时位于两个链表上,一个是顺序链表,一个是优先级链表。到底是怎样的呢?于是我继续读代码,简单画了下优先级队列的结构图。
1.2 简化结构图
在简化结构图中:
- Sp:即
SequentialPrev
,表示元素在顺序链表中前驱节点; - Sn:即
SequentialNext
,表示元素在顺序链表中后继节点; - Pp:即
PriorityPrev
,表示元素在优先级链表中前驱节点; - Pn:即
PriorityNext
,表示元素在优先级链表中后继节点。
结合UML类图、简化结构图和PriorityQueue<T>
类中的字段
private SortedList<int, PriorityChain<T>> _priorityChains;
可以得出结论:
- 优先级队列以顺序链表为骨架的,构建并维护多条优先级关系链表;
- 多条优先级关系链表通过排序的优先级链表维护。
2 代码分析
泛型类PriorityQueue<T>
用于实现一个优先级队列,该队列具有以下功能:
Enqueue(入队)
:将具有特定优先级的数据元素添加到队列中;Dequeue(出队)
:从队列中返回并移除最高优先级的元素;RemoveItem(删除)
:从队列中删除元素;Peek(查看队首元素)
:返回但不移除队列中最高优先级的元素;ChangeItemPriority(改变元素优先级)
:更改队列中元素的优先级。
PriorityQueue<T>
类使用了几个私有字段实现这些功能:
_priorityChains
:用于存储按优先级排序的链表,类型为SortedList<int, PriorityChain<T>>
;_cacheReusableChains
:用于缓存可重用的链表,类型为Stack<PriorityChain<T>>
;_head
:队首元素,类型为PriorityItem<T>
;_tail
:队尾元素,类型为PriorityItem<T>
;_count
:表示队列中元素(PriorityItem<T>
)数量。
2.2 入队
入队(Enqueue)
,是将具有特定优先级的数据元素添加到队列中。思路:首先将T
类型的数据包装成一个PriorityItem<T>
并插入到顺序链表的尾部,然后再构建将元素的优先级链表关系。
/// <summary>
/// 将指定数据入队到优先级队列
/// </summary>
/// <param name="priority"></param>
/// <param name="data"></param>
/// <returns></returns>
public PriorityItem<T> Enqueue(DispatcherPriority priority, T data)
{
// 1. 获取优先级链表
PriorityChain<T> chain = GetChain(priority);
// 2. 将指定数据包装成链表的元素类型
PriorityItem<T> priorityItem = new PriorityItem<T>(data);
// 3. 将元素插入到顺序链表的尾部
InsertItemInSequentialChain(priorityItem, _tail);
// 4. 将元素插入指定优先级链表的尾部
InsertItemInPriorityChain(priorityItem, chain, chain.Tail);
return priorityItem;
}
将T
类型的数据包装成一个PriorityItem<T>
并插入到顺序链表的尾部,这一步很简单和普通链表的插入逻辑是一致的,按下不表。
关于构建元素的优先级链表关系,其构建逻辑如下:
- 在排序链表集合
_priorityChains
中,获取指定优先级priority
的关系链表; - 若1没找到,则需要新构建一条关系链表实例并添加到排序链表集合
_priorityChains
中。首先尝试从缓存栈_cacheReusableChains
中弹出一个PriorityChain<T>
实例;若缓存栈为空,则新建一个此实例。
关于缓存栈_cacheReusableChains
:
- 目的是减少
PriorityChain<T>
实例构建次数; - 缓存栈最大数量为10;
- 当某一条
PriorityChain<T>
的元素为0且栈大小不超过10,则不会删除优先级链表,而是将其放入缓存栈; - 当需要构建新的优先级
priority
的关系链表时,首先从缓存栈中获取,而不是每次都要新建实例。
2.3 出队
出队(Dequeue)
,即从队列中返回并移除最高优先级的元素;实现思路如下:
- 获取出队的对象:由于排序链表集合
_priorityChains
是有序的,最后一个元素就是最高优先级,即为需要出队的对象; - 删除元素;
- 返回出队对象
/// <summary>
/// 从优先级队列出队,并获取数据
/// </summary>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
public T Dequeue()
{
int count = _priorityChains.Count;
if (count <= 0)
{
throw new InvalidOperationException();
}
// 1. 获取最高优先级的队首元素
PriorityItem<T> head = _priorityChains.Values[count - 1].Head;
// 2. 从队列移除队首元素
RemoveItem(head);
// 3. 返回队首元素的数据
return head.Data;
}
2.4 删除
删除(RemoveItem):从队列中删除元素。思路:先从相应的优先级链表中删除关系,再从顺序连表中删除实例。
/// <summary>
/// 从优先级队列移除元素
/// </summary>
/// <param name="item"></param>
public void RemoveItem(PriorityItem<T> item)
{
PriorityChain<T> chain = item.Chain;
// 1. 从优先级链表中删除指定元素(删除优先级关系)
RemoveItemFromPriorityChain(item);
// 2. 从顺序链表中删除指定元素(彻底删除)
RemoveItemFromSequentialChain(item);
}
2.5 查看队首元素
查看队首元素(Peek)
,返回但不移除队列中最高优先级的元素。
/// <summary>
/// 查询优先级队列的队首元素的数据
/// </summary>
/// <returns></returns>
public T Peek()
{
T obj = default(T);
int count = _priorityChains.Count;
if (count > 0)
{
obj = _priorityChains.Values[count - 1].Head.Data; // 获取最高优先级的队首元素的数据
}
return obj;
}
2.6 改变元素优先级
改变元素优先级(ChangeItemPriority)
,更改队列中元素的优先级。
/// <summary>
/// 改变指定元素的优先级
/// </summary>
/// <param name="item"></param>
/// <param name="priority"></param>
public void ChangeItemPriority(PriorityItem<T> item, DispatcherPriority priority)
{
// 1. 从优先级链表中删除指定元素
RemoveItemFromPriorityChain(item);
// 2. 获取指定优先级的优先级链表
PriorityChain<T> chain = GetChain(priority);
// 3. 插入元素到指定的优先级队列
InsertItemInPriorityChain(item, chain);
}
3 源码及注释
namespace System.Windows.Threading
{
internal class PriorityQueue<T>
{
private SortedList<int, PriorityChain<T>> _priorityChains; // 排序的优先级链表
private Stack<PriorityChain<T>> _cacheReusableChains; // 优先级链表缓存栈
private PriorityItem<T> _head; // 顺序链表的头元素(不是队首元素)
private PriorityItem<T> _tail; // 顺序链表的尾元素(不是队尾元素)
private int _count;
public PriorityQueue()
{
_priorityChains = new SortedList<int, PriorityChain<T>>();
_cacheReusableChains = new Stack<PriorityChain<T>>(10);
_head = _tail = (PriorityItem<T>)null;
_count = 0;
}
/// <summary>
/// 优先级队列的最大优先级
/// </summary>
public DispatcherPriority MaxPriority
{
get
{
int count = _priorityChains.Count;
return count > 0 ? (DispatcherPriority)_priorityChains.Keys[count - 1] : DispatcherPriority.Invalid;
}
}
/// <summary>
/// 将指定数据入队到优先级队列
/// </summary>
/// <param name="priority"></param>
/// <param name="data"></param>
/// <returns></returns>
public PriorityItem<T> Enqueue(DispatcherPriority priority, T data)
{
// 1. 获取优先级链表
PriorityChain<T> chain = GetChain(priority);
// 2. 将指定数据包装成链表的元素类型
PriorityItem<T> priorityItem = new PriorityItem<T>(data);
// 3. 将元素插入到顺序链表的尾部
InsertItemInSequentialChain(priorityItem, _tail);
// 4. 将元素插入指定优先级链表的尾部
InsertItemInPriorityChain(priorityItem, chain, chain.Tail);
return priorityItem;
}
/// <summary>
/// 从优先级队列出队,并获取数据
/// </summary>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
public T Dequeue()
{
int count = _priorityChains.Count;
if (count <= 0)
{
throw new InvalidOperationException();
}
// 1. 获取最高优先级的队首元素
PriorityItem<T> head = _priorityChains.Values[count - 1].Head;
// 2. 从队列移除队首元素
RemoveItem(head);
// 3. 返回队首元素的数据
return head.Data;
}
/// <summary>
/// 查询优先级队列的队首元素的数据
/// </summary>
/// <returns></returns>
public T Peek()
{
T obj = default(T);
int count = _priorityChains.Count;
if (count > 0)
{
obj = _priorityChains.Values[count - 1].Head.Data; // 获取最高优先级的队首元素的数据
}
return obj;
}
/// <summary>
/// 从优先级队列移除元素
/// </summary>
/// <param name="item"></param>
public void RemoveItem(PriorityItem<T> item)
{
PriorityChain<T> chain = item.Chain;
// 1. 从优先级链表中删除指定元素(删除优先级关系)
RemoveItemFromPriorityChain(item);
// 2. 从顺序链表中删除指定元素(彻底删除)
RemoveItemFromSequentialChain(item);
}
/// <summary>
/// 改变指定元素的优先级
/// </summary>
/// <param name="item"></param>
/// <param name="priority"></param>
public void ChangeItemPriority(PriorityItem<T> item, DispatcherPriority priority)
{
// 1. 从优先级链表中删除指定元素
RemoveItemFromPriorityChain(item);
// 2. 获取指定优先级的优先级链表
PriorityChain<T> chain = GetChain(priority);
// 3. 插入元素到指定的优先级队列
InsertItemInPriorityChain(item, chain);
}
/// <summary>
/// 获取指定优先级的优先级链表
/// </summary>
/// <param name="priority">指定的优先级</param>
/// <returns></returns>
private PriorityChain<T> GetChain(DispatcherPriority priority)
{
PriorityChain<T> chain = (PriorityChain<T>)null;
// 获取优先级链表总条数,若存在优先级链表,则获取链表
int count = _priorityChains.Count;
if (count > 0)
{
if (priority == (DispatcherPriority)_priorityChains.Keys[0])
{
chain = _priorityChains.Values[0];
}
else if (priority == (DispatcherPriority)_priorityChains.Keys[count - 1])
{
chain = _priorityChains.Values[count - 1];
}
else if (priority > (DispatcherPriority)_priorityChains.Keys[0] &&
priority < (DispatcherPriority)_priorityChains.Keys[count - 1])
{
_priorityChains.TryGetValue((int)priority, out chain);
}
}
// 若没找到优先级链表
// 1. 从缓存栈取一条链表,修改其优先级到指定优先级,返回
// 2. 若缓存栈中也没有,则新创建一条优先级链表实例
// 3. 将新的优先级链表(不论是从缓存栈获取还是新建的)添加到排序的优先级链表字典中
// 4. 返回结果
if (chain == null)
{
if (_cacheReusableChains.Count > 0)
{
chain = _cacheReusableChains.Pop();
chain.Priority = priority;
}
else
{
chain = new PriorityChain<T>(priority);
}
_priorityChains.Add((int)priority, chain);
}
return chain;
}
/// <summary>
/// 插入元素到指定的优先级队列
/// </summary>
/// <param name="item"></param>
/// <param name="chain"></param>
private void InsertItemInPriorityChain(PriorityItem<T> item, PriorityChain<T> chain)
{
if (chain.Head == null)
{
InsertItemInPriorityChain(item, chain, (PriorityItem<T>)null);
}
else
{
PriorityItem<T> sequentialPrev = item.SequentialPrev;
// 迭代(前向搜索)的停止条件
// case 1: 找到 item 所在顺序链表的头节点
// case 2: 找到一个节点,其所在的优先级链表就是要插入的优先级链表
while (sequentialPrev != null && sequentialPrev.Chain != chain)
{
sequentialPrev = sequentialPrev.SequentialPrev;
}
// 将元素插入到指定的优先级链表的指定元素之后
InsertItemInPriorityChain(item, chain, sequentialPrev);
}
}
/// <summary>
/// 将元素插入到指定的优先级链表的指定元素之后
/// </summary>
/// <param name="item">待插入的元素</param>
/// <param name="chain">优先级链表</param>
/// <param name="after"></param>
internal void InsertItemInPriorityChain(
PriorityItem<T> item,
PriorityChain<T> chain,
PriorityItem<T> after)
{
// 将待插入的元素加入到指定的优先级链表
item.Chain = chain;
// 若 after 为 null ,则插入到 head 前面
// 若 after 非 null ,则插入到 after 后面
if (after == null)
{
if (chain.Head != null)
{
chain.Head.PriorityPrev = item; // 1. 将 head 的前驱(Prev)设为 item
item.PriorityNext = chain.Head; // 2. 将 item 的后继(Next)设为 head
chain.Head = item; // 3. 将 item 设为头节点
}
else
{
chain.Head = chain.Tail = item;
}
}
else
{
// 将 item 的前驱(Prev)设置为 after (即表示将 item 插入到 after 后面)
item.PriorityPrev = after;
// 若 after 存在后继(Next), 则执行双向链表插入元素的常规逻辑
// 若 after 无后继,则将 after 的后继设为 item并将_tail设为尾节点
if (after.PriorityNext != null)
{
item.PriorityNext = after.PriorityNext; // 1.将 item 的后继(Next)设置为 after 的后继
after.PriorityNext.PriorityPrev = item; // 2.将 after 的后继的前驱设为 item
after.PriorityNext = item; // 3.将 after 的后继设为 item
}
else
{
after.PriorityNext = item;
chain.Tail = item;
}
}
++chain.Count;// 优先级链表
}
/// <summary>
/// 从优先级链表中删除指定元素
/// </summary>
/// <param name="item"></param>
private void RemoveItemFromPriorityChain(PriorityItem<T> item)
{
// 若有前驱(Prev),则执行链表删除元素的常规逻辑
// 若无前驱,则把后继(Next)设为优先级链表的头节点
if (item.PriorityPrev != null)
{
item.PriorityPrev.PriorityNext = item.PriorityNext; // 把前一个的 Next 置为 item 的 Next
}
else
{
item.Chain.Head = item.PriorityNext;
}
// 若有后继(Next),则把 item 的前驱(Prev)设置为后继的前驱
// 若无后继,则把 item 的前驱(Prev)设为优先级链表的尾节点
if (item.PriorityNext != null)
{
item.PriorityNext.PriorityPrev = item.PriorityPrev;
}
else
{
item.Chain.Tail = item.PriorityPrev;
}
// 将 item 的前驱和后继都置为null,并将优先级链表数量减一
item.PriorityPrev = item.PriorityNext = (PriorityItem<T>)null;
--item.Chain.Count;
// 若元素的优先级链表中节点数量为 0(即优先级链表无元素)
if (item.Chain.Count == 0)
{
// 若 item 所在的优先级链表的优先级是最高优先级,则删最后一个
// 否则,按通过字典的键删之
if (item.Chain.Priority == (DispatcherPriority)_priorityChains.Keys[_priorityChains.Count - 1])
{
_priorityChains.RemoveAt(_priorityChains.Count - 1);
}
else
{
_priorityChains.Remove((int)item.Chain.Priority);
}
// 若缓存容量小于10,则将 item 所在优先级链表压入链表缓存栈
if (_cacheReusableChains.Count < 10)
{
item.Chain.Priority = DispatcherPriority.Invalid;
_cacheReusableChains.Push(item.Chain);
}
}
// 将 item 指向的优先级队列的引用置为null
item.Chain = (PriorityChain<T>)null;
}
/// <summary>
/// 插入一个元素到顺序链表
/// </summary>
/// <param name="item">待插入的元素</param>
/// <param name="after"></param>
internal void InsertItemInSequentialChain(PriorityItem<T> item, PriorityItem<T> after)
{
// 若 after 为 null ,则插入到 head 前面
// 若 after 非 null ,则插入到 after 后面
if (after == null)
{
// 若存在头节点,将 item 插入到 head 前面
// 若头节点为 null,则待插入的元素作为头节点和尾节点
if (_head != null)
{
_head.SequentialPrev = item; // 1. 将 head 的前驱(Prev)设为 item
item.SequentialNext = _head; // 2. 将 item 的后继(Next)设为 head
_head = item; // 3. 将 item 设为头节点
}
else
{
_head = _tail = item;
}
}
else
{
// 将 item 的前驱(Prev)设置为 after (即表示将 item 插入到 after 后面)
item.SequentialPrev = after;
// 若 after 存在后继(Next), 则执行双向链表插入元素的常规逻辑
// 若 after 无后继,则将 after 的后继设为 item并将_tail设为尾节点
if (after.SequentialNext != null)
{
item.SequentialNext = after.SequentialNext; // 1.将 item 的后继(Next)设置为 after 的后继
after.SequentialNext.SequentialPrev = item; // 2.将 after 的后继的前驱设为 item
after.SequentialNext = item; // 3.将 after 的后继设为 item
}
else
{
after.SequentialNext = item;
_tail = item;
}
}
++_count;// 顺序链表数量加一
}
/// <summary>
/// 从顺序链表中删除指定元素
/// </summary>
/// <param name="item"></param>
private void RemoveItemFromSequentialChain(PriorityItem<T> item)
{
// 若有前驱(Prev),则把前一个的Next置位item的Next【链表删除元素的常规逻辑】
// 若无前驱,则把后继(Next)设为头节点
if (item.SequentialPrev != null)
{
item.SequentialPrev.SequentialNext = item.SequentialNext;
}
else
{
_head = item.SequentialNext;
}
// 若有后继(Next),则把item的前驱(Prev)设置为后继的前驱
// 若无后继,则把item的前驱(Prev)设为尾节点
if (item.SequentialNext != null)
{
item.SequentialNext.SequentialPrev = item.SequentialPrev;
}
else
{
_tail = item.SequentialPrev;
}
// 将item的前驱和后继都置为null,并将队列数量减一
item.SequentialPrev = item.SequentialNext = (PriorityItem<T>)null;
--_count;
}
}
}