Trong bài này, chúng ta tìm hiểu sơ lược về hai lớp tập dữ liệu HashSet và List, sau đó là đưa so sánh giữa chúng.
HashSet
Lớp HashSet<T> (tạm dịch là tập băm) thể hiện một tập dữ liệu với mỗi phần tử có kiểu dữ liệu là T. Ví dụ HashSet<int> thể hiện 1 tập dữ liệu với mỗi phần tử số nguyên.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace LINQ { class Program { static void Main(string[] args) { // Tạo biến numbers có kiểu là 1 HashSet với mỗi phần tử kiểu int HashSet<int> numbers = new c<int>(); // Nhập vào HashSet phần tử 0 đến 4 for (int i = 1; i <= 5; i++) { numbers.Add(i); } // In dữ liệu từ tập numbers ra màn hình DisplaySet(numbers); Console.ReadLine(); } private static void DisplaySet(HashSet<int> set) { Console.Write("{"); foreach (int i in set) { Console.Write(" {0}", i); } Console.WriteLine(" }"); } } }
List
Tương tự, List<T> (danh sách) cũng chứa danh sách các phần tử với mỗi phần tử có kiểu T. Ví dụ List<int> cũng là danh sách chứa các phần tử kiểu int. Ví dụ sử dụng một danh sách List như sau. Kiểu List rất thông dụng và được nhiều lập trình viên sử dụng hiện nay, có thể do nó xuất hiện đầu tiên trong các tài liệu, chương trình giảng dạy về công nghệ .NET. Tuy nhiên, nhiều trường hợp sử dụng List chưa chắc đã tối ưu hiệu suất thực thi.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace LINQ { class Program { static void Main(string[] args) { // Tạo biến numbers có kiểu là 1 List với mỗi phần tử kiểu int List<int> numbers = new List<int>(); // Nhập vào HashSet phần tử 0 đến 4 for (int i = 1; i <= 5; i++) { numbers.Add(i); } // In dữ liệu từ tập numbers ra màn hình DisplaySet(numbers); Console.ReadLine(); } private static void DisplaySet(List<int> list) { Console.Write("{"); foreach (int i in list) { Console.Write(" {0}", i); } Console.WriteLine(" }"); } } }
So sánh HashSet và List
Bạn có thể thấy ở 2 phần trên, HashSet và List đều có chức năng na ná nhau, đều có thể thêm phần tử mới và có thể duyệt vòng foreach để lấy giá trị dữ liệu từng phần tử. Vậy khi nào dùng HashSet, khi nào dùng List? Đây là câu hỏi mà rất nhiều bạn thắc mắc. Dựa theo phương pháp so sánh của Roy T., chúng ta thực thi đoạn mã sau.
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; namespace LINQ { class Program { static void Main(string[] args) { int times = 10000000; for (int listSize = 1; listSize < 10; listSize++) { List<string> list = new List<string>(); HashSet<string> hashset = new HashSet<string>(); for (int i = 0; i < listSize; i++) { list.Add("string" + i.ToString()); hashset.Add("string" + i.ToString()); } Stopwatch timer = new Stopwatch(); timer.Start(); for (int i = 0; i < times; i++) { list.Remove("string0"); list.Add("string0"); } timer.Stop(); Console.WriteLine(listSize.ToString() + " item LIST strs time: " + timer.ElapsedMilliseconds.ToString() + "ms"); timer = new Stopwatch(); timer.Start(); for (int i = 0; i < times; i++) { hashset.Remove("string0"); hashset.Add("string0"); } timer.Stop(); Console.WriteLine(listSize.ToString() + " item HASHSET strs time: " + timer.ElapsedMilliseconds.ToString() + "ms"); Console.WriteLine(); } for (int listSize = 1; listSize < 50; listSize += 3) { List<object> list = new List<object>(); HashSet<object> hashset = new HashSet<object>(); for (int i = 0; i < listSize; i++) { list.Add(new object()); hashset.Add(new object()); } object objToAddRem = list[0]; Stopwatch timer = new Stopwatch(); timer.Start(); for (int i = 0; i < times; i++) { list.Remove(objToAddRem); list.Add(objToAddRem); } timer.Stop(); Console.WriteLine(listSize.ToString() + " item LIST objs time: " + timer.ElapsedMilliseconds.ToString() + "ms"); timer = new Stopwatch(); timer.Start(); for (int i = 0; i < times; i++) { hashset.Remove(objToAddRem); hashset.Add(objToAddRem); } timer.Stop(); Console.WriteLine(listSize.ToString() + " item HASHSET objs time: " + timer.ElapsedMilliseconds.ToString() + "ms"); Console.WriteLine(); } Console.ReadLine(); } } }
Dựa vào đoạn mã so sánh trên, bạn có thể thấy kích thước phần tử ảnh hưởng rất lớn đến hiệu suất thực thi của HashSet và List. Với tập dữ liệu lớn, rõ ràng HashSet vượt trội hơn hẳn List. List chỉ “ăn được” HashSet ở tập dữ liệu kích thước rất nhỏ (trong ví dụ là 3 phần tử trở lại). Tuy nhiên, cần lưu ý rằng do HashSet thực thị nhanh hơn vì vậy nó bắt buộc phải ngốn bộ nhớ RAM nhiều hơn so với List.
Chúng ta có thể thấy một sự công bằng công nghệ ở đây, thực thi nhanh tốn bộ nhớ, ít tốn bộ nhớ thì thực thi chậm. Do đó, tùy theo nhu cầu của phần mềm bạn có thể lựa chọn HashSet hay List để tối ưu mã nguồn của mình. Việc tối ưu này chủ yếu dành cho các phần mềm công nghiệp được trau chuốt nhiều lần, ít khi xảy ra ở các phần mềm nghiệp dư, vì vậy nhiều lập trình viên lẫn lộn trong việc sử dụng HashSet hay List là điều dễ hiểu.
- APA:
Dammio. (2018). So sánh hiệu suất giữa HashSet và List trong .NET. https://www.dammio.com/2018/06/22/so-sanh-hieu-suat-giua-hashset-va-list-trong-net.
- BibTeX:
@misc{dammio,
author = {Dammio},
title = {So sánh hiệu suất giữa HashSet và List trong .NET},
year = {2018},
url = {https://www.dammio.com/2018/06/22/so-sanh-hieu-suat-giua-hashset-va-list-trong-net},
urldate = {2025-02-07}
}
Trong bài là so sánh hiệu suất tổng (thêm và bớt). Admin thử chia ra lúc thêm hay bớt so sánh thế nào mới có cái nhìn tốt nhất!