моя контактная информация
Почтамезофия@protonmail.com
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] = "五"
Получите значение в соответствии с ключом, ок — это бит флага, если он найден, тип — логический
Если значение не найдено, об ошибке не будет сообщено и будет возвращено нулевое значение соответствующего типа.
value, ok := mapName[key]
if !ok {
fmt.Println(ok)
}
Примечание: обход карты неупорядочен.
for key, value := range myMap {
// 处理每对键值
}
// 例子
for i, s := range m {
fmt.Println(i, s)
}
Основная суть карты в языке Голанг заключается в использовании
hashmap
реализован, поэтому карта по сути哈希表
。
哈希表
это использование哈希函数
Организуйте данные в структуры данных, которые поддерживают быструю вставку и поиск.
哈希函数
, также известная как хеш-функция, — это функция, которая преобразует входные данные любой длины (например, строку) в выходные данные фиксированной длины с помощью определенного хэш-алгоритма. Хэш-значения обычно хранятся в виде массива, чтобы обеспечить производительность доступа к хеш-значениям.
Если диапазон входных данных превышает диапазон отображаемых выходных данных, это может привести к тому, что разные входные данные получат один и тот же выходной сигнал.
哈希冲突
。Обычно есть два пути решения этой проблемы:
开放地址法
и拉链法
Метод открытого адреса:
Структуры данных обычно реализуются с использованием массивов.
недостаток:
Этот подход требует больше места для разрешения конфликтов, поскольку не только сохраняются данные, но и требуется дополнительное пространство для разрешения конфликтов.
Метод Zipper (языковая карта 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 автоматически внедрит соответствующие ключи, значения и другие структуры в соответствии с пользовательским кодом.
Карта поверхности
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)
}
Согласно приведенному выше коду, существует два условия для оценки расширения:
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
Или реализуйте свою собственную, одновременно безопасную карту.