[Golang cơ bản] Maps trong Golang

Post on: 2023-03-15 00:14:09 | in: Golang
Maps trong Go là một loại dữ liệu được sử dụng để lưu trữ các cặp key-value. Key trong map phải là duy nhất và không thể thay đổi, còn giá trị của mỗi key có thể được lưu trữ và truy xuất...

Tổng quan

Maps là một kiểu dữ liệu có sẵn trong Go tương tự như bảng băm, nó ánh xạ một khóa vào một giá trị. Map là một bộ sưu tập không được sắp xếp trong đó mỗi khóa là duy nhất trong khi giá trị có thể giống nhau cho hai hoặc nhiều khóa khác nhau. Lợi ích của việc sử dụng map là nó cung cấp các hoạt động truy xuất, tìm kiếm, chèn và xóa nhanh chóng.

Map là kiểu dữ liệu được tham chiếu. Khi bạn gán một map cho một map khác, cả hai đều trỏ đến cùng một map cơ bản. Dưới đây là định dạng của một map

map[key_type]value_type

Cả key_type và value_type có thể khác nhau hoặc giống nhau về kiểu dữ liệu. Ví dụ dưới đây sử dụng string làm kiểu khóa và int làm kiểu giá trị.

map[string]int

Các kiểu dữ liệu được phép sử dụng làm khóa trong một Map

Khóa của map có thể là bất kỳ kiểu dữ liệu so sánh được nào. Một số trong số các kiểu dữ liệu so sánh được được định nghĩa bởi cú pháp Go là:

  • boolean
  • numeric
  • string,
  • pointer
  • channel
  • interface types
  • structs – Nếu tất cả các trường (field) của một kiểu dữ liệu khai báo bởi người dùng (user-defined type) đều là so sánh được (comparable).
  • array – Nếu kiểu dữ liệu của các phần tử trong mảng là so sánh được (comparable).

Một số kiểu dữ liệu không so sánh được (non-comparable) theo định nghĩa của Go và không thể được sử dụng làm khóa trong một Map bao gồm:

  • Slice
  • Map
  • Function

Các kiểu dữ liệu được phép sử dụng làm giá trị trong một Map

Giá trị trong một Map ở Go có thể là bất kỳ kiểu dữ liệu nào.

Tạo một Map
  • Sử dụng cú pháp map[]{} (được gọi là map literal).
  • Sử dụng hàm make.

Xem xét từng phương pháp một.

Sử dụng cú pháp map[]{}

Một trong những cách phổ biến nhất để tạo một Map là sử dụng map literal:

map[key_type]value_type{}

Một ví dụ về cách sử dụng map literal với kiểu khóa là string và kiểu giá trị là integer:

employeeSalary := map[string]int{}

Một Map cũng có thể được tạo với một số giá trị khóa được khởi tạo sẵn bằng cách sử dụng map literal.

employeeSalary := map[string]int{
"John": 1000
"Sam": 2000
}

Một cặp key-value mới cũng có thể được thêm vào Map.

employeeSalary["Tom"] = 2000

Hãy xem một chương trình minh họa:

package main

import "fmt"

func main() {
    //Declare
    employeeSalary := map[string]int{}
    fmt.Println(employeeSalary)

    //Intialize using map lieteral
    employeeSalary = map[string]int{
        "John": 1000,
        "Sam":  1200,
    }

    //Adding a key value
    employeeSalary["Tom"] = 2000
    fmt.Println(employeeSalary)
}

Output

map[]
map[John:1000 Sam:1200 Tom:2000]

Trong chương trình trên, chúng ta tạo một Map sử dụng map literal đã khởi tạo sẵn một số giá trị. Sau đó, chúng ta thêm một cặp key-value mới vào Map và in ra nó bằng cách sử dụng fmt.Println, trong đó in ra tất cả các cặp key-value theo định dạng map[key:value key:value].

Một Map cũng có thể được khai báo bằng từ khóa var, tuy nhiên nó sẽ tạo ra một Map nil vì giá trị khởi tạo mặc định của Map là nil. Việc thêm bất kỳ key-value trị nào vào Map này sẽ gây ra một lỗi panic. Hãy xem ví dụ cho điều này:

package main

func main() {
    var employeeSalary map[string]int
    employeeSalary["Tom"] = 2000
}

Output

panic: assignment to entry in nil map

Chương trình trên đã gặp lỗi panic vì Map này là nil.

Một trong những trường hợp sử dụng khi khai báo một Map với từ khóa var là khi một Map đã tồn tại cần được gán cho nó hoặc khi chúng ta muốn gán kết quả của một hàm cho nó.

Sử dụng Make để tạo Map.

Đây là một cách khác để tạo Map. Hàm tích hợp sẵn make có thể được sử dụng để tạo ra một Map. Nó trả về một Map đã được khởi tạo. Do đó, các cặp key-value có thể được thêm vào đó.

package main

import "fmt"

func main() {
    //Declare
    employeeSalary := make(map[string]int)
    //Adding a key value
    employeeSalary["Tom"] = 2000
    fmt.Println(employeeSalary)
}

Output

map[Tom:2000]

Trong chương trình trên, chúng ta đã tạo một Map bằng cách sử dụng hàm make. Sau đó, chúng ta đã thêm một cặp key-value vào đó. Sau đó, chúng ta đã in nó bằng fmt.Println, nó sẽ in ra tất cả các cặp key-value.

Các hoạt động trên Map.

Dưới đây là các hoạt động áp dụng cho Map:

  • Thêm một cặp key-value
  • Cập nhật một khóa
  • Lấy giá trị tương ứng với một khóa
  • Xóa một cặp key-value
  • Kiểm tra xem một khóa có tồn tại hay không.
Thêm một cặp key-value

Dưới đây là định dạng để thêm một cặp khóa-giá trị vào một bản đồ (map).

mapName[key] = value

Hãy xem một ví dụ.

package main

import "fmt"

func main() {
    //Declare
    employeeSalary := make(map[string]int)

    //Adding a key value
    employeeSalary["Tom"] = 2000
    fmt.Pr

Output

map[Tom:2000]

Cũng lưu ý rằng thêm vào một bản đồ (map) nil sẽ gây ra một sự cố (panic).

Cập nhật một cặp key-value

Khi cố gắng thêm một khóa vào bản đồ mà đã tồn tại, giá trị mới sẽ ghi đè lên giá trị cũ. Điều này tương tự như cập nhật một khóa trong bản đồ. Hãy xem một ví dụ.

package main

import "fmt"

func main() {
    //Declare
    employeeSalary := make(map[string]int)

    //Adding a key value
    fmt.Println("Before update")
    employeeSalary["Tom"] = 2000
    fmt.Println(employeeSalary)

    fmt.Println("After update")
    employeeSalary["Tom"] = 3000
    fmt.Println(employeeSalary)
}

Output

Before update
map[Tom:2000]
After update
map[Tom:3000]

Trong chương trình trên, sau khi viết cùng một khóa "Tom" với giá trị mới là 3000, nó ghi đè lên giá trị hiện tại của 2000. Khi chúng ta in ra map lại, giá trị được in ra là 3000.

Lấy giá trị tương ứng với một khóa (key).

Dưới đây là định dạng để truy xuất giá trị tương ứng với một khóa (key).

val := mapName[key]

Hãy xem một ví dụ.

package main

import "fmt"

func main() {
    //Declare
    employeeSalary := make(map[string]int)

    //Adding a key value
    employeeSalary["Tom"] = 2000

    //Retrieve a value
    salary := employeeSalary["Tom"]
    fmt.Printf("Salary: %d", salary)
}

Xóa một cặp key-value

Dưới đây là định dạng để xóa một giá trị tương ứng với một khóa (key).

delete(map_name, key)

package main


import "fmt"

func main() {
    //Declare
    employeeSalary := make(map[string]int)

    //Adding a key value
    fmt.Println("Adding key")
    employeeSalary["Tom"] = 2000
    fmt.Println(employeeSalary)

    fmt.Println("\nDeleting key")
    delete(employeeSalary, "Tom")
    fmt.Println(employeeSalary)
}

Output

Adding key
map[Tom:2000]


Deleting key
map[]

Trong chương trình trên, chúng ta đã xóa khóa và khi chúng ta in map lại, khóa đó không còn tồn tại nữa.

Kiểm tra xem một khóa có tồn tại trong map hay không

Dưới đây là định dạng để kiểm tra xem một khóa có tồn tại trong map hay không.

val, ok := mapName[key]

Có hai trường hợp:

  • Nếu khóa tồn tại, biến val sẽ là giá trị của khóa trong bản đồ và biến ok sẽ là true.
  • Nếu khóa không tồn tại, biến val sẽ là giá trị mặc định bằng không của kiểu giá trị và biến ok sẽ là false.

Hãy xem một ví dụ.

package main

import "fmt"

func main() {
    //Declare
    employeeSalary := make(map[string]int)

    //Adding a key value
    employeeSalary["Tom"] = 2000
    fmt.Println("Key exists case")
    val, ok := employeeSalary["Tom"]
    fmt.Printf("Val: %d, ok: %t\n", val, ok)
    fmt.Println("Key doesn't exists case")

    val, ok = employeeSalary["Sam"]
    fmt.Printf("Val: %d, ok: %t\n", val, ok)
}

Output

Key exists case
Val: 2000, ok: true
Key doesn't exists case
Val: 0, ok: false

Trong chương trình trên, khi khóa tồn tại, biến val được thiết lập thành giá trị thực tế, là 2000 trong ví dụ này, và biến ok là true. Khi khóa không tồn tại, biến val được thiết lập thành 0, là giá trị mặc định bằng không của kiểu int và biến ok là false. Biến ok là cách tốt nhất để kiểm tra xem khóa có tồn tại trong bản đồ hay không.

Trong trường hợp chúng ta chỉ muốn kiểm tra xem một khóa có tồn tại và không cần giá trị val, thì chúng ta có thể sử dụng từ khóa rỗng " _ " thay cho val.

_, ok = employeeSalary["Sam"]

Các hàm trên bản đồ (Maps)

Dưới đây là các hàm tích hợp sẵn có thể được sử dụng trên một map

  • Hàm len()

Hàm len()

Hàm len() được sử dụng để lấy độ dài của map, là số lượng cặp key/value trong map. Dưới đây là định dạng để sử dụng hàm này trên map.

len(mapName)

Hãy xem một ví dụ.

package main

import "fmt"

func main() {
    //Declare
    employeeSalary := make(map[string]int)

    //Adding a key value
    employeeSalary["Tom"] = 2000
    employeeSalary["Sam"] = 1200

    lenOfMap := len(employeeSalary)
    fmt.Println(lenOfMap)
}

Output

2

Giá trị không

Giá trị không của một map là nil. Điều này cũng được chứng minh khi chúng ta khai báo một map bằng từ khóa var. Xem chương trình dưới đây.

package main

import "fmt"

func main() {
    var employeeSalary map[string]int
    if employeeSalary == nil {
        fmt.Println("employeeSalary map is nil")
    }
}

Output

employeeSalary map is nil

Maps là kiểu dữ liệu tham chiếu.

Map là kiểu dữ liệu tham chiếu. Vì vậy, khi gán một map cho một biến mới, cả hai biến đều trỏ đến cùng một map. Bất kỳ thay đổi nào trong một map sẽ phản ánh trên map khác và ngược lại.

package main

import "fmt"

func main() {
    //Declare
    employeeSalary := make(map[string]int)

    //Adding a key value
    employeeSalary["Tom"] = 2000
    employeeSalary["Sam"] = 1200

    eS := employeeSalary

    //Change employeeSalary
    employeeSalary["John"] = 3000
    fmt.Println("Changing employeeSalary Map")
    fmt.Printf("employeeSalary: %v\n", employeeSalary)
    fmt.Printf("eS: %v\n", eS)

    //Change eS
    employeeSalary["John"] = 4000
    fmt.Println("\nChanging eS Map")
    fmt.Printf("employeeSalary: %v\n", employeeSalary)
    fmt.Printf("eS: %v\n", eS)
}

Trong chương trình trên, eS là một biến map mới được gán bằng biến employeeSalary đang tồn tại.

  • Đầu tiên, chúng ta thêm một khóa mới vào biến employeeSalary. Sự thay đổi được phản ánh cả trong map employeeSalaryeS.
  • Thứ hai, chúng ta đã cập nhật một khóa đã tồn tại trong map eS. Sự thay đổi lại được phản ánh cả trong map employeeSalaryeS.

Điều này cho thấy rằng maps là kiểu dữ liệu tham chiếu.

Lặp lại một map

Toán tử phạm vi (range operator) có thể được sử dụng để lặp lại một map trong Go.

Hãy xác định một map trước.

sample := map[string]string{
        "a": "x",
        "b": "y",
}
  • Lặp lại tất cả các khóa và giá trị
for k, v := range sample {
   fmt.Printf("key :%s value: %s\n", k, v)
}

Output:

key :a value: x
key :b value: y
  • Lặp lại chỉ các khóa
for k := range sample {
   fmt.Printf("key :%s\n", k)
}

Output:

key :a
key :b
  • Lặp lại chỉ các giá trị
for _, v := range sample {
   fmt.Printf("value :%s\n", v)
}

Output:

value :x
value :y
  • Lấy danh sách tất cả các khóa
keys := getAllKeys(sample)
fmt.Println(keys)


func getAllKeys(sample map[string]string) []string {
    var keys []string
    for k := range sample {
        keys = append(keys, k)
    }
    return keys
}

Output:

[a b]

Maps không an toàn để sử dụng đồng thời

go maps không an toàn cho việc sử dụng đồng thời.

Mã lỗi: Dưới đây là một đoạn mã lỗi. Nó có thể dẫn đến sự cố nếu đọc và ghi đồng thời trên map xảy ra.

package main

var (
   allData = make(map[string]string)
)

func get(key string) string {
    return allData[key]
}

func set(key string, value string) {
    allData[key] = value
}

func main() {
    go set("a", "Some Data 1")
    go set("b", "Some Data 2")
    go get("a")
    go get("b")
    go get("a")
}

Possible Output:

fatal error: concurrent map read and map write

Correct Code:

Chúng ta có thể sử dụng một khóa để cho phép truy cập đồng thời vào map.

package main

import (
    "fmt"
    "sync"
)

var (
    allData = make(map[string]string)
    rwm     sync.RWMutex
)

func get(key string) string {
    rwm.RLock()
    defer rwm.RUnlock()
    return allData[key]
}

func set(key string, value string) {
    rwm.Lock()
    defer rwm.Unlock()
    allData[key] = value

}

func main() {
    set("a", "Some Data")
    result := get("a")
    fmt.Println(result)
}

Output

Some data

Tổng kết

Đó là tất cả về maps trong golang. Chúng ta đã tìm hiểu cách tạo ra một map, thực hiện các thao tác trên map, một số hàm được định nghĩa trên một map như hàm Glen(), cách chúng ta có thể lặp lại các phần tử trong map và cuối cùng nhưng không kém là map là không an toàn cho việc sử dụng đồng thời. Hy vọng bạn đã thích bài viết này. Vui lòng chia sẻ phản hồi/cải thiện/lỗi trong phần bình luận.

 
Tag: golang cơ bản go