내 연락처 정보
우편메소피아@프로톤메일.com
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
map은 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 언어에서 map의 기본 본질은 다음을 사용하는 것입니다.
hashmap
구현되었으므로 지도는 기본적으로哈希表
。
哈希表
용도이다哈希函数
빠른 삽입과 검색을 지원하는 데이터 구조로 데이터를 구성합니다.
哈希函数
해시 함수라고도 알려진 은 특정 해시 알고리즘을 통해 임의 길이의 입력(예: 문자열)을 고정 길이 출력으로 변환하는 함수입니다. 해시 값은 일반적으로 해시 값의 액세스 성능을 보장하기 위해 배열과 같은 형태로 저장됩니다.
입력 범위가 매핑된 출력 범위를 초과하면 서로 다른 입력이 동일한 출력을 얻을 수 있습니다.
哈希冲突
。이 문제를 해결하는 방법에는 일반적으로 두 가지가 있습니다.
开放地址法
그리고拉链法
공개 주소 방법:
데이터 구조는 일반적으로 배열을 사용하여 구현됩니다.
결점:
이 접근 방식을 사용하면 데이터가 저장될 뿐만 아니라 충돌을 해결하려면 추가 공간이 필요하므로 충돌을 해결하려면 더 많은 공간이 필요합니다.
지퍼 방식(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 필드가 하나만 있습니다.그러나 컴파일하는 동안 Go 컴파일러는 사용자 코드에 따라 해당 키, 값 및 기타 구조를 자동으로 삽입합니다.
표면 B맵
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.
}
실제 비맵
// 编译期间会动态地创建一个新的结构:
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)
}
위의 코드에 따르면 확장을 판단하는 데는 두 가지 조건이 있습니다.
overLoadFactor(h.count+1, h.B)
, 부하율 = 요소 수 ¼ 버킷 수tooManyOverflowBuckets(h.noverflow, h.B))
확장 방법:
부하율이 너무 크면 새 버킷이 생성되고, 새 버킷 길이는 원래 길이의 두 배가 되며, 이전 버킷 데이터는 새 버킷으로 이동됩니다.
데이터가 많지 않은데 오버플로 버킷이 너무 많습니다.확장 중에 버킷 수는 변경되지 않습니다. 증분 확장과 유사한 재배치 작업이 다시 수행되고 느슨한 키-값 쌍이 재배열되어 버킷 사용량이 늘어나고 더 빠른 액세스가 보장됩니다.
확장 단계:
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
또는 동시에 안전한 자신만의 맵을 구현하세요.