.NET6 中的 PriorityQueue
.NET6 中的 PriorityQueue
Intro
.NET 6 中引入了一個新的集合類型 PriorityQueue,正如它的名字那樣,在普通的 Queue 基礎之上增加了優先級的支持,接下來就一起來看一下怎么使用,以及一些常用的使用場景介紹。
Get Started
來看一個簡單的使用示例:
var?priorityQueue?=?new?PriorityQueue<string,?int>(); priorityQueue.Enqueue("Alice",?100); priorityQueue.EnqueueRange(Enumerable.Range(1,?5).Select(x?=>?($"X_{x}",?100?-?x)) );while?(priorityQueue.TryDequeue(out?var?element,?out?var?priority)) {Console.WriteLine($"Element:{element},?{priority}"); }輸出示例:
可以看到輸出的順序和我們添加的順序是相反的, PriorityQueue 在 Dequeue 的時候是從優先級最小開始的,值越小優先級越高,優先級越高越優先輸出,如果我們希望最大先輸出是不是可以呢,答案是肯定的,只是我們需要指定我們自己的優先級比較規則就可以了,可以參考后面的示例
Scenes
借助帶優先級的自動排序,我們可以做一些自動排序的需求的時候可以考慮使用 PriorityQueue
Message Queue
借助 PriorityQueue 可以來實現一個優先級消息隊列,允許用戶在發消息的時候指定一個消息的優先級,在消費的時候就會按優先級
var?random?=?new?Random();var?queue?=?new?PriorityQueue<string,?int>(); for?(var?i?=?1;?i?<=?10;?i++) {queue.Enqueue($"Message_{i}",?random.Next(10,?100)); }while?(queue.TryDequeue(out?var?message,?out?_)) {Console.WriteLine(message); }在上面的示例里我們默認指定了一個 int 作為 Priority 的類型,并入隊了一些消息,但是往往會有很多的消息,可能會出現優先級相同的情況,我們可以使用時間和int 作為一個聯合的 Priority 類型,可以參考下面的示例:
var?random?=?new?Random();var?queue?=?new?PriorityQueue<string,?(DateTime?time,?int?priority)>(new?DateTimePriorityComparer());var?time?=?DateTime.UtcNow; Thread.Sleep(1000); for?(var?k?=?0;?k?<?3;?k++) {for?(var?i?=?1;?i?<=?3;?i++){queue.Enqueue($"Message_{i}_{k}",?(i?>?2???time?:?DateTime.UtcNow,?random.Next(5,?10)));} }while?(queue.TryDequeue(out?var?message,?out?var?priority)) {Console.WriteLine($"{message},?{priority.priority},?{priority.time}"); }輸出示例如下:
由上面的結果可以看出來,在 priority一樣的情況下,我們會先處理時間較小的消息,也可以根據自己的需要進行定制排序方式,自定義 Priority 比較邏輯即可,上面的自定義優先級比較代碼如下:internal?class?DateTimePriorityComparer?:?IComparer<(DateTime?time,?int?priority)> {public?int?Compare((DateTime?time,?int?priority)?x,?(DateTime?time,?int?priority)?y){var?priorityComparison?=?x.priority.CompareTo(y.priority);if?(priorityComparison?!=?0)?return?priorityComparison;return?x.time.CompareTo(y.time);} }
Rank
在很多做排行榜的應用中也可以使用 PriorityQueue 來實現,比如我們來做一個學生成績的排名
來看下面的示例代碼:
var?list?=?new?List<(string?name,?int?score)> {("Mike",?99),("Ming",?100),("Jane",?96),("Yi",?94),("Harry",?90), };var?priorityQueue?=?new?PriorityQueue<string,?int>(); priorityQueue.EnqueueRange(list);Console.WriteLine("--Unordered:"); foreach?(var?item?in?priorityQueue.UnorderedItems) {Console.WriteLine($"Name:{item.Element},?score:{item.Priority}"); }Console.WriteLine("--Ordered:"); while?(priorityQueue.TryDequeue(out?var?name,?out?var?score)) {Console.WriteLine($"Name:{name},?score:{score}"); }Console.WriteLine("-----TOP?3"); priorityQueue?=?new?PriorityQueue<string,?int>(new?High2LowComparer()); priorityQueue.EnqueueRange(list); var?rank?=?0; while?(rank++?<?3?&&?priorityQueue.TryDequeue(out?var?name,?out?var?score)) {Console.WriteLine($"Rank({rank}):Name:{name},?score:{score}"); }上面的 list 就是一個成績清單,隨便寫了幾個測試數據,通過 PriorityQueue 的 UnorderedItems 我們可以拿到排序之前的數據,也是我們加入隊列(Enqueue)的順序,默認的比較是小的在前,也就是成績低的在前,那我們想按從大到小排序的話就需要自定義比較方式。
上面的 High2LowComparer 就是一個自定義的比較,其實就是把比較的結果取了一個反,代碼如下:
internal?class?High2LowComparer?:?IComparer<int> {public?int?Compare(int?x,?int?y){return?y.CompareTo(x);} }上面的輸出結果如下:
More
Redis 里有一個 zset(sortedSet) 類型的數據也可以做類似的事情,但是 zset 和 PriorityQueue 還是有一些不同的,zset 是一個 set,是一個自動去重的集合,而 PriorityQueue 還是一個 Queue 不會去重,zset 可以修改對應元素的 priority(score),但是 PriorityQueue 目前不支持修改元素對應的優先級
PriorityQueue ?可以解決我們的一些問題,但是有一些使用要注意的地方:
首先如果 Priority 是一樣的話,輸出的順序是有可能不一樣的,這是由它內部的實現算法決定的,不能嚴格的保證順序
PriorityQueue 是非線程安全的,需要注意線程安全問題
PriorityQueue 中的 Peek 方法只會獲取 queue 里即將出隊的元素,但是不會從隊列中移除這個元素
References
- https://devblogs.microsoft.com/dotnet/announcing-net-6-preview-2/ 
- https://github.com/dotnet/runtime/blob/v6.0.0-preview.2.21154.6/src/libraries/System.Collections/src/System/Collections/Generic/PriorityQueue.cs 
- https://github.com/WeihanLi/SamplesInPractice/blob/master/net6sample/PriorityQueueSample/Program.cs 
總結
以上是生活随笔為你收集整理的.NET6 中的 PriorityQueue的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: C#通过工厂模式,我把一大堆if干掉了
- 下一篇: BeetleX 之 WebApi网关1.
