- Array và List đều là những kiểu dữ liệu được sử dụng nhiều và phổ biến
- Có kích thước cố định
- Truy cập phần tử nhanh (O(1))
- Không có sẵn phương thức thêm/xoá phần tử
- Read more docs
int[] numbers = new int[3] { 1, 2, 3 };
Debug.Log(numbers[0]); // 1
- Kích thước linh hoạt
- Thêm/xoá các phần tử dễ dàng
- Tìm kiếm phần tử chậm hơn HashSet.
- Read more docs
List<string> players = new List<string>() { "Alice", "Bob" };
players.Add("Charlie");
Debug.Log(players[1]); // Bob
1.3. Lưu ý khi sử dụng vòng lặp for/foreach (Read more)
- Đối với
Array
, vòng lặp foreach đôi khi có thể nhanh như, hoặc thậm chí nhanh hơn, vòng lặp for. Điều này là do trình biên dịch C# tối ưu hóa foreach cho mảng, khiến nó gần như tương đương với vòng lặp for. Vì mảng có kích thước cố định và các phần tử của chúng được lưu trữ trong một khối bộ nhớ liền kề, trình biên dịch có thể tạo mã IL (Ngôn ngữ trung gian) rất hiệu quả cho foreach trên mảng, thường khiến nó hiệu quả như vòng lặp for. - Đối với
List
, vòng lặp for thường nhanh hơn foreach khi lặp qua các đối tượng List. Điều này là do foreach tạo ra một đối tượng Enumerator, có thể thêm một chút chi phí, trong khi for truy cập trực tiếp các phần tử theo chỉ mục. Việc phân bổ thêm này trong foreach có thể ảnh hưởng một chút đến hiệu suất, đặc biệt là trong các tình huống mà chi phí thu gom rác (GC) là một mối quan tâm. - Tóm lại:
- Sử dụng for với Lists: Đối với các trường hợp bạn đang lặp lại các danh sách lớn hoặc có mã nhạy cảm về hiệu suất, for thường được ưu tiên cho List. Điều này có thể tránh việc phân bổ enumerator bổ sung đi kèm với foreach.
- Sử dụng foreach với Mảng: Nếu bạn đang lặp qua các mảng, foreach thường có hiệu suất ngang bằng với for và có thể giúp mã của bạn dễ đọc hơn.
- HashSet rất hữu ích khi bạn cần lưu các phần tử duy nhất và thực hiện các tháo tác tập hợp hiệu quả
- Read more docs
HashSet<string> enemyNames = new HashSet<string>();
enemyNames.Add("Zombie");
enemyNames.Add("Skeleton");
enemyNames.Add("Orc");
// Thêm phần tử trùng lặp sẽ không có tác dụng
enemyNames.Add("Zombie"); // Không được thêm vào
- Hiệu suất cao: Các thao tác Contains, Add, Remove có độ phức tạp O(1)
- Tự động loại bỏ trùng lặp: Đảm bảo mọi phần tử là duy nhất
Tính năng | HashSet | List |
---|---|---|
Tốc độ tìm kiếm | O(1) | O(n) |
Cho phép trùng lặp | Không | Có |
Thứ tự phần tử | Không đảm bảo | Được đảm bảo |
Tốc độ thêm/xóa | Nhanh | Chậm hơn (đặc biệt khi xóa) |
==> Tóm lại: HashSet là sự lựa chọn tuyệt vời khi bạn cần lưu trữ các phần tử duy nhất và thường xuyên kiểm tra sự tồn tại của phần tử
- Là cấu trúc dữ liệu cho phép lưu trữ dữ liệu theo dạng cặp
key-value
với hiệu suất truy xuất cao - Read more docs
using System.Collections.Generic;
// Khởi tạo Dictionary cơ bản
Dictionary<string, int> playerScores = new Dictionary<string, int>();
// Khởi tạo với giá trị ban đầu
Dictionary<string, string> weaponTypes = new Dictionary<string, string>() {
{"sword", "melee"},
{"bow", "ranged"},
{"staff", "magic"}
};
playerScores.Add("Player1", 100);
playerScores.Add("Player2", 150);
// Hoặc sử dụng indexer
playerScores["Player3"] = 200;
int score = playerScores["Player1"]; // Lấy giá trị
// Kiểm tra tồn tại key trước khi truy cập
if (playerScores.ContainsKey("Player2")) {
Debug.Log("Score của Player2: " + playerScores["Player2"]);
}
- Truy xuất nhanh: Độ phức tạp O(1) cho các thao tác tìm kiếm, thêm, xóa
- Tổ chức linh hoạt: Dễ dàng ánh xạ giữa các đối tượng game
- Hiệu suất cao: Lý tưởng cho hệ thống truy câp nhanh như inventory, player data, ...
- Key phải là duy nhất: Mỗi key chỉ xuất hiện một lần trong Dictionary
- Key không được null: Sẽ gây ra exception nếu cố gắng sử dụng null key
- Kiểm tra tồn tại: Luôn kiểm tra ContainsKey trước khi truy cập để tránh KeyNotFoundException
- Hiệu suất: Dictionary chiếm nhiều bộ nhớ hơn List/Array nhưng truy xuất nhanh hơn
Tính năng | Dictionary<TKey,TValue> | List | Array (T[]) | HashSet |
---|---|---|---|---|
Truy cập bằng key | ✅ O(1) | ❌ Không hỗ trợ | ❌ Không hỗ trợ | ❌ Chỉ kiểm tra tồn tại |
Truy cập bằng index | ❌ Không hỗ trợ | ✅ O(1) | ✅ O(1) | ❌ Không hỗ trợ |
Tìm kiếm phần tử | ✅ O(1) (by key) | ✅ O(1) | ||
Thêm phần tử | ✅ O(1) | ✅ O(1)* | ❌ Kích thước cố định | ✅ O(1) |
Xóa phần tử | ✅ O(1) | ❌ Không hỗ trợ | ✅ O(1) | |
Lưu trữ key-value | ✅ Có | ❌ Không | ❌ Không | ❌ Chỉ lưu giá trị |
Cho phép trùng lặp | ❌ Key phải duy nhất | ✅ Có | ✅ Có | ❌ Giá trị duy nhất |
Bảo toàn thứ tự | ❌ Không đảm bảo | ✅ Có | ✅ Có | ❌ Không đảm bảo |
Tốt cho | Tra cứu nhanh bằng key | Danh sách có thứ tự | Dữ liệu cố định | Kiểm tra tồn tại nhanh |
*Ghi chú:
- ✅ = Hỗ trợ tốt
⚠️ = Có thể chậm với dữ liệu lớn- ❌ = Không hỗ trợ
- O(1)* với List: O(1) nếu Capacity chưa đầy, O(n) khi cần mở rộng
==> Tóm lại: Dictionary là công cụ mạnh mẽ để quản lý dữ liệu trong Unity, đặc biệt khi bạn cần ánh xạ giữa các định danh và giá trị với hiệu suất cao.
- Queue là cấu trúc dữ liệu hàng đợi (FIFO - First In First Out), nghĩa là phần tử nào được thêm vào trước thì sẽ được lấy ra trước. Nó hữu ích khi bạn cần quản lý một danh sách các hành động, sự kiện hoặc nhiệm vụ cần xử lý theo thứ tự.
- Read more docs
Queue<int> queue = new Queue<int>(); // Khởi tạo queue chứa số nguyên
void Add(){
queue.Enqueue(1);
queue.Enqueue(2);
queue.Enqueue(3);
}
int Get(){
int firstElement = queue.Dequeue(); // Lấy ra 1 và xóa khỏi hàng đợi
return firstElement; // kết quả trả về bằng 1
}
int Peek(){
int front = queue.Peek(); // Xem phần tử đầu tiên nhưng không xóa
return front
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TaskManager : MonoBehaviour
{
Queue<string> taskQueue = new Queue<string>();
void Start()
{
// Thêm nhiệm vụ vào hàng đợi
taskQueue.Enqueue("Nhặt vũ khí");
taskQueue.Enqueue("Tấn công kẻ địch");
taskQueue.Enqueue("Thu thập vật phẩm");
StartCoroutine(ProcessTasks());
}
IEnumerator ProcessTasks()
{
while (taskQueue.Count > 0)
{
string currentTask = taskQueue.Dequeue();
Debug.Log("Thực hiện nhiệm vụ: " + currentTask);
yield return new WaitForSeconds(2); // Giả lập thời gian thực hiện nhiệm vụ
}
Debug.Log("Hoàn thành tất cả nhiệm vụ!");
}
}
📌 Giải thích:
- Danh sách nhiệm vụ được quản lý bằng Queue.
- Dùng Coroutine để thực hiện nhiệm vụ tuần tự (mỗi nhiệm vụ mất 2 giây).
- Khi taskQueue.Count == 0, tất cả nhiệm vụ đã hoàn thành.
- Quản lý danh sách nhiệm vụ tuần tự (như AI, NPC thực hiện hành động theo thứ tự).
- Xử lý hàng đợi sự kiện (như log chat, thông báo hệ thống).
- Tạo hệ thống Pooling (quản lý đối tượng tái sử dụng để tối ưu hiệu năng).
- Tính toán đường đi AI (bằng BFS - Breadth First Search).
- Stack là cấu trúc dữ liệu ngăn xếp (LIFO - Last In First Out), tức là phần tử nào được thêm vào sau sẽ được lấy ra trước. Nó thích hợp sử dụng để quản lý các trạng thái, hệ thống Undo/Redo hoặc giải quyết bài toán đệ quy.
- Read more docs
Stack<int> stack = new Stack<int>(); // Khởi tạo stack chứa số nguyên
void Push(){
stack.Push(10);
stack.Push(20);
stack.Push(30);
}
int Pop(){
int lastElement = stack.Pop(); // Lấy ra 30 và xóa khỏi stack
return lastElement;
}
int Peek(){
int topElement = stack.Peek(); // Xem phần tử trên cùng nhưng không xóa
return topElement;
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class UndoRedoManager : MonoBehaviour
{
Stack<string> undoStack = new Stack<string>();
Stack<string> redoStack = new Stack<string>();
void Start()
{
PerformAction("Di chuyển lên");
PerformAction("Di chuyển phải");
PerformAction("Tấn công");
Undo();
Undo();
Redo();
}
void PerformAction(string action)
{
undoStack.Push(action);
redoStack.Clear(); // Khi thực hiện hành động mới, không thể Redo nữa
Debug.Log("Hành động: " + action);
}
void Undo()
{
if (undoStack.Count > 0)
{
string lastAction = undoStack.Pop();
redoStack.Push(lastAction);
Debug.Log("Undo: " + lastAction);
}
else
{
Debug.Log("Không thể Undo!");
}
}
void Redo()
{
if (redoStack.Count > 0)
{
string lastUndoneAction = redoStack.Pop();
undoStack.Push(lastUndoneAction);
Debug.Log("Redo: " + lastUndoneAction);
}
else
{
Debug.Log("Không thể Redo!");
}
}
}
📌 Giải thích:
- undoStack lưu trữ các hành động đã thực hiện.
- redoStack lưu trữ các hành động đã Undo để có thể Redo.
- Khi thực hiện hành động mới, redoStack bị xóa để tránh Redo lỗi.
Đặc điểm | Stack<T> (LIFO) |
Queue<T> (FIFO) |
---|---|---|
Cách hoạt động | Lấy phần tử cuối cùng trước | Lấy phần tử đầu tiên trước |
Phương thức chính | Push() , Pop() , Peek() |
Enqueue() , Dequeue() , Peek() |
Ứng dụng | Hệ thống Undo/Redo, đệ quy, duyệt cây | Quản lý hàng đợi, xử lý nhiệm vụ tuần tự |
- Hệ thống Undo/Redo (ví dụ: chỉnh sửa bản đồ, hành động nhân vật).
- Duyệt cây đệ quy (DFS - Depth First Search).
- Hệ thống xử lý trạng thái nhân vật (State Machine).
- Quản lý lịch sử hành động của AI.
- Là một cấu trúc dữ liệu giúp quản lý danh sách liên kết 2 chiều
- Read more docs
- Có nhiều cách để thêm phần tử
LinkedList<int> numbers = new LinkedList<int>();
// Thêm vào đầu danh sách
numbers.AddFirst(10);
// Thêm vào cuối danh sách
numbers.AddLast(20);
// Thêm một phần tử sau một node cụ thể
LinkedListNode<int> node = numbers.AddLast(30);
numbers.AddAfter(node, 40);
// Thêm một phần tử trước một node cụ thể
numbers.AddBefore(node, 25);
- Sử dụng foreach
foreach (int number in numbers)
{
Debug.Log(number);
}
- Sử dụng while thông qua LinkedListNode
LinkedListNode<int> current = numbers.First;
while (current != null)
{
Debug.Log(current.Value);
current = current.Next;
}
- Có nhiều cách để xóa phần tử
numbers.Remove(25); // Xóa giá trị cụ thể
numbers.RemoveFirst(); // Xóa phần tử đầu tiên
numbers.RemoveLast(); // Xóa phần tử cuối cùng
numbers.Clear(); // Xóa toàn bộ danh sách
- Quản lý danh sách hành động của AI (các hành động liên tục như di chuyển, tấn công).
- Quản lý đối tượng trong game (danh sách quái vật, đạn trong game bắn súng, checkpoint).
- Triển khai Undo/Redo (lịch sử thao tác của người chơi).
Tiêu chí | List | LinkedList |
---|---|---|
Thêm/Xóa ở giữa | Chậm (O(n)) | Nhanh (O(1)) |
Truy cập phần tử | Nhanh (O(1)) | Chậm (O(n)) |
Duyệt danh sách | Nhanh | Trung bình |
Bộ nhớ sử dụng | Ít hơn | Nhiều hơn (do lưu Node) |
- Dùng List nếu bạn cần truy cập phần tử nhanh bằng index.
- Dùng LinkedList nếu bạn cần thêm/xóa phần tử thường xuyên ở giữa danh sách.
- LinkedList hữu ích khi cần thêm/xóa phần tử nhanh ở giữa danh sách.
- Tuy nhiên, nó tốn bộ nhớ hơn so với List.
- Trong Unity, nên dùng cho các hệ thống hàng đợi, undo/redo, hoặc quản lý pool đối tượng.