Trong hướng dẫn này, chúng ta sẽ tìm hiểu map là gì, cú pháp khai báo map, thêm phần tử, truy xuất phần tử, xóa phần tử, duyệt các phần tử, ... của map trong Go.
Map là một kiểu tích hợp sẵn trong Go được sử dụng để lưu trữ các cặp khóa-giá trị.
Hãy lấy ví dụ về một công ty khởi nghiệp với một vài nhân viên. Để đơn giản, hãy giả sử rằng tên đầu tiên của tất cả những nhân viên này là duy nhất. Chúng ta đang tìm kiếm một cấu trúc dữ liệu để lưu trữ lương của từng nhân viên.
Map sẽ hoàn toàn phù hợp cho trường hợp sử dụng này. Tên của nhân viên có thể là khóa và tiền lương có thể là giá trị.
Map tương tự như dictionary trong các ngôn ngữ khác như C#, Java, Python.
Map có thể được tạo bằng cách truyền kiểu dữ liệu của khóa và giá trị cho hàm make
. Sau đây là cú pháp để tạo một map mới.
make(map[type of key]type of value)
employeeSalary := make(map[string]int)
Dòng mã trên tạo một map được đặt tên là employeeSalary
có khóa kiểu string
và giá trị kiểu int
.
package main
import (
"fmt"
)
func main() {
employeeSalary := make(map[string]int)
fmt.Println(employeeSalary)
}
Chạy chương trình trong playground
Chương trình trên tạo ra một map được đặt tên employeeSalary
với khóa kiểu string
và giá trị kiểu int
. Chương trình trên sẽ in ra kết quả sau:
map[]
Vì chúng ta chưa thêm bất kỳ phần tử nào vào map nên nó trống.
Cú pháp để thêm các phần tử mới vào map cũng giống như cú pháp của mảng. Chương trình dưới đây thêm một số nhân viên mới vào map employeeSalary
.
package main
import (
"fmt"
)
func main() {
employeeSalary := make(map[string]int)
employeeSalary["steve"] = 12000
employeeSalary["jamie"] = 15000
employeeSalary["mike"] = 9000
fmt.Println("employeeSalary map contents:", employeeSalary)
}
Chạy chương trình trong playground
Chúng ta đã thêm ba nhân viên steve
, jamie
và mike
và tiền lương tương ứng của họ.
Chương trình trên in ra kết quả sau:
employeeSalary map contents: map[steve:12000 jamie:15000 mike:9000]
Cũng có thể khởi tạo một map trong quá trình khai báo chính nó.
package main
import (
"fmt"
)
func main() {
employeeSalary := map[string]int {
"steve": 12000,
"jamie": 15000,
}
employeeSalary["mike"] = 9000
fmt.Println("employeeSalary map contents:", employeeSalary)
}
Chạy chương trình trong playground
Chương trình trên khai báo map employeeSalary
và thêm hai phần tử vào nó trong quá trình khai báo chính nó. Sau đó, một phần tử nữa có khóa mike
được thêm vào. Chương trình in ra kết quả sau:
employeeSalary map contents: map[jamie:15000 mike:9000 steve:12000]
Map không nhất thiết chỉ có khóa phải là kiểu chuỗi. Tất cả các kiểu có thể so sánh được như boolean, integer, float, complex, string, ... cũng có thể làm khóa.
Ngay cả các kiểu do người dùng định nghĩa như struct cũng có thể là khóa. Nếu bạn muốn biết thêm về các kiểu có thể so sánh, vui lòng truy cập:
Zero value của map là nil
. Nếu bạn cố gắng thêm các phần tử vào map nil
, lỗi thời gian chạy sẽ xảy ra. Do đó, map phải được khởi tạo trước khi thêm các phần tử.
package main
func main() {
var employeeSalary map[string]int
employeeSalary["steve"] = 12000
}
Chạy chương trình trong playground
Trong chương trình trên, map employeeSalary
là nil
và chúng ta đang cố gắng thêm một khóa mới vào map. Chương trình sẽ báo lỗi:
panic: assignment to entry in nil map
Bây giờ chúng ta đã thêm một số phần tử vào map, hãy tìm hiểu cách truy xuất chúng. map[key]
là cú pháp để truy xuất các phần tử của map.
package main
import (
"fmt"
)
func main() {
employeeSalary := map[string]int{
"steve": 12000,
"jamie": 15000,
"mike": 9000,
}
employee := "jamie"
salary := employeeSalary[employee]
fmt.Println("Salary of", employee, "is", salary)
}
Chạy chương trình trong playground
Chương trình trên khá đơn giản. Tiền lương của nhân viên jamie
được truy xuất và in ra. Chương trình in ra kết quả sau:
Salary of jamie is 15000
Điều gì sẽ xảy ra nếu một phần tử không có trong map? Map sẽ trả về zero value của kiểu dữ liệu của phần tử đó. Trong trường hợp map employeeSalary
, nếu chúng ta cố gắng truy cập một phần tử không có trong map, zero value của phần tử kiểu int
là 0
sẽ được trả về.
package main
import (
"fmt"
)
func main() {
employeeSalary := map[string]int{
"steve": 12000,
"jamie": 15000,
}
fmt.Println("Salary of joe is", employeeSalary["joe"])
}
Chạy chương trình trong playground
Kết quả của chương trình trên là:
Salary of joe is 0
Chương trình trên trả về mức lương của joe là 0
. Sẽ không có lỗi thời gian chạy khi chúng ta cố gắng truy xuất giá trị cho một khóa không có trong map.
Trong phần trên, chúng ta đã biết rằng khi không có khóa, zero value của kiểu sẽ được trả về. Điều này không hữu ích khi chúng ta muốn tìm hiểu xem liệu khóa có thực sự tồn tại trên map hay không.
Ví dụ, chúng ta muốn biết liệu một khóa có hiện diện trong map employeeSalary
hay không.
value, ok := map[key]
Trên đây là cú pháp để tìm xem một khóa cụ thể có trong map hay không. Nếu ok
là true, thì khóa có mặt và giá trị của nó có trong biến value
, ngược lại khóa sẽ không có.
package main
import (
"fmt"
)
func main() {
employeeSalary := map[string]int{
"steve": 12000,
"jamie": 15000,
}
newEmp := "joe"
value, ok := employeeSalary[newEmp]
if ok == true {
fmt.Println("Salary of", newEmp, "is", value)
return
}
fmt.Println(newEmp, "not found")
}
Chạy chương trình trong playground
Trong chương trình trên, ở dòng số 13, ok
sẽ là false vì khóa joe
không có mặt trong map. Do đó chương trình sẽ in ra kết quả sau:
joe not found
Mẫu range
của vòng lặp for
được sử dụng để duyệt qua trên tất cả các phần tử của map.
package main
import (
"fmt"
)
func main() {
employeeSalary := map[string]int{
"steve": 12000,
"jamie": 15000,
"mike": 9000,
}
fmt.Println("Contents of the map")
for key, value := range employeeSalary {
fmt.Printf("employeeSalary[%s] = %d\n", key, value)
}
}
Chạy chương trình trong playground
Kết quả chương trình trên là:
Contents of the map
employeeSalary[mike] = 9000
employeeSalary[steve] = 12000
employeeSalary[jamie] = 15000
Một thực tế quan trọng là thứ tự truy xuất các giá trị từ map khi sử dụng for range
không được đảm bảo là giống nhau cho mỗi lần thực thi chương trình. Nó cũng không giống với thứ tự mà các phần tử được thêm vào map
delete (map, key) là cú pháp để xóa khóa key
khỏi map. Hàm xóa không trả lại bất kỳ giá trị gì.
package main
import (
"fmt"
)
func main() {
employeeSalary := map[string]int{
"steve": 12000,
"jamie": 15000,
"mike": 9000,
}
fmt.Println("map before deletion", employeeSalary)
delete(employeeSalary, "steve")
fmt.Println("map after deletion", employeeSalary)
}
Chạy chương trình trong playground
Chương trình trên xóa khóa steve
khỏi map và in ra kết quả sau:
map before deletion map[steve:12000 jamie:15000 mike:9000]
map after deletion map[mike:9000 jamie:15000]
Nếu chúng tôi cố gắng xóa một khóa không có trong map, sẽ không có lỗi thời gian chạy.
Cho đến nay chúng ta chỉ lưu trữ lương của nhân viên trong map. Sẽ thật tuyệt nếu chúng ta cũng có thể lưu trữ quốc gia của từng nhân viên trong map phải không?
Điều này có thể đạt được bằng cách sử dụng map kiểu struct. Nhân viên có thể được biểu diễn dưới dạng struct chứa các trường lương và quốc gia và chúng sẽ được lưu trữ trong map với khóa kiểu chuỗi và giá trị kiểu struct.
Hãy viết một chương trình để hiểu làm thế nào điều này có thể được thực hiện.
package main
import (
"fmt"
)
type employee struct {
salary int
country string
}
func main() {
emp1 := employee{
salary: 12000,
country: "USA",
}
emp2 := employee{
salary: 14000,
country: "Canada",
}
emp3 := employee{
salary: 13000,
country: "India",
}
employeeInfo := map[string]employee{
"Steve": emp1,
"Jamie": emp2,
"Mike": emp3,
}
for name, info := range employeeInfo {
fmt.Printf("Employee: %s Salary:$%d Country: %s\n", name, info.salary, info.country)
}
}
Chạy chương trình trong playground
Trong chương trình trên, struct employee
chứa các trường salary
và country
. Chúng ta tạo ra ba nhân viên emp1
, emp2
và emp3
.
Trong dòng số 25, chúng ta khởi tạo một map với loại khóa kiểu string
và giá trị kiểu employee
với ba nhân viên mà chúng ta đã tạo.
Map được duyệt qua ở dòng số 31 và chi tiết nhân viên được in ở dòng tiếp theo. Chương trình này sẽ in ra kết quả sau:
Employee: Mike Salary:$13000 Country: India
Employee: Steve Salary:$12000 Country: USA
Employee: Jamie Salary:$14000 Country: Canada
Độ dài của map có thể được xác định bằng cách sử dụng hàm len.
package main
import (
"fmt"
)
func main() {
employeeSalary := map[string]int{
"steve": 12000,
"jamie": 15000,
}
fmt.Println("length is", len(employeeSalary))
}
Chạy chương trình trong playground
Hàm len(workerSalary)
trong chương trình trên trả về độ dài của map. Chương trình trên in ra kết quả sau:
length is 2
Tương tự như slice, map là kiểu tham chiếu. Khi một map được gán cho một biến mới, cả hai đều trỏ đến cùng một cấu trúc dữ liệu nội bộ. Do đó, những thay đổi được thực hiện ở cái này sẽ phản ánh ở cái kia.
package main
import (
"fmt"
)
func main() {
employeeSalary := map[string]int{
"steve": 12000,
"jamie": 15000,
"mike": 9000,
}
fmt.Println("Original employee salary", employeeSalary)
modified := employeeSalary
modified["mike"] = 18000
fmt.Println("Employee salary changed", employeeSalary)
}
Chạy chương trình trong playground
Trong dòng số 14 của chương trình trên, map employeeSalary
được gán cho map modified
. Trong dòng tiếp theo, mức lương của mike
được thay đổi thành 18000
trong map modified
. Lương của Mike ở trong map employeeSalary
bây giờ cũng sẽ bằng 18000
. Chương trình đầu ra:
Original employee salary map[jamie:15000 mike:9000 steve:12000]
Employee salary changed map[jamie:15000 mike:18000 steve:12000]
Tương tự là trường hợp khi các map được truyền dưới dạng tham số cho các hàm. Khi bất kỳ thay đổi nào được thực hiện đối với map bên trong hàm, nó cũng sẽ thay đổi map bên ngoài hàm.
Map không thể được so sánh bằng cách sử dụng toán tử ==
. Chỉ có thể được sử dụng ==
để kiểm tra xem một map có phải là nil
hay không.
package main
func main() {
map1 := map[string]int{
"one": 1,
"two": 2,
}
map2 := map1
if map1 == map2 {
}
}
Chạy chương trình trong playground
Chương trình trên sẽ không biên dịch được với lỗi
invalid operation: map1 == map2 (map can only be compared to nil)
Một cách để kiểm tra xem hai map có bằng nhau hay không là so sánh từng phần tử riêng lẻ của từng map một. Cách khác là sử dụng reflection. Tôi khuyến khích bạn viết một chương trình cho điều này và làm cho nó hoạt động :).
Trong hướng dẫn tiếp theo, chúng ta sẽ tìm hiểu về chuỗi trong Go.
Cảm ơn bạn đã đọc.
Bạn có thể vui lòng tắt trình chặn quảng cáo ❤️ để hỗ trợ chúng tôi duy trì hoạt động của trang web.
Trong hướng dẫn này, chúng ta sẽ tìm hiểu phương thức (method) trong Go là gì? Cú pháp khai báo phương thức, so sánh phương thức với hàm, ... trong Go.
Trong hướng dẫn này, chúng ta sẽ tìm hiểu struct là gì, cách khai báo và sử dụng một struct trong Go, struct ẩn danh, so sanh hai struct, ...
Trong hướng dẫn này, chúng ta sẽ tìm hiểu cách con trỏ (pointer) hoạt động trong Go và nó khác với con trỏ trong các ngôn ngữ khác như C và C++ như thế nào.
Chuỗi (string) xứng đáng được đề cập đặc biệt trong Go vì chúng khác biệt trong cách triển khai khi so sánh với các ngôn ngữ khác.