0
0
Lập trình
Admin Team
Admin Teamtechmely

🗺️ Tìm Hiểu Về Bản Đồ Trong Go — Phần 0: Cơ Bản

Đăng vào 3 ngày trước

• 5 phút đọc

Tìm Hiểu Về Bản Đồ Trong Go — Phần 0: Cơ Bản

Giới Thiệu Chuỗi Bài Viết

Chào mừng bạn đến với phần đầu tiên trong chuỗi bài viết về bản đồ trong Go! Trong các phần tiếp theo, chúng ta sẽ đi từ những khái niệm cơ bản nhất về bản đồ đến những khía cạnh sâu hơn như cách chúng được triển khai, cấu trúc bộ nhớ, hàm băm, tối ưu hóa và các mẹo hiệu suất. Đây là phần 0, nơi chúng ta bắt đầu từ những điều cơ bản: bản đồ là gì, cách chúng hoạt động và những gì bạn cần biết trước khi đi sâu hơn.

🧩 Bản Đồ Là Gì?

Trong Go, bản đồ là một cấu trúc dữ liệu dạng khóa-giá trị. Nếu bạn đã sử dụng Rust, bạn sẽ biết đến nó như một HashMap. Nếu bạn đã sử dụng Python, đó là một dict.

go Copy
m := map[string]int{
    "apple":  5,
    "orange": 3,
}
fmt.Println(m["apple"]) // 5

Chúng là các tập hợp không có thứ tự của các cặp khóa-giá trị, được thiết kế cho việc tìm kiếm và chèn nhanh.

🗝️ Bản Đồ Là Con Trỏ

Khác với slices, bản đồ trong Go thực chất là các con trỏ đến một cấu trúc dữ liệu gọi là hmap. Điều này có hai hệ quả quan trọng:

  • Bạn không cần phải truyền chúng qua con trỏ.
  • Khi bạn truyền một bản đồ vào một hàm, bạn đã truyền một con trỏ trong nền.

Trong những ngày đầu, những gì chúng ta gọi là bản đồ bây giờ được viết dưới dạng con trỏ, vì vậy bạn đã viết *map[int]int. Chúng tôi đã từ bỏ điều đó khi nhận ra rằng không ai bao giờ viết map mà không viết *map.

Giá trị mặc định là nil.

go Copy
var m map[string]int
fmt.Println(m == nil) // true
  • Nếu bạn khai báo một bản đồ và sau đó gán nó cho một giá trị mới, cả m1m2 sẽ được đặt tại các vị trí bộ nhớ độc nhất.
  • Thực tế, m1 sẽ được sao chép sang m2 (như tất cả các phép gán trong Golang).
  • Cập nhật m2 cũng đồng nghĩa với việc cập nhật m1.
go Copy
m1 := make(map[string]int)
m2 := m1

Sơ đồ: m1m2 trỏ đến cùng một cấu trúc hmap

⚖️ Bản Đồ Không Thể So Sánh

Khác với slices, bạn không thể so sánh hai bản đồ trực tiếp:

go Copy
m1 := map[string]int{}
m2 := map[string]int{}

// Điều này sẽ không biên dịch ❌
if m1 == m2 {}

// Điều này cũng không biên dịch ❌
if m1 == m1 {}

// Nhưng bạn *có thể* kiểm tra nếu một bản đồ là nil ✅
if m1 == nil {
    fmt.Println("m1 là nil")
}

🔑 Khóa Phải Có Thể So Sánh

Loại mà bạn sử dụng làm khóa phải có thể so sánh trong Go.

✅ Khóa hợp lệ:

  • string
  • int, float64
  • struct (nếu tất cả các trường đều có thể so sánh)

❌ Khóa không hợp lệ:

  • slice
  • map
  • function

interface{} hoặc any có thể đôi khi hợp lệ, đôi khi không hợp lệ:

go Copy
var x, x2 map[interface{}]int

x["a"] = 1 // OK✅

x2[[]int{1, 2}] = 1 // panic thời gian chạy!❌

⏱️ Tìm Kiếm và Chèn Thường Là O(1)

Bản đồ Go dựa trên hàm băm, vì vậy các hoạt động như tìm kiếm và chèn thường có độ phức tạp thời gian là O(1):

Tại sao lại “thường” là O(1)? Trong các phần tiếp theo, chúng ta sẽ khám phá va chạm hàm băm, các bucket và lý do tại sao hiệu suất đôi khi có thể giảm. Hãy theo dõi!

⚡ Bản Đồ Không An Toàn Cho Đa Luồng

Bản đồ Go không an toàn cho việc ghi đồng thời. Nếu bạn cố gắng, bạn sẽ nhận được một lỗi nghiêm trọng thời gian chạy:

go Copy
m := map[int]int{}
go func() { m[1] = 42 }()
go func() { m[2] = 84 }()

💥 Panic thời gian chạy: ghi bản đồ đồng thời

🏗️ Tạo Bản Đồ Với make

Bạn có thể tạo bản đồ với make:

go Copy
m := make(map[int]int, 10)

Ở đây, số 10 không phải là độ dài — nó là một gợi ý cho Go về kích thước dự kiến. Điều này giúp giảm thiểu tải trọng khi chèn nhiều mục.

Chúng ta sẽ nói về lý do tại sao nó chỉ là một gợi ý và cách Go thực sự phân bổ các bucket trong phần tiếp theo.

🔜 Điều Gì Đang Chờ Đợi?

Đây chỉ là phần khởi đầu! Trong phần 1, chúng ta sẽ bắt đầu khám phá những lớp bên trong của cấu trúc hmap — xương sống của các bản đồ trong Go. Chúng ta sẽ đề cập đến:

  • Cấu trúc hmap nội bộ
  • Các bucket và hàm băm
  • Các yếu tố tải và chiến lược thay đổi kích thước

Và sau tất cả các phần, bạn sẽ có cái nhìn rõ ràng về những gì đang xảy ra bên trong các bản đồ trong Go!

Hãy theo dõi — mọi thứ sẽ trở nên thật thú vị! 🚀

Thực Hành Tốt Nhất

  • Luôn kiểm tra xem bản đồ có nil hay không trước khi truy cập.
  • Sử dụng sync.Map cho các trường hợp đa luồng.
  • Chỉ sử dụng các kiểu dữ liệu có thể so sánh làm khóa.

Cạm Bẫy Thường Gặp

  • Không thể so sánh hai bản đồ trực tiếp.
  • Cẩn thận với các va chạm hàm băm khi có nhiều phần tử.

Mẹo Hiệu Suất

  • Sử dụng make với thông số kích thước dự kiến để cải thiện hiệu suất.
  • Tránh ghi vào bản đồ từ nhiều goroutines mà không có cơ chế đồng bộ.

Câu Hỏi Thường Gặp

1. Tại sao bản đồ trong Go không an toàn cho đa luồng?

Bản đồ trong Go không an toàn cho việc ghi đồng thời bởi vì không có khóa bảo vệ, điều này có thể dẫn đến tình trạng không nhất quán trong dữ liệu.

2. Làm thế nào để tạo một bản đồ với kích thước dự kiến?

Sử dụng make(map[KeyType]ValueType, expectedSize) để giúp giảm thiểu chi phí thay đổi kích thước sau này.

3. Có cách nào để so sánh hai bản đồ không?

Không, bạn không thể so sánh hai bản đồ trực tiếp, nhưng bạn có thể kiểm tra xem chúng có nil hay không.

Tài Nguyên Tham Khảo

Liên Kết Nội Bộ

Hy vọng rằng bài viết này sẽ giúp bạn có cái nhìn rõ hơn về bản đồ trong Go và chuẩn bị cho những phần tiếp theo đầy hấp dẫn!

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào