2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
Map ist eine ungeordnete Sammlung von Schlüssel-Wert-Paaren im KV-Format
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] = "老五"
Löschen Sie Elemente basierend auf Schlüsseln. Beim Löschen nicht vorhandener Schlüssel wird kein Fehler gemeldet.
delete(mapName, key)
// 假设map名为m,key为int,value为string
delete(m, 5)
Zum Ändern können Sie den Wert, der dem angegebenen Schlüssel entspricht, direkt ändern.
mapName[key] = newValue
// 假设map名为m,key为int,value为string
m[5] = "五"
Holen Sie sich den Wert gemäß dem Schlüssel. Ok ist das Flag-Bit, ob er gefunden wird. Der Typ ist Boolesch
Wenn der Wert nicht gefunden wird, wird kein Fehler gemeldet und ein Nullwert des entsprechenden Typs zurückgegeben.
value, ok := mapName[key]
if !ok {
fmt.Println(ok)
}
Hinweis: Die Kartendurchquerung ist ungeordnet
for key, value := range myMap {
// 处理每对键值
}
// 例子
for i, s := range m {
fmt.Println(i, s)
}
Die zugrunde liegende Essenz der Karte in der Golang-Sprache ist die Verwendung
hashmap
implementiert ist, also ist die Karte im Wesentlichen哈希表
。
哈希表
ist eine Verwendung哈希函数
Organisieren Sie Daten in Datenstrukturen, die schnelles Einfügen und Suchen unterstützen.
哈希函数
, auch als Hash-Funktion bekannt, ist eine Funktion, die eine Eingabe beliebiger Länge (z. B. eine Zeichenfolge) über einen bestimmten Hash-Algorithmus in eine Ausgabe fester Länge umwandelt. Um die Zugriffsleistung von Hash-Werten sicherzustellen, werden Hash-Werte üblicherweise in einer Array-ähnlichen Form gespeichert.
Wenn der Bereich der Eingabe den Bereich der zugeordneten Ausgabe überschreitet, kann dies dazu führen, dass verschiedene Eingaben die gleiche Ausgabe erhalten
哈希冲突
。Normalerweise gibt es zwei Möglichkeiten, dieses Problem zu lösen:
开放地址法
Und拉链法
Offene Adressmethode:
Datenstrukturen werden normalerweise mithilfe von Arrays implementiert
Mangel:
Dieser Ansatz erfordert mehr Speicherplatz zum Lösen von Konflikten, da nicht nur die Daten gespeichert werden, sondern auch zusätzlicher Speicherplatz zum Lösen von Kollisionen erforderlich ist.
Zipper-Methode (Go Language Map verwendet diese Methode):
Als zugrunde liegende Datenstrukturen werden üblicherweise Arrays und verknüpfte Listen verwendet
Eine verknüpfte Liste, die an verschiedenen Indizes eines Arrays verknüpft ist, wird auch Bucket genannt.
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 // 下一个可用的溢出桶地址
}
Im Quellcode verfügt der Bmap-Typ nur über ein Tophash-Feld.Während der Kompilierung fügt der Go-Compiler jedoch automatisch entsprechende Schlüssel, Werte und andere Strukturen entsprechend dem Benutzercode ein.
Oberflächen-Bmap
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.
}
tatsächliche Bmap
// 编译期间会动态地创建一个新的结构:
type bmap struct {
topbits [8]uint8 // 这里存储哈希值的高八位,用于在确定key的时候快速试错,加快增删改查寻址效率,有时候也叫tophash
keys [8]keytype // 存储key的数组,这里bmap最多存储8个键值对
elems [8]valuetype // 存储value的数组,这里bmap也最多存储8个键值对
...
overflow uintptr // 溢出桶指针
}
In der Go-Sprache wird die Kartenerweiterung automatisch durchgeführt, um die Kartenleistung aufrechtzuerhalten.
Beim Schreiben geht zunächst die Karte durchruntime.mapassign
Stellen Sie fest, ob eine Erweiterung erforderlich ist
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)
}
Gemäß dem obigen Code gibt es zwei Bedingungen für die Beurteilung der Expansion:
overLoadFactor(h.count+1, h.B)
, Lastfaktor = Anzahl der Elemente ÷ Anzahl der EimertooManyOverflowBuckets(h.noverflow, h.B))
Erweiterungsmethode:
Wenn der Lastfaktor zu groß ist, wird ein neuer Bucket erstellt. Die neue Bucket-Länge beträgt das Doppelte der ursprünglichen Länge, und dann werden die alten Bucket-Daten in den neuen Bucket verschoben.
Es gibt nicht viele Daten, aber zu viele Überlaufbuckets.Während der Erweiterung bleibt die Anzahl der Buckets unverändert, ähnlich wie bei der inkrementellen Erweiterung, und lose Schlüssel-Wert-Paare werden neu angeordnet, um die Bucket-Nutzung zu erhöhen und einen schnelleren Zugriff zu gewährleisten.
Ausbauschritte:
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)
}
...
}
渐进式驱逐
Schlüssel-Wert-Paare migrieren. Dies bedeutet, dass während der Erweiterung das alte Bucket-Array und das neue Bucket-Array gleichzeitig vorhanden sind, neu eingefügte Schlüssel-Wert-Paare direkt im neuen Bucket platziert werden und der Zugriff auf den alten Bucket einen Migrationsvorgang auslöst.// 进入后是一个简单的判断,之后的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
oderint64
。sync.Map
Oder implementieren Sie Ihre eigene gleichzeitig sichere Karte.