Maison développement back-end Golang Go Maps expliqué : comment les paires clé-valeur sont réellement stockées

Go Maps expliqué : comment les paires clé-valeur sont réellement stockées

Sep 10, 2024 am 11:01 AM

Ceci est un extrait du post ; l'article complet est disponible ici : https://victoriametrics.com/blog/go-map/.

Si vous êtes nouveau sur Go, il peut être un peu déroutant de comprendre comment utiliser les cartes dans Go. Et même lorsque vous êtes plus expérimenté, comprendre comment fonctionnent réellement les cartes peut être assez difficile.

Prenons cet exemple : avez-vous déjà défini un « indice » pour une carte et vous êtes-vous demandé pourquoi on l'appelle « indice » et non quelque chose de simple comme la longueur, comme nous le faisons avec les tranches ?

// hint = 10
m := make(map[string]int, 10)
Copier après la connexion

Ou peut-être avez-vous remarqué que lorsque vous utilisez une boucle for-range sur une carte, l'ordre ne correspond pas à l'ordre d'insertion, et il change même si vous parcourez la même carte à des moments différents. Mais bizarrement, si vous faites une boucle dessus exactement au même moment, l'ordre reste généralement le même.

C'est une longue histoire, alors attachez votre ceinture de sécurité et plongez-y.

Avant de continuer, juste un avertissement : les informations ici sont basées sur Go 1.23. Si les choses ont changé et que ce n'est pas à jour, n'hésitez pas à m'envoyer un ping sur X (@func25).

Map in Go : Démarrage rapide

Parlons donc des cartes dans Go, c'est un type intégré qui agit comme un stockage clé-valeur. Contrairement aux tableaux dans lesquels vous êtes coincé avec des clés sous forme d'indices croissants comme 0, 1, 2, etc., avec des cartes, la clé peut être de n'importe quel type comparable.

Cela vous donne beaucoup plus de flexibilité.

m := make(map[string]int)
m["a"] = 1
m["b"] = 2

m // map[a:1 b:2]
Copier après la connexion

Dans cet exemple, nous avons créé une carte vide en utilisant make(), où les clés sont des chaînes et les valeurs sont des entiers.

Go Maps Explained: How Key-Value Pairs Are Actually Stored

Carte["a": 1, "b": 2]

Maintenant, au lieu d'attribuer manuellement chaque clé, vous pouvez gagner du temps en utilisant une carte littérale. Cela vous permet de configurer vos paires clé-valeur en même temps, dès la création de la carte :

m := map[string]int{
    "a": 1,
    "b": 2,
}
Copier après la connexion

Tout ce que vous faites est de lister les clés et leurs valeurs entre accolades lorsque vous créez la carte pour la première fois, aussi simple que cela.

Et si vous réalisez plus tard que vous n'avez plus besoin d'une certaine paire clé-valeur, Go est là pour vous. Il existe une fonction de suppression pratique qui supprime la clé dont vous ne voulez pas : delete(m, "a").

La valeur zéro d'une carte est nulle, et une carte nulle est un peu comme une carte vide à certains égards. Vous pouvez essayer d'y rechercher des clés, et Go ne paniquera pas et ne fera pas planter votre programme.

Si vous recherchez une clé qui n'est pas là, Go vous donne simplement la "valeur zéro" pour le type de valeur de cette carte :

var m map[string]int

println(m["a"]) // 0
m["a"] = 1      // panic: assignment to entry in nil map
Copier après la connexion

Mais voici le problème : vous ne pouvez pas ajouter de nouvelles paires clé-valeur à une carte nulle.

En fait, Go gère les cartes d'une manière assez similaire à la façon dont il gère les tranches. Les cartes et les tranches commencent par zéro, et Go ne panique pas si vous faites quelque chose d'« inoffensif » avec elles alors qu'elles sont nulles. Par exemple, vous pouvez parcourir une tranche nulle sans aucun « drame ».

Alors, que se passe-t-il si vous essayez de parcourir une carte nulle ?

var m map[string]int

for k, v := range m {
    println(k, v)
} 
Copier après la connexion

Rien ne se passe, aucune erreur, aucune surprise. Cela ne fait rien en silence.

L'approche de Go consiste à traiter la valeur par défaut de tout type comme quelque chose d'utile, et non comme quelque chose qui fait exploser votre programme. Le seul moment où Go fait une crise, c'est lorsque vous faites quelque chose de vraiment illégal, comme essayer d'ajouter une nouvelle paire clé-valeur à une carte nulle ou accéder à un index hors limites dans une tranche.

Il y a quelques autres choses que vous devez savoir sur les cartes dans Go :

  • Une boucle for-range sur une carte ne renverra pas les clés dans un ordre spécifique.
  • Les cartes ne sont pas thread-safe, le runtime Go provoquera une erreur fatale si vous essayez de lire (ou d'itérer avec une plage for) et d'écrire simultanément sur la même carte.
  • Vous pouvez vérifier si une clé est dans une carte en effectuant une simple vérification ok : _, ok := m[key].
  • Le type de clé d'une carte doit être comparable.

Plongeons dans ce dernier point concernant les clés de carte. J'ai mentionné plus tôt que "la clé pourrait être de n'importe quel type comparable", mais il y a un peu plus que cela.

"Alors, qu'est-ce qu'un type comparable exactement ? Et qu'est-ce qui ne l'est pas ?"

C'est assez simple : si vous pouvez utiliser == pour comparer deux valeurs du même type, alors ce type est considéré comme comparable.

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)
Copier après la connexion

Mais comme vous pouvez le voir, le code ci-dessus ne se compile même pas. Le compilateur se plaint : "opération invalide : s == s (la carte ne peut être comparée qu'à zéro)."

Cette même règle s'applique à d'autres types non comparables comme les tranches, les fonctions ou les structures qui contiennent des tranches ou des cartes, etc. Ainsi, si vous essayez d'utiliser l'un de ces types comme clés dans une carte, vous êtes pas de chance.

func main() {
  var s map[[]int]string
}

// compile error: invalid map key type []intcompilerIncomparableMapKey
Copier après la connexion

Mais voici un petit secret, les interfaces peuvent être à la fois comparables et non comparables.

Qu'est-ce que ça veut dire ? Vous pouvez absolument définir une carte avec une interface vide comme clé sans aucune erreur de compilation. Mais attention, il y a de fortes chances que vous rencontriez une erreur d'exécution.

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()
Copier après la connexion

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.

Map Anatomy

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
  ...
}
Copier après la connexion

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
}
Copier après la connexion

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
}
Copier après la connexion

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.

Go Maps Explained: How Key-Value Pairs Are Actually Stored

Map is passed by value

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.

Go Maps Explained: How Key-Value Pairs Are Actually Stored

Buckets of a map

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.

Go Maps Explained: How Key-Value Pairs Are Actually Stored

Assign a key-value pair to an empty map

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     
Copier après la connexion

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.

Quand je dis « seaux principaux », je parle d'un autre concept : « seaux de débordement ». Ceux-ci entrent en jeu lorsque vous vous trouvez dans une situation de collisions élevées. Imaginez que vous ayez 4 compartiments, mais que l'un d'eux est complètement rempli de 8 paires clé-valeur en raison de collisions élevées, tandis que les 3 autres compartiments sont vides.

L'article complet est disponible ici : https://victoriametrics.com/blog/go-map/.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn

Outils d'IA chauds

Undresser.AI Undress

Undresser.AI Undress

Application basée sur l'IA pour créer des photos de nu réalistes

AI Clothes Remover

AI Clothes Remover

Outil d'IA en ligne pour supprimer les vêtements des photos.

Undress AI Tool

Undress AI Tool

Images de déshabillage gratuites

Clothoff.io

Clothoff.io

Dissolvant de vêtements AI

Video Face Swap

Video Face Swap

Échangez les visages dans n'importe quelle vidéo sans effort grâce à notre outil d'échange de visage AI entièrement gratuit !

Article chaud

<🎜>: Grow A Garden - Guide de mutation complet
3 Il y a quelques semaines By DDD
<🎜>: Bubble Gum Simulator Infinity - Comment obtenir et utiliser les clés royales
3 Il y a quelques semaines By 尊渡假赌尊渡假赌尊渡假赌
Mandragora: Whispers of the Witch Tree - Comment déverrouiller le grappin
3 Il y a quelques semaines By 尊渡假赌尊渡假赌尊渡假赌
Nordhold: Système de fusion, expliqué
3 Il y a quelques semaines By 尊渡假赌尊渡假赌尊渡假赌

Outils chauds

Bloc-notes++7.3.1

Bloc-notes++7.3.1

Éditeur de code facile à utiliser et gratuit

SublimeText3 version chinoise

SublimeText3 version chinoise

Version chinoise, très simple à utiliser

Envoyer Studio 13.0.1

Envoyer Studio 13.0.1

Puissant environnement de développement intégré PHP

Dreamweaver CS6

Dreamweaver CS6

Outils de développement Web visuel

SublimeText3 version Mac

SublimeText3 version Mac

Logiciel d'édition de code au niveau de Dieu (SublimeText3)

Sujets chauds

Tutoriel Java
1667
14
Tutoriel PHP
1273
29
Tutoriel C#
1255
24
Golang vs Python: performance et évolutivité Golang vs Python: performance et évolutivité Apr 19, 2025 am 12:18 AM

Golang est meilleur que Python en termes de performances et d'évolutivité. 1) Les caractéristiques de type compilation de Golang et le modèle de concurrence efficace le font bien fonctionner dans des scénarios de concurrence élevés. 2) Python, en tant que langue interprétée, s'exécute lentement, mais peut optimiser les performances via des outils tels que Cython.

Golang et C: concurrence vs vitesse brute Golang et C: concurrence vs vitesse brute Apr 21, 2025 am 12:16 AM

Golang est meilleur que C en concurrence, tandis que C est meilleur que Golang en vitesse brute. 1) Golang obtient une concurrence efficace par le goroutine et le canal, ce qui convient à la gestion d'un grand nombre de tâches simultanées. 2) C Grâce à l'optimisation du compilateur et à la bibliothèque standard, il offre des performances élevées près du matériel, adaptées aux applications qui nécessitent une optimisation extrême.

Partage avec Go: un guide du débutant Partage avec Go: un guide du débutant Apr 26, 2025 am 12:21 AM

GOISIDEALFORBEGINNERNERS et combinant pour pourcloudandNetWorkServicesDuetOtssimplicity, Efficiency, andCurrencyFeatures.1) InstallgofromTheofficialwebsiteandverifywith'goversion'..2)

Golang vs C: Performance et comparaison de la vitesse Golang vs C: Performance et comparaison de la vitesse Apr 21, 2025 am 12:13 AM

Golang convient au développement rapide et aux scénarios simultanés, et C convient aux scénarios où des performances extrêmes et un contrôle de bas niveau sont nécessaires. 1) Golang améliore les performances grâce à des mécanismes de collecte et de concurrence des ordures, et convient au développement de services Web à haute concurrence. 2) C réalise les performances ultimes grâce à la gestion manuelle de la mémoire et à l'optimisation du compilateur, et convient au développement du système intégré.

Impact de Golang: vitesse, efficacité et simplicité Impact de Golang: vitesse, efficacité et simplicité Apr 14, 2025 am 12:11 AM

GOIMIMPACTSDEVENCEMENTSPOSITIVEMENTS INSPECT, EFFICACTION ET APPLICATION.1) VITESSE: GOCOMPILESQUICKLYANDRUNSEFFIÉMENT, IDEALFORLARGEPROROSTS.2) Efficacité: ITSCOMPEHENSIVESTANDARDLIBRARYREDUCEEXTERNEDENDENCES, EnhancingDevelovefficiency.3) Simplicité: Simplicité: Implicité de la manière

C et Golang: Lorsque les performances sont cruciales C et Golang: Lorsque les performances sont cruciales Apr 13, 2025 am 12:11 AM

C est plus adapté aux scénarios où le contrôle direct des ressources matérielles et une optimisation élevée de performances sont nécessaires, tandis que Golang est plus adapté aux scénarios où un développement rapide et un traitement de concurrence élevé sont nécessaires. 1.C's Avantage est dans ses caractéristiques matérielles proches et à des capacités d'optimisation élevées, qui conviennent aux besoins de haute performance tels que le développement de jeux. 2. L'avantage de Golang réside dans sa syntaxe concise et son soutien à la concurrence naturelle, qui convient au développement élevé de services de concurrence.

Golang vs Python: différences et similitudes clés Golang vs Python: différences et similitudes clés Apr 17, 2025 am 12:15 AM

Golang et Python ont chacun leurs propres avantages: Golang convient aux performances élevées et à la programmation simultanée, tandis que Python convient à la science des données et au développement Web. Golang est connu pour son modèle de concurrence et ses performances efficaces, tandis que Python est connu pour sa syntaxe concise et son écosystème de bibliothèque riche.

Golang et C: les compromis en performance Golang et C: les compromis en performance Apr 17, 2025 am 12:18 AM

Les différences de performance entre Golang et C se reflètent principalement dans la gestion de la mémoire, l'optimisation de la compilation et l'efficacité du temps d'exécution. 1) Le mécanisme de collecte des ordures de Golang est pratique mais peut affecter les performances, 2) la gestion manuelle de C et l'optimisation du compilateur sont plus efficaces dans l'informatique récursive.

See all articles