私の連絡先情報
郵便メール:
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
マップは、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] = "老五"
キーに基づいて要素を削除します。存在しないキーを削除してもエラーは報告されません。
delete(mapName, key)
// 假设map名为m,key为int,value为string
delete(m, 5)
変更するには、指定したキーに対応する値を直接変更できます。
mapName[key] = newValue
// 假设map名为m,key为int,value为string
m[5] = "五"
キーに従って値を取得します。ok は見つかったかどうかのフラグビットです。タイプはブールです。
値が見つからない場合、エラーは報告されず、対応する型の null 値が返されます。
value, ok := mapName[key]
if !ok {
fmt.Println(ok)
}
注: マップの走査は順序付けされていません
for key, value := range myMap {
// 处理每对键值
}
// 例子
for i, s := range m {
fmt.Println(i, s)
}
Golang 言語のマップの根本的な本質は、次のとおりです。
hashmap
実装されているため、マップは基本的に哈希表
。
哈希表
用途です哈希函数
データを、高速な挿入と検索をサポートするデータ構造に編成します。
哈希函数
は、ハッシュ関数とも呼ばれ、特定のハッシュ アルゴリズムを通じて任意の長さの入力 (文字列など) を固定長の出力に変換する関数です。ハッシュ値は、通常、ハッシュ値のアクセスパフォーマンスを確保するために配列のような形式で格納されます。
入力の範囲がマップされた出力の範囲を超えると、異なる入力が同じ出力を取得する可能性があります。
哈希冲突
。通常、この問題を解決するには次の 2 つの方法があります。
开放地址法
そして拉链法
オープンアドレス方式:
データ構造は通常、配列を使用して実装されます
欠点:
このアプローチでは、データが保存されるだけでなく、衝突を解決するために追加のスペースが必要になるため、競合を解決するためにより多くのスペースが必要になります。
ジッパーメソッド (Go 言語マップはこのメソッドを使用します):
配列とリンク リストは通常、基礎となるデータ構造として使用されます。
配列の異なるインデックスでリンクされたリンク リストはバケットとも呼ばれます。
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 // 下一个可用的溢出桶地址
}
ソース コードでは、bmap タイプには tophash フィールドが 1 つだけあります。ただし、コンパイル中に、Go コンパイラーはユーザー コードに従って、対応するキー、値、その他の構造を自動的に挿入します。
サーフェス 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.
}
実際のbmap
// 编译期间会动态地创建一个新的结构:
type bmap struct {
topbits [8]uint8 // 这里存储哈希值的高八位,用于在确定key的时候快速试错,加快增删改查寻址效率,有时候也叫tophash
keys [8]keytype // 存储key的数组,这里bmap最多存储8个键值对
elems [8]valuetype // 存储value的数组,这里bmap也最多存储8个键值对
...
overflow uintptr // 溢出桶指针
}
Go 言語では、マップのパフォーマンスを維持するためにマップの拡張が自動的に実行されます。
まず、書き込み時にマップが渡されますruntime.mapassign
拡張が必要かどうかを判断する
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)
}
上記のコードによれば、展開を判断するための条件は 2 つあります。
overLoadFactor(h.count+1, h.B)
、負荷率 = 要素数 ÷ バケット数tooManyOverflowBuckets(h.noverflow, h.B))
拡張方法:
負荷率が大きすぎる場合は、元の長さの 2 倍の新しいバケットが作成され、古いバケットのデータが新しいバケットに移動されます。
データはそれほど多くありませんが、オーバーフロー バケットが多すぎます。拡張中、バケットの数は変更されず、増分拡張と同様の再配置操作が再度実行され、バケットの使用量を増やしてアクセスを高速化するために、緩いキーと値のペアが再配置されます。
拡張手順:
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)
}
...
}
渐进式驱逐
キーと値のペアを移行します。これは、拡張中、古いバケット配列と新しいバケット配列が同時に存在し、新しく挿入されたキーと値のペアが新しいバケットに直接配置され、古いバケットへのアクセスによって移行操作がトリガーされることを意味します。// 进入后是一个简单的判断,之后的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
またはint64
。sync.Map
または、独自の同時安全マップを実装します。