前言
以前我學 Collection 的方式,就是背:
- List 是動態陣列
- Dictionary 是 Hash Table
- Queue 是 FIFO
- Stack 是 LIFO
但一直有一個問題沒有答案。
既然 Array 已經可以存很多資料了,為什麼 .NET 還要設計這麼多 Collection?
如果世界只有 Array
假設 .NET 只有 Array。
Student[] students = new Student[100];
其實很多事情都能做,例如:
- 存放資料
- 修改資料
- 使用索引快速取得資料
Array 還有一個非常大的優點。取出指定Array元素
students[50]
時間複雜度是
O(1)
但是Array當然會有缺點。
資料量變多
假設:
Student[] students = new Student[100];
突然第 101 個學生來了。
Array 並不能自己變大,只能:
建立新的 Array
↓
Copy 所有資料
↓
丟掉舊的 Array
這時我開始思考。
如果有一個容器可以自己長大,不就好了?
於是就有了List<T>
這也是我現在對 List 最簡單的理解。
List = 會自己擴容的 Array。
查詢資料
假設有一百萬個學生。
我要找:
Tony
如果只有 Array。
只能:
Jack?
↓
不是
↓
Amy?
↓
不是
↓
Bob?
↓
...
運氣不好。
可能要找一百萬次。
因此我開始想:
有沒有辦法不要從第一筆開始找?
於是就有了Dictionary<TKey, TValue>
它利用 Hash,先找到 Bucket,再找到真正的資料。
因此平均查找速度變成O(1)
所以我現在的理解是:
Dictionary = 用空間換取搜尋速度。
排隊情境
假設我要做:
- 超商叫號
- 印表機
- 訊息佇列
資料一定是:
先進先出
如果使用 Array
- 刪掉第一個元素
- 後面的資料都要往前搬
Array 做得到沒錯,但是效率很差。
所以.NET 才提供Queue<T>專門處理 FIFO。
回到上一個狀態
例如:
- Undo
- Browser Back
- Function Call
這些都有一個共同特性。
最後放進去的
↓
最先拿出來
這就是:
LIFO
因此.NET 才提供Stack<T>專門處理 LIFO。
如果資料不能重複
例如:
Jack
Amy
Jack
Array 並不會阻止重複。
如果我要知道Jack 有沒有存在,還是只能自己搜尋。
因此.NET 又提供HashSet<T>專門處理不允許重複的資料。
我後來才理解
以前我的腦中是:
Collection
↓
List
Dictionary
Queue
Stack
HashSet
全部都是不同的資料結構,現在我的理解變成:
Array
│
├── 大小不固定
│ ↓
│ List
│
├── 搜尋太慢
│ ↓
│ Dictionary
│
├── FIFO
│ ↓
│ Queue
│
├── LIFO
│ ↓
│ Stack
│
├── 不允許重複
│ ↓
└── HashSet
Collection 並不只是很多資料結構,是為了針對解決某一種資料管理問題而產生的設計選擇。
我的心法
不要先背 Collection。
先問自己:
我現在遇到的是什麼問題?
因為:
問題,決定了該使用哪一種 Collection。
而不是:
哪一種 Collection 比較厲害。
快速複習
| 遇到的問題 | 解決方式 |
|---|---|
| Array 大小固定 | List |
| 搜尋效率太差 | Dictionary |
| 先進先出 | Queue |
| 後進先出 | Stack |
| 不允許重複 | HashSet |