簡介
PooledList<T> 是 高性能集合類型,由 Collections.Pooled 提供,用於替代 List<T>,通過 對象池 (ArrayPool<T>) 複用內部數組來減少 GC(垃圾回收)壓力。
⚡ 核心目標:
在需要頻繁創建/銷燬 List<T> 的場景下,PooledList<T> 通過數組租借與歸還的機制避免頻繁分配內存,從而提升性能並降低 GC 負擔。
安裝
dotnet add package Collections.Pooled --version 1.0.82
添加命名空間
using Collections.Pooled;
特點
- 數組池化:內部數組從
ArrayPool<T>.Shared(默認)或自定義池中租借,減少分配。 Span<T>支持:提供Span屬性,直接訪問內部數組的填充部分,支持零拷貝操作。IDisposable實現:調用Dispose()時,返回內部數組到池中(不調用Dispose不會出錯,但會降低池化效果)。- 擴展方法:如
TryFind、TryFindLast(替換標準Find、FindLast,返回bool以避免異常)。 - 添加/插入
Span:AddSpan和InsertSpan方法返回一個Span<T>,允許直接寫入內部存儲,而無需多次調用Add。 -
構造函數選項:
- 支持指定自定義
ArrayPool<T>。 clearMode參數控制數組返回池時是否清除內容(默認自動)。sizeToCapacity參數使初始Count == Capacity,適合值類型避免不必要的零初始化。
- 支持指定自定義
ToPooledList()擴展:從IEnumerable<T>快速創建PooledList<T>。- 性能提升:在高頻操作中,減少
GC觸發,尤其適合循環中創建臨時列表的場景。
內部原理
普通 List<T> 內部
List<T>內部維護一個T[]數組。- 當容量不足時會 申請更大數組 並 拷貝數據。
- 對象銷燬後,這些數組最終交給
GC回收。
PooledList<T> 內部
- 內部數組不是直接
new出來的,而是從ArrayPool<T>.Shared租借。 - 使用結束時通過
Dispose()方法 歸還數組,供下次複用。 - 避免頻繁分配和回收大數組,降低
GC Gen2壓力。
基本用法
創建與釋放
using Microsoft.Toolkit.HighPerformance.Buffers;
using var list = new PooledList<int>(); // 自動使用 ArrayPool<int>
list.Add(1);
list.Add(2);
list.Add(3);
foreach (var item in list)
{
Console.WriteLine(item);
}
// Dispose() 會自動歸還數組
💡 推薦使用 using 確保 Dispose() 被調用,否則不會歸還數組,造成內存浪費。
初始容量
using var list = new PooledList<int>(initialCapacity: 1024);
轉換為 Span<T> / Memory<T>
PooledList<T> 的優勢之一是可以直接獲取底層內存:
Span<int> span = list.AsSpan();
Memory<int> memory = list.AsMemory();
這樣可以高效地與 Span/Memory API 交互,避免額外拷貝。
常用操作
與 List<T> 基本一致:
list.Add(10);
list.AddRange(new[] { 20, 30, 40 });
list.Insert(1, 15);
list.RemoveAt(0);
list.Clear();
Console.WriteLine(list.Count);
Console.WriteLine(list.Capacity);
與 List<T> 對比
| 特性 | List<T> |
PooledList<T> |
|---|---|---|
| 內存分配 | 每次擴容 new 新數組 |
從 ArrayPool<T> 租借,複用數組 |
| GC 壓力 | 大量頻繁創建/銷燬時 GC 壓力大 | 減少 GC Gen2 壓力 |
| 釋放方式 | 依賴 GC | 必須 Dispose() 歸還數組 |
| 性能(頻繁操作場景) | 可能產生大量堆分配 | 高性能、低分配 |
| Span/Memory 支持 | 需要 AsSpan() 擴展 |
內置 AsSpan、AsMemory,零拷貝訪問 |
| 適用場景 | 通用集合 | 高性能、臨時性數據集合(網絡、序列化、算法) |
高級用法
與 ArrayPool<T> 配合
using var list = new PooledList<byte>(ArrayPool<byte>.Shared, 2048);
可以傳入自定義池,比如為特殊場景優化的 ArrayPool<T>。
與 Span<T> 高效處理
適合序列化/反序列化:
Span<byte> buffer = list.AsSpan();
ProcessBuffer(buffer); // 直接操作底層數組,無需複製
搜索和擴展
var list = new PooledList<string> { "apple", "banana", "cherry" };
bool found = list.TryFind(s => s.StartsWith("b"), out string result);
Console.WriteLine(found ? result : "Not found"); // 輸出: banana
var pooledFromEnumerable = Enumerable.Range(1, 5).ToPooledList(); // 擴展方法
Console.WriteLine(string.Join(", ", pooledFromEnumerable)); // 輸出: 1, 2, 3, 4, 5
pooledFromEnumerable.Dispose();
TryFind和TryFindLast:返回bool和out值,避免null檢查。
注意事項與最佳實踐
必須調用 Dispose()
- 否則不會歸還數組,導致內存泄漏。
- 推薦
using塊。
不要長期持有 Span/Memory
- 因為數組歸還池後可能被其他線程複用。
適用場景
- 高頻率、大數據臨時集合。
- 網絡協議解析、日誌聚合、臨時緩存。
- 需要
Span訪問的場景。
不適合場景
- 長期持有的全局集合。
- 數據量小且生命週期長的普通集合。