面對大量資料集合遍歷,究竟選 Dictionary 還是 List?本文從底層儲存原理、執行緒安全性、實測效能三個維度深度剖析,並給出具體選型建議。
| 特性 | Dictionary<TKey,TValue> | List<T> |
|---|---|---|
| 底層結構 | 雜湊表(Hash Table) | 動態陣列 |
| 查找複雜度 | O(1) 平均 | O(n) |
| 遍歷順序 | 插入順序(.NET Core 保證) | 索引順序 |
| 執行緒安全 | 非安全 | 非安全 |
| 記憶體開銷 | 較大(桶陣列 + Entry 連結) | 較小(連續陣列) |
// Dictionary 保持插入順序遍歷
var dic = new Dictionary();
dic.Add(1,5); dic.Add(10,3); dic.Add(2,5);
foreach(var k in dic.Keys) Console.WriteLine(k); // 1, 10, 2
// Hashtable 打亂順序
var ht = new Hashtable();
ht.Add(1,5); ht.Add(10,3); ht.Add(2,5);
foreach(var k in ht.Keys) Console.WriteLine(k); // 順序不定
Hashtable 內部使用 Thread.BeginCriticalRegion() 提供寫入鎖定,屬舊版相容產品;Dictionary 無同步機制,效能更高。
Key 經 GetHashCode() 計算雜湊碼,模桶數量決定桶索引。衝突時以 Entry 結構體形成單向鏈表(開放定址法)。查找流程:
1. 計算 Key 雜湊碼 → 桶索引
2. 遍歷該桶鏈表,EqualityComparer<TKey>.Default.Equals 比對 Key
3. 命中回傳 Value
此為 Key 必須唯一且不可變的根本原因。
var dic = new Dictionary();
var list = new List>();
var r = new Random();
for(int i=0; i<100000; i++) {
string k = i.ToString(), v = r.Next(10).ToString();
dic.Add(k, v);
list.Add(new KeyValuePair(k, v));
}
// Dictionary 遍歷
var sw = Stopwatch.StartNew();
var sb = new StringBuilder();
foreach(var kv in dic) sb.Append(kv.Value);
sw.Stop();
Console.WriteLine($"Dictionary: {sw.ElapsedMilliseconds} ms");
// List 遍歷
sw.Restart(); sb.Clear();
foreach(var kv in list) sb.Append(kv.Value);
sw.Stop();
Console.WriteLine($"List: {sw.ElapsedMilliseconds} ms");
典型結果(Release 模式,x64):
Dictionary: ~12 ms | List: ~8 ms
分析:
- List 連續記憶體,CPU 快取命中率極高,遍歷最快。
- Dictionary 需遍歷桶陣列 + Entry 鏈,指令較多,但差距在百萬級以下可忽略。
- 若需頻繁 Key 查找,Dictionary 綜合效能完勝。
Dictionary / HashSetList / 陣列ConcurrentDictionary / ImmutableDictionary / 鎖保護LinkedList / OrderedDictionaryDictionary.TryGetValue 避免雙重查找。new Dictionary(capacity) 減少擴容。