2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
map est une collection non ordonnée de paires clé-valeur au format kv
var name map[key_type]value_type
// 方式一
var m map[int]string = map[int]string{}
// 方式二
m := map[int]string{
1 : "老一",
2 : "老二",
3 : "老三",
}
// 方式三:5代表容量,也就是在内存中占用多大的空间,可以省略
m := make(map[int]string,5)
mapName[key] = value
// 假设map名为m,key为int,value为string
m[5] = "老五"
Supprimez les éléments en fonction des clés et aucune erreur ne sera signalée lors de la suppression de clés inexistantes.
delete(mapName, key)
// 假设map名为m,key为int,value为string
delete(m, 5)
Pour modifier, vous pouvez modifier directement la valeur correspondant à la clé spécifiée.
mapName[key] = newValue
// 假设map名为m,key为int,value为string
m[5] = "五"
Obtenez la valeur en fonction de la clé, ok est le bit d'indicateur s'il est trouvé, le type est booléen
Si la valeur n'est pas trouvée, aucune erreur ne sera signalée et une valeur nulle du type correspondant sera renvoyée.
value, ok := mapName[key]
if !ok {
fmt.Println(ok)
}
Remarque : la traversée de la carte n'est pas ordonnée
for key, value := range myMap {
// 处理每对键值
}
// 例子
for i, s := range m {
fmt.Println(i, s)
}
L'essence sous-jacente de la carte en langage golang est d'utiliser
hashmap
est implémenté, donc la carte est essentiellement哈希表
。
哈希表
est une utilisation哈希函数
Organisez les données dans des structures de données qui prennent en charge une insertion et une recherche rapides.
哈希函数
, également connue sous le nom de fonction de hachage, est une fonction qui convertit une entrée de n'importe quelle longueur (comme une chaîne) en une sortie de longueur fixe via un algorithme de hachage spécifique. Les valeurs de hachage sont généralement stockées sous forme de tableau pour garantir les performances d'accès aux valeurs de hachage.
Si la plage de l'entrée dépasse la plage de la sortie mappée, différentes entrées peuvent obtenir la même sortie.
哈希冲突
。Il existe généralement deux manières de résoudre ce problème :
开放地址法
et拉链法
Méthode d'adresse ouverte :
Les structures de données sont généralement implémentées à l'aide de tableaux
défaut:
Cette approche nécessite plus d'espace pour résoudre les conflits, car non seulement les données sont stockées, mais un espace supplémentaire est également nécessaire pour résoudre les collisions.
Méthode Zipper (go Language Map utilise cette méthode) :
Les tableaux et les listes chaînées sont généralement utilisés comme structures de données sous-jacentes
Une liste chaînée liée à différents index d’un tableau est également appelée compartiment.
type hmap struct {
count int // 当前哈希表中的元素数量,即键值对数量,可用内置函数len()获取
flags uint8 // 标志位,标记map状态和属性的字段,如正在迭代等状态
B uint8 // 表示哈希表桶(buckets)的数量为2的B次方
noverflow uint16 // 溢出桶的大致数量,扩容时会用到
hash0 uint32 // 哈希种子,对key做哈希是加入种子计算哈希值,确保map安全性
buckets unsafe.Pointer // 存储桶数组的指针
oldbuckets unsafe.Pointer // 扩容时用于保存旧桶数组的指针 , 大小为新桶数组的一半
nevacuate uintptr // 扩容时的迁移进度器,迁移桶下标小于此值说明完成迁移
extra *mapextra // 溢出桶的指针,指向mapextra结构体,用于存储一些额外的字段和信息
}
// mapextra 处理桶溢出的结构体
type mapextra struct {
overflow *[]*bmap // 溢出桶数组指针,仅当key和elem非指针时才使用
oldoverflow *[]*bmap // 旧的溢出桶数组指针,仅当key和elem非指针时才使用
nextOverflow *bmap // 下一个可用的溢出桶地址
}
Dans le code source, le type bmap n'a qu'un seul champ tophash.Mais lors de la compilation, le compilateur Go injectera automatiquement la clé, la valeur et d'autres structures correspondantes en fonction du code utilisateur.
Carte bmap de surface
type bmap struct {
// tophash generally contains the top byte of the hash value
// for each key in this bucket. If tophash[0] < minTopHash,
// tophash[0] is a bucket evacuation state instead.
tophash [bucketCnt]uint8
// Followed by bucketCnt keys and then bucketCnt elems.
// NOTE: packing all the keys together and then all the elems together makes the
// code a bit more complicated than alternating key/elem/key/elem/... but it allows
// us to eliminate padding which would be needed for, e.g., map[int64]int8.
// Followed by an overflow pointer.
}
bmap réel
// 编译期间会动态地创建一个新的结构:
type bmap struct {
topbits [8]uint8 // 这里存储哈希值的高八位,用于在确定key的时候快速试错,加快增删改查寻址效率,有时候也叫tophash
keys [8]keytype // 存储key的数组,这里bmap最多存储8个键值对
elems [8]valuetype // 存储value的数组,这里bmap也最多存储8个键值对
...
overflow uintptr // 溢出桶指针
}
Dans le langage Go, l'expansion de la carte est effectuée automatiquement pour maintenir les performances de la carte.
Premièrement, lors de l'écriture, la carte passeruntime.mapassign
Déterminer si une expansion est nécessaire
func mapassign(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {
...
// If we hit the max load factor or we have too many overflow buckets,
// and we're not already in the middle of growing, start growing.
if !h.growing() && (overLoadFactor(h.count+1, h.B) || tooManyOverflowBuckets(h.noverflow, h.B)) {
hashGrow(t, h)
goto again // Growing the table invalidates everything, so try again
}
...
}
// overLoadFactor reports whether count items placed in 1<<B buckets is over loadFactor.
func overLoadFactor(count int, B uint8) bool {
return count > bucketCnt && uintptr(count) > loadFactorNum*(bucketShift(B)/loadFactorDen)
}
func tooManyOverflowBuckets(noverflow uint16, B uint8) bool {
if B > 15 {
B = 15
}
return noverflow >= uint16(1)<<(B&15)
}
Selon le code ci-dessus, il y a deux conditions pour juger de l’expansion :
overLoadFactor(h.count+1, h.B)
, facteur de charge = nombre d'éléments ÷ nombre de godetstooManyOverflowBuckets(h.noverflow, h.B))
Méthode d'expansion :
Lorsque le facteur de charge est trop grand, un nouveau compartiment est créé. La nouvelle longueur du compartiment est le double de la longueur d'origine, puis les anciennes données du compartiment sont déplacées vers le nouveau compartiment.
Il n'y a pas beaucoup de données, mais il y a trop de compartiments de débordement.Pendant l'expansion, le nombre de compartiments reste inchangé. Les opérations de relocalisation similaires à l'expansion incrémentielle sont à nouveau effectuées et les paires clé-valeur lâches sont réorganisées pour augmenter l'utilisation du compartiment et garantir un accès plus rapide.
Étapes d'extension :
func hashGrow(t *maptype, h *hmap) {
...
// 原有桶设置给oldbuckets
oldbuckets := h.buckets
// 创建新桶
newbuckets, nextOverflow := makeBucketArray(t, h.B+bigger, nil)
flags := h.flags &^ (iterator | oldIterator)
if h.flags&iterator != 0 {
flags |= oldIterator
}
// commit the grow (atomic wrt gc)
h.B += bigger
h.flags = flags
h.oldbuckets = oldbuckets
h.buckets = newbuckets
h.nevacuate = 0
h.noverflow = 0
...
}
// 这个是mapdelete函数中的处理迁移的位置
func mapassign(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {
...
if h.growing() {
//
growWork(t, h, bucket)
}
...
}
渐进式驱逐
Migrez les paires clé-valeur. Cela signifie que lors de l'expansion, l'ancien tableau de compartiments et le nouveau tableau de compartiments existeront en même temps, les paires clé-valeur nouvellement insérées seront placées directement dans le nouveau compartiment et l'accès à l'ancien compartiment déclenchera une opération de migration.// 进入后是一个简单的判断,之后的evacuate是核心逻辑处理,特别多,感兴趣自己看源码
func growWork(t *maptype, h *hmap, bucket uintptr) {
// make sure we evacuate the oldbucket corresponding
// to the bucket we're about to use
evacuate(t, h, bucket&h.oldbucketmask())
// evacuate one more oldbucket to make progress on growing
if h.growing() {
evacuate(t, h, h.nevacuate)
}
}
int
ouint64
。sync.Map
Ou implémentez votre propre carte simultanément sécurisée.