0

Go Zero to Hero - Bài 22: Các thao tác trên Map: Thêm, xóa, kiểm tra phần tử

Map là cấu trúc dữ liệu không thể thiếu trong mọi ứng dụng backend. Hiểu rõ vòng đời và các thao tác trên Map giúp bạn tránh được những lỗi "chí mạng" như Panic hay rò rỉ dữ liệu.


1. Thêm và Sửa (Add & Update): Đơn giản nhưng cẩn thận bẫy "Nil Map"

Cú pháp để thêm một phần tử mới hoặc cập nhật một phần tử đã tồn tại trong Map là giống hệt nhau. Trình biên dịch của Go sẽ tự động hiểu: Nếu Key chưa có thì thêm mới, nếu Key đã có thì ghi đè Value.

package main

import "fmt"

func main() {
    // 1. Khởi tạo Map
    serverStatus := make(map[string]string)

    // 2. Thêm phần tử (Add)
    serverStatus["redis_cache"] = "ONLINE"
    serverStatus["postgres_db"] = "MAINTENANCE"

    // 3. Cập nhật phần tử (Update)
    serverStatus["postgres_db"] = "ONLINE" // Ghi đè trạng thái mới

    fmt.Println(serverStatus) 
    // Kết quả: map[postgres_db:ONLINE redis_cache:ONLINE]
}

Bẫy thực chiến (Map lồng Map):

Khi làm việc với dữ liệu JSON phức tạp, bạn sẽ hay gặp kiểu Map lồng Map (ví dụ: map[string]map[string]string). Lúc này, bạn KHÔNG THỂ gán thẳng giá trị vào Map con nếu chưa khởi tạo nó.

// Kịch bản quản lý user theo phòng ban
departments := make(map[string]map[string]string)

// ❌ LỖI PANIC: Vì Map con (phòng IT) chưa được khởi tạo (nil)
// departments["IT"]["user_01"] = "Hoàng" 

// ✅ CÁCH LÀM CHUẨN:
departments["IT"] = make(map[string]string) // Phải make() Map con trước
departments["IT"]["user_01"] = "Hoàng"      // Sau đó mới được gán

2. Kiểm tra phần tử (Check): Sự thanh lịch của Comma Ok

Như đã nhắc ở bài trước, khi truy cập vào một Key không tồn tại, Go không báo lỗi mà âm thầm trả về Zero Value. Để biết chắc chắn một Key có thực sự tồn tại trong Map hay không, chúng ta dùng kỹ thuật Comma Ok.

func main() {
    ipBlacklist := map[string]bool{
        "192.168.1.100": true,
        "10.0.0.5":      false, // IP này tồn tại trong list, nhưng được cho phép (false)
    }

    targetIP := "10.0.0.5"

    // status chứa giá trị, exists là cờ boolean báo hiệu Key có tồn tại hay không
    if status, exists := ipBlacklist[targetIP]; exists {
        if status == true {
            fmt.Println(targetIP, "- Bị chặn!")
        } else {
            fmt.Println(targetIP, "- Được phép truy cập.")
        }
    } else {
        fmt.Println(targetIP, "- IP chưa từng được ghi nhận trong hệ thống.")
    }
}

💡 Kỹ thuật này là "kim chỉ nam" cho mọi logic Backend: Không bao giờ tin tưởng vào Zero Value khi truy vấn Map.


3. Xóa phần tử (Delete): Hàm delete và cơ chế làm sạch

Để xóa một phần tử, Go cung cấp hàm tích hợp sẵn delete(map, key).

func main() {
    activeSessions := map[string]string{
        "session_abc": "User_Hoang",
        "session_xyz": "User_Nam",
    }

    // Xóa session của Nam
    delete(activeSessions, "session_xyz")

    // Nếu xóa một Key KHÔNG TỒN TẠI thì sao?
    // Go cực kỳ an toàn: Nó sẽ KHÔNG BÁO LỖI, không Crash, mà chỉ đơn giản là bỏ qua.
    delete(activeSessions, "session_ghost") 
}

Xóa TOÀN BỘ Map thì làm thế nào?

Trước phiên bản Go 1.21, không có hàm nào để làm rỗng (clear) toàn bộ một Map. Các kỹ sư phải dùng 1 trong 2 cách:

  1. Duyệt vòng lặp fordelete từng phần tử (Chậm).
  2. Gán biến đó bằng một Map mới activeSessions = make(map[string]string) để Garbage Collector dọn dẹp Map cũ (Nhanh và hay dùng nhất).

Tin vui từ Go 1.21: Đội ngũ phát triển đã bổ sung hàm clear()! Nó giúp làm rỗng toàn bộ Map (hoặc Slice) mà vẫn giữ nguyên vùng nhớ (Capacity) đã được cấp phát, giúp tối ưu hiệu năng cực tốt.

// Yêu cầu: Go version >= 1.21
clear(activeSessions) // Lúc này activeSessions trở thành một Map rỗng: map[]

4. Duyệt Map (Iterate) bằng for range

Cũng giống như Slice, bạn dùng for range để quét qua tất cả các cặp Key-Value trong Map.

func main() {
    httpCodes := map[int]string{
        200: "OK",
        404: "Not Found",
        500: "Internal Server Error",
    }

    // k = key, v = value
    for k, v := range httpCodes {
        fmt.Printf("Mã lỗi %d có ý nghĩa là: %s\n", k, v)
    }

    // Nếu chỉ muốn lấy Key (Bỏ qua Value)
    for k := range httpCodes {
        fmt.Println("Ghi nhận mã:", k)
    }

    // Nếu chỉ muốn lấy Value (Bỏ qua Key)
    for _, v := range httpCodes {
        fmt.Println("Mô tả:", v)
    }
}

⚠️ Nhắc lại luật thép: Thứ tự duyệt trong Map là vô trật tự (ngẫu nhiên). Lần chạy này in ra 200 trước, lần chạy sau có thể in ra 500 trước. Tuyệt đối không dùng Map nếu logic của bạn yêu cầu dữ liệu phải được sắp xếp theo một thứ tự cụ thể (lúc đó hãy dùng Slice).


Tổng kết

Chỉ với vài từ khóa đơn giản, bạn đã làm chủ hoàn toàn vòng đời của dữ liệu bên trong Map:

  1. Thêm/Sửa: Dùng phép gán =. Chú ý phải make() Map con nếu thao tác với Map lồng nhau.
  2. Kiểm tra: Luôn dùng Comma Ok (val, ok := map[key]) để bảo vệ logic hệ thống.
  3. Xóa: Dùng delete(map, key) để xóa lẻ tẻ, và dùng clear(map) (Go 1.21+) để "xóa trắng" bộ nhớ.
  4. Duyệt: Dùng for k, v := range nhưng phải nhớ là dữ liệu trả về sẽ không theo thứ tự nào cả.

All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí