# Dictionary
在 C# 中,Dictionary 是一種非常有用的資料結構,它提供了一種高效的方式來儲存和查找鍵值對。每個鍵值對由一個「鍵」(key) 和一個「值」(value) 組成,其中鍵是唯一的。這意味著在同一個 Dictionary 中,沒有兩個鍵是相同的,但可以有多個值是相同的。
### 你應該先知道
1. 鍵值對(Key-Value Pair)概念
什麼是鍵值對?
Dictionary 是一個儲存鍵值對(Key-Value Pair)的集合。這裡的「鍵」(Key)是唯一的標識符,用來存取對應的「值」(Value)。例如,在電話簿中,鍵可以是某人的名字,而值是這個人的電話號碼。
為什麼鍵必須唯一?
每個鍵都必須是唯一的,因為 Dictionary 是透過鍵來查找值的。如果有多個相同的鍵,系統就不知道應該返回哪個對應的值。相當於一個鎖只能有一把對應的鑰匙。如果試圖插入相同的鍵,通常會覆蓋原來的值。
鍵和值的應用
這樣的鍵值對概念非常靈活,可以用來解決許多問題,像是快速查詢某個項目的相關資料(如ID查找名字),或是根據不同條件儲存對應的設定或資料。
2. 雜湊函數(Hashing)基礎
雜湊函數的概念
Dictionary 背後的基礎技術是「雜湊函數」。雜湊函數是一種演算法,將任意長度的輸入(鍵)轉換成固定長度的輸出(通常是數字),這個輸出用來決定這個鍵在 Dictionary 內儲存的位置。
為什麼使用雜湊?
雜湊的好處是它能夠將鍵快速映射到一個內存地址,從而在常數時間內(O(1))進行查找、插入或刪除操作。這使得 Dictionary 比起線性結構(如陣列或 List)更具查找效率。
雜湊衝突的處理
當兩個不同的鍵通過雜湊函數產生相同的雜湊值時,會發生「雜湊衝突」。這是不可避免的,但 Dictionary 通常會採用某些方法來解決這個問題,比如鏈結法(在同一個位置儲存多個鍵值對)或開放定址法(找一個新的可用位置來儲存)。
3. 查找時間複雜度
O(1) 的操作效率
Dictionary 的最大優勢在於其查找、插入和刪除操作的時間複雜度是 O(1),也就是說,不管集合的大小如何,這些操作所需的時間都不會顯著增加。這是由於雜湊函數能夠迅速定位到所需的資料。
相較於其他資料結構的優勢
與陣列、List 或線性搜索相比,這種查找效率是巨大的進步。在這些線性結構中,搜索需要依次檢查每個元素(O(n)),隨著集合的增大,查找時間會線性增長,而 Dictionary 的查找時間則幾乎不受集合大小的影響。
實際應用的意義
這種快速查找對於處理大量資料或需要頻繁查找操作的應用來說非常重要,例如儲存和查找用戶資料、查詢設定或儲存大型數據集中的對應關係。
4. 泛型(Generics)
什麼是泛型?
Dictionary<TKey, TValue> 使用了泛型,這意味著在使用時可以為鍵和值指定不同的資料型別。泛型的好處在於可以避免指定特定型別,讓程式碼更靈活,同時提供了編譯時的型別檢查,以防止在運行時遇到不匹配的型別錯誤。
鍵和值的型別選擇
例如,鍵可以是 string,而值可以是 int、string 或自定義的資料結構。這樣的設計允許 Dictionary 可以應對多樣化的應用場景。例如,使用 Dictionary<string, int> 可以將商品名稱(string)映射到其價格(int),或使用 Dictionary<int, string> 儲存學生ID與名字的對應。
泛型的安全性和性能優勢
泛型的引入讓程式在使用 Dictionary 時不需要進行型別轉換,這不僅提高了程式的可讀性,也增加了運行效率,減少了由於不正確型別引發的錯誤。
5. 例外處理(Exception Handling)
常見的例外情況
使用 Dictionary 時,常見的例外情況是查找一個不存在的鍵時引發的 KeyNotFoundException。這是在嘗試存取一個沒有加入到 Dictionary 中的鍵時發生的。
如何避免例外發生?
一個常見的防範措施是使用 TryGetValue() 方法。這個方法會嘗試查找指定的鍵,如果找到該鍵,會返回對應的值;如果沒有找到該鍵,則不會拋出例外,而是返回 false。這讓程式更安全且不易中斷。
例外處理的重要性
在處理大型資料集或頻繁查找操作時,合理的例外處理可以防止程式因一個小錯誤而崩潰。學會如何捕捉和處理例外,使得程式更加健壯,尤其是在涉及使用者輸入或動態資料時。
---
### 主要特點
1. 鍵和值的對應:
每個鍵都對應一個值。鍵用來唯一標識每個值。
值可以是任何類型的數據,而鍵通常是可以用來比較的類型,如 int、string 或自定義的類型。
2. 快速查找:
Dictionary 使用哈希表來儲存鍵值對,這使得根據鍵快速查找對應的值成為可能。一般來說,查找操作的時間複雜度為 O(1),即常數時間。
3. 不允許重複的鍵:
在 Dictionary 中,鍵必須是唯一的。如果嘗試插入具有相同鍵的項目,舊的值將被新值覆蓋。
4. 動態擴展:
Dictionary 會根據需要自動擴展,以容納更多的鍵值對。這樣用戶不必擔心預先分配足夠的空間。
5. 支持遍歷:
可以遍歷 Dictionary 中的所有鍵值對,通常使用 foreach 迴圈來完成。
6. 查詢和操作:
提供了多種方法來查詢、添加、更新和移除鍵值對。例如,TryGetValue 用於安全地查找值,Remove 用於刪除鍵值對。
### 使用情境
Dictionary 非常適合用於需要快速查找、插入或更新數據的場景,比如:
* 儲存用戶信息(例如,使用用戶ID作為鍵)。
* 處理配置設定(例如,使用設定名稱作為鍵)。
* 管理對象屬性(例如,使用屬性名稱作為鍵)。
### 例子
```
using System;
class Program
{
static void Main(string[] args)
{
/*
// 創建一個 Dictionary,鍵是 int,值是 string
// 使用 KeyValuePair 的方式添加元素
Dictionary<int, string> names = new Dictionary<int, string>
{
{1, "Aba"}, // 添加鍵值對 1 -> "Aba"
{2, "Test"}, // 添加鍵值對 2 -> "Test"
{3, "TEST"} // 添加鍵值對 3 -> "TEST"
};
// for遍歷
// 使用 ElementAt(i) 來取得第 i 個元素
for (int i = 0; i < names.Count; i++)
{
// 透過 ElementAt(i) 方法來取得字典中的第 i 個 KeyValuePair
KeyValuePair<int, string> pair = names.ElementAt(i);
Console.WriteLine($"Key is {pair.Key} Value is {pair.Value}");
}
Console.WriteLine();
// foreach遍歷
foreach (KeyValuePair<int, string> pair in names)
{
Console.WriteLine($"Key is {pair.Key} Value is {pair.Value}");
}
*/
// 創建一個 Dictionary,鍵和值的類型都是 string
// 初始包含兩個鍵值對:{"Math", "Aba"} 和 {"Science", "Test"}
Dictionary<string, string> teachers = new Dictionary<string, string>
{
{"Math", "Aba"}, // 鍵 "Math" 對應的值是 "Aba"
{"Science", "Test"} // 鍵 "Science" 對應的值是 "Test"
};
// 試圖訪問鍵為 "math" 的值
// 因為 Dictionary 是區分大小寫的,所以這會產生錯誤
// Console.WriteLine(teachers["math"]); // error: math != Math
// 使用 TryGetValue 方法安全地檢查是否存在鍵 "Math"
// 如果鍵存在,則將對應的值存儲在 result 變量中,並輸出
// 否,則輸出 "Math not found"
if (teachers.TryGetValue("Math", out string result))
{
Console.WriteLine(result); // 輸出 "Aba"
// 更改鍵為 "Math" 的值為 "Joe"
teachers["Math"] = "Joe";
}
else
{
Console.WriteLine("Math not found"); // 如果鍵 "Math" 不存在則輸出此消息
}
// teachers.Remove("Math");
// 使用 foreach 迴圈遍歷字典,並輸出每個鍵值對
foreach (var item in teachers)
{
// 輸出格式為 "鍵 - 值"
Console.WriteLine($"{item.Key} - {item.Value}");
}
}
}
```