Rumah > Artikel > pembangunan bahagian belakang > Peta Go Dijelaskan: Cara Pasangan Nilai Kunci Sebenarnya Disimpan
Ini adalah petikan siaran; siaran penuh tersedia di sini: https://victoriametrics.com/blog/go-map/.
Jika anda baru menggunakan Go, agak mengelirukan untuk memikirkan cara menggunakan peta dalam Go. Dan walaupun anda lebih berpengalaman, memahami cara peta benar-benar berfungsi boleh menjadi agak sukar.
Ambil contoh ini: Pernahkah anda menetapkan 'petunjuk' untuk peta dan tertanya-tanya mengapa ia dipanggil 'petunjuk' dan bukan sesuatu yang mudah seperti panjang, seperti yang kita lakukan dengan kepingan?
// hint = 10 m := make(map[string]int, 10)
Atau mungkin anda perasan bahawa apabila anda menggunakan gelung jarak jauh pada peta, tertib itu tidak sepadan dengan tertib sisipan, malah ia berubah jika anda menggelung pada peta yang sama pada masa yang berbeza. Tetapi peliknya, jika anda mengulanginya pada masa yang sama, pesanan biasanya tetap sama.
Ini adalah cerita yang panjang, jadi kencangkan tali pinggang keledar anda dan selami.
Sebelum kita meneruskan, sekadar makluman—maklumat di sini adalah berdasarkan Go 1.23. Jika keadaan telah berubah dan ini tidak terkini, sila ping saya di X(@func25).
Jadi, mari bercakap tentang peta dalam Go, ia adalah jenis terbina dalam yang bertindak sebagai storan nilai kunci. Tidak seperti tatasusunan yang anda terperangkap dengan kunci sebagai indeks yang semakin meningkat seperti 0, 1, 2 dan seterusnya, dengan peta, kuncinya boleh menjadi sebarang jenis yang setanding.
Itu memberikan anda lebih banyak fleksibiliti.
m := make(map[string]int) m["a"] = 1 m["b"] = 2 m // map[a:1 b:2]
Dalam contoh itu, kami mencipta peta kosong menggunakan make(), dengan kuncinya ialah rentetan dan nilainya ialah int.
Sekarang, bukannya memberikan setiap kunci secara manual, anda boleh menjimatkan masa anda dengan menggunakan literal peta. Ini membolehkan anda menyediakan pasangan nilai kunci anda sekaligus, tepat apabila anda membuat peta:
m := map[string]int{ "a": 1, "b": 2, }
Apa yang anda lakukan ialah menyenaraikan kunci dan nilainya di dalam pendakap kerinting apabila anda mula-mula mencipta peta, semudah itu.
Dan jika anda menyedari kemudian bahawa anda tidak memerlukan pasangan nilai kunci tertentu lagi, Go akan membantu anda. Terdapat fungsi padam yang berguna yang, baik, memadamkan kunci yang anda tidak mahu: padam(m, "a").
Nilai sifar peta ialah sifar dan peta sifar adalah seperti peta kosong dalam beberapa cara. Anda boleh cuba mencari kunci di dalamnya, dan Go tidak akan panik dan ranap program anda.
Jika anda mencari kunci yang tiada di sana, Go secara senyap-senyap memberikan anda "nilai sifar" untuk jenis nilai peta itu:
var m map[string]int println(m["a"]) // 0 m["a"] = 1 // panic: assignment to entry in nil map
Tetapi inilah perkaranya: anda tidak boleh menambah pasangan nilai kunci baharu pada peta sifar.
Malah, Go mengendalikan peta dengan cara yang hampir sama dengan cara ia menangani kepingan. Kedua-dua peta dan kepingan bermula sebagai sifar, dan Go tidak panik jika anda melakukan sesuatu yang "tidak berbahaya" dengan mereka semasa mereka tiada. Sebagai contoh, anda boleh melingkari sekeping nol tanpa sebarang "drama".
Jadi, apa yang berlaku jika anda cuba mengulangi peta kosong?
var m map[string]int for k, v := range m { println(k, v) }
Tiada apa-apa yang berlaku, tiada ralat, tiada kejutan. Ia secara senyap-senyap tidak melakukan apa-apa.
Pendekatan Go adalah untuk menganggap nilai lalai apa-apa jenis sebagai sesuatu yang berguna, bukan sesuatu yang menyebabkan program anda meledak. Satu-satunya masa Go membuat kesesuaian ialah apabila anda melakukan sesuatu yang benar-benar menyalahi undang-undang, seperti cuba menambah pasangan nilai kunci baharu pada peta sifar atau mengakses indeks di luar sempadan dalam kepingan.
Terdapat beberapa perkara lagi yang perlu anda ketahui tentang peta dalam Go:
Mari kita menyelami perkara terakhir tentang kunci peta. Saya telah menyebut sebelum ini bahawa "kunci itu boleh menjadi apa-apa jenis yang setanding," tetapi terdapat lebih banyak daripada itu.
"Jadi, apakah sebenarnya jenis yang setanding? Dan apa yang tidak?"
Ia agak mudah: jika anda boleh menggunakan == untuk membandingkan dua nilai daripada jenis yang sama, maka jenis itu dianggap setanding.
func main() { var s map[int]string if s == s { println("comparable") } } // compile error: invalid operation: s == s (map can only be compared to nil)
Tetapi seperti yang anda lihat, kod di atas tidak dikompil. Pengkompil mengadu: "operasi tidak sah: s == s (peta hanya boleh dibandingkan dengan sifar)."
Peraturan yang sama ini digunakan untuk jenis lain yang tidak setanding seperti kepingan, fungsi atau struktur yang mengandungi kepingan atau peta, dsb. Jadi, jika anda cuba menggunakan mana-mana jenis tersebut sebagai kunci dalam peta, anda kurang beruntung.
func main() { var s map[[]int]string } // compile error: invalid map key type []intcompilerIncomparableMapKey
Tetapi inilah sedikit rahsia, antara muka boleh setanding dan tidak setanding.
Apakah maksudnya? Anda benar-benar boleh menentukan peta dengan antara muka kosong sebagai kunci tanpa sebarang ralat penyusunan. Tetapi berhati-hati, ada kemungkinan besar anda akan menghadapi ralat masa jalan.
func main() { m := map[interface{}]int{ 1: 1, "a": 2, } m[[]int{1, 2, 3}] = 3 m[func() {}] = 4 } // panic: runtime error: hash of unhashable type []int // panic: runtime error: hash of unhashable type func()
Everything looks fine until you try to assign an uncomparable type as a map key.
That's when you'll hit a runtime error, which is trickier to deal with than a compile-time error. Because of this, it's usually a good idea to avoid using interface{} as a map key unless you have a solid reason and constraints that prevent misuse.
But that error message: "hash of unhashable type []int" might seem a bit cryptic. What's this about a hash? Well, that's our cue to dig into how Go handles things under the hood.
When explaining the internals of something like a map, it's easy to get bogged down in the nitty-gritty details of the Go source code. But we're going to keep it light and simple so even those new to Go can follow along.
What you see as a single map in your Go code is actually an abstraction that hides the complex details of how the data is organized. In reality, a Go map is composed of many smaller units called "buckets."
type hmap struct { ... buckets unsafe.Pointer ... }
Look at Go source code above, a map contains a pointer that points to the bucket array.
This is why when you assign a map to a variable or pass it to a function, both the variable and the function's argument are sharing the same map pointer.
func changeMap(m2 map[string]int) { m2["hello"] = 2 } func main() { m1 := map[string]int{"hello": 1} changeMap(m1) println(m1["hello"]) // 2 }
But don't get it twisted, maps are pointers to the hmap under the hood, but they aren't reference types, nor are they passed by reference like a ref argument in C#, if you change the whole map m2, it won't reflect on the original map m1 in the caller.
func changeMap(m2 map[string]int) { m2 = map[string]int{"hello": 2} } func main() { m1 := map[string]int{"hello": 1} changeMap(m1) println(m1["hello"]) // 1 }
In Go, everything is passed by value. What's really happening is a bit different: when you pass the map m1 to the changeMap function, Go makes a copy of the *hmap structure. So, m1 in the main() and m2 in the changeMap() function are technically different pointers point to the same hmap.
For more on this topic, there's a great post by Dave Cheney titled There is no pass-by-reference in Go.
Each of these buckets can only hold up to 8 key-value pairs, as you can see in the image below.
The map above has 2 buckets, and len(map) is 6.
So, when you add a key-value pair to a map, Go doesn't just drop it in there randomly or sequentially. Instead, it places the pair into one of these buckets based on the key's hash value, which is determined by hash(key, seed).
Let's see the simplest assignment scenario in the image below, when we have an empty map, and assign a key-value pair "hello": 1 to it.
It starts by hashing "hello" to a number, then it takes that number and mods it by the number of buckets.
Since we only have one bucket here, any number mod 1 is 0, so it's going straight into bucket 0 and the same process happens when you add another key-value pair. It'll try to place it in bucket 0, and if the first slot's taken or has a different key, it'll move to the next slot in that bucket.
Take a look at the hash(key, seed), when you use a for-range loop over two maps with the same keys, you might notice that the keys come out in a different order:
func main() { a := map[string]int{"a": 1, "b": 2, "c": 3, "d": 4, "e": 5, "f": 6} b := map[string]int{"a": 1, "b": 2, "c": 3, "d": 4, "e": 5, "f": 6} for i := range a { print(i, " ") } println() for i := range b { print(i, " ") } } // Output: // a b c d e f // c d e f a b
How's that possible? Isn't the key "a" in map a and the key "a" in map b hashed the same way?
But here's the deal, while the hash function used for maps in Go is consistent across all maps with the same key type, the seed used by that hash function is different for each map instance. So, when you create a new map, Go generates a random seed just for that map.
In the example above, both a and b use the same hash function because their keys are string types, but each map has its own unique seed.
"Wait, a bucket has only 8 slots? What happens if the bucket gets full? Does it grow like a slice?"
Well, sort of. When the buckets start getting full, or even almost full, depending on the algorithm's definition of "full", the map will trigger a growth, which might double the number of main buckets.
But here's where it gets a bit more interesting.
Wenn ich „Haupt-Buckets“ sage, beziehe ich mich auf ein anderes Konzept: „Überlauf-Buckets“. Diese kommen ins Spiel, wenn es zu einer Situation mit vielen Kollisionen kommt. Stellen Sie sich vor, Sie haben 4 Buckets, aber einer davon ist aufgrund hoher Kollisionen vollständig mit 8 Schlüssel-Wert-Paaren gefüllt, während die anderen 3 Buckets leer bleiben.
Der vollständige Beitrag ist hier verfügbar: https://victoriametrics.com/blog/go-map/.
Atas ialah kandungan terperinci Peta Go Dijelaskan: Cara Pasangan Nilai Kunci Sebenarnya Disimpan. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!