Loading [MathJax]/jax/input/TeX/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >深入理解Go语言中的map

深入理解Go语言中的map

原创
作者头像
windealli
发布于 2024-03-21 11:09:49
发布于 2024-03-21 11:09:49
2740
举报
文章被收录于专栏:windealliwindealli

一、引言

哈希表和数组是最常见的数据结构,几乎所有的语言都会有数组和哈希表两种容器类型 。哈希表表示的是键值对之间映射关系,在Go语言中,通过map来表示哈希表。 本文将深入浅出介绍map的概念、使用方式、底层结构、性能、最佳实现等话题,帮助开发更好的理解和使用map

二、map的基本概念和使用

1. 什么是map

在Go语言中,map是一种内置的数据结构,用于存储键值对。Go语言中的map有如下特点

  • 内置数据结构map是Go语言内置的数据结构,它是一种无序的键值对集合,其中键是唯一的。Go语言在语言级别支持map, 使用方便。
  • 快速查找map提供了非常快速的查找、插入和删除操作,这些操作的平均时间复杂度为O(1)。这使得map非常适合用于需要快速访问数据的场景。
  • 动态性map是动态的,可以在运行时动态地增加或删除键值对,而不需要预先声明大小。
  • 键的多样性:Map的键可以是任何可比较的类型,例如整数、字符串等。这为存储和检索各种类型的数据提供了灵活性。
  • 非并发安全:标准的Map在Go中并不是并发安全的。如果需要在多个goroutine中并发访问Map,需要使用sync包中的Mutex或RWMutex来保证并发安全,或者使用并发安全的数据结构,如sync.Map。
  • 应用场景:Map在Go中被广泛应用于各种场景,如数据库查询结果的存储、配置项的读取、缓存的实现等。

2. map的定义和初始化

初始化Map有几种方式:

代码语言:go
AI代码解释
复制
// 方式一: 使用var关键字声明Map,然后使用make函数初始化
var myMap map[string]int
myMap = make(map[string]int)

// 方式二: 使用make函数直接声明并初始化Map
myMap := make(map[string]int)

// 方式三: 使用Map字面量初始化Map,这在创建预填充的Map时非常有用
myMap := map[string]int{
    "apple":  5,
    "banana": 10,
}

注意: 使用Map时,如果没有初始化(即值为nil),直接赋值会导致运行时错误。

3. map基本操作:增、删、改、查

下面是对map进行增、删、改、查的基本方法

代码语言:go
AI代码解释
复制
// 增(Insert): 向Map中添加新的键值对; 如果key已存在,则更新value
myMap["orange"] = 15

// 删(Delete): 从Map中删除键值对; 如果key不存在,delete函数不会执行任何操作。
delete(myMap, "apple")

// 改(Update): 更新Map中的键值对; 如果key已经存在,这将替换原来的值。如果key不存在,这将添加一个新的键值对
myMap["banana"] = 20

// 查(Lookup):查找Map中的键对应的值;
value, exists := myMap["banana"] // 这里value是与键关联的值,exists是一个布尔值,如果键存在于Map中,则为true;如果键不存在,则为false,并且value将是类型的零值。
value := myMap["banana"] // 如果你只关心值,可以忽略第二个返回值

注意: map在并发环境下不是线程安全的,如果需要在多个goroutine中使用Map,应该使用互斥锁(sync.Mutex)或者使用sync.Map

4. map的遍历

在Go语言中,可以使用for循环和range关键字来遍历Map。遍历时,range表达式返回两个值:键和对应的值。这里是一个基本的例子:

代码语言:go
AI代码解释
复制
myMap := map[string]int{
    "apple":  5,
    "banana": 10,
    "cherry": 15,
}

for key, value := range myMap {
    fmt.Printf("Key: %s, Value: %d\n", key, value)
}

在上面的代码中,key变量会遍历Map中的所有键,而value变量会遍历对应的值。每次迭代时,keyvalue都会更新为当前遍历到的键值对。

可以忽略第二个变量

代码语言:go
AI代码解释
复制
for key := range myMap {

如果你只需要值,可以使用下划线_来忽略键:

代码语言:go
AI代码解释
复制
for _, value := range myMap {

需要注意的是,Map的遍历顺序是随机的,每次遍历的顺序可能都不一样。这是因为Go的Map类型是故意设计为无序的,以避免依赖特定的遍历顺序,这可能会导致程序中的一些隐蔽的bug。

此外,虽然可以在遍历过程中删除或修改Map中的键值对,但是添加键值对可能会导致未定义的行为。如果需要在遍历时修改Map,建议先记录需要做出的更改,然后在遍历结束后进行。

三、哈希函数简介

在Go语言中,map本质上是哈希表(hash table)。哈希表是一种使用哈希函数组织数据,以支持快速插入和搜索的数据结构。实现哈希表的两个关键是哈希函数和解决哈希冲突。

哈希函数

哈希函数,也被称为散列函数,是一种将任意长度的输入(如字符串)通过特定的散列算法,变换成固定长度的输出(即哈希值或消息摘要)的函数。通常,会使用类似数组(连续内存)的形式来存储哈希值,从而保证哈希值的访问性能。

哈希函数应该尽可能保证不同的输入有相同的输出。

如上图所示,哈希函数的结果分布较为均匀,哈希值的增删改查的时间复杂度为O(1)

哈希冲突

在实际场景中,因为可能的输入范围通常是远超映射范围(输出的范围,受存储空间的影响)。因而难免会出现不同的输入得到相同的输出,即发生哈希冲突。

基于拉链法实现的哈希函数

大多数的编程语言都用拉链法实现哈希表, 拉链法通常使用数组和链表作为底层数据结构。

哈希值使用数组将哈希值HashValue相同的Key对应的Value通过链表数组进行维护

哈希函数将哈希键Key映射到数组的索引,数组的每一个元素都有一个Value桶,使用链表进行维护。

四、map的底层数据结构

源码分析

在Go语言中,map使用类似拉链法的方式实现哈希表,Go语言运行时同时使用了多个数据结构组合表示哈希表。

代码语言:go
AI代码解释
复制
// runtime/map.go
// A header for a Go map.
type hmap struct {
	count     int // 当前哈希表中的元素数量
	flags     uint8
	B         uint8  // 当前哈希表持有的 buckets 数量, 因为哈希表中桶的数量都按2倍扩容,改字段存储对数,也就是 len(buckets) == 2^B
	noverflow uint16 // 溢出桶的大致数量
	hash0     uint32 // hash seed

	buckets    unsafe.Pointer // 存储 2^B 个桶的数组
	oldbuckets unsafe.Pointer // 扩容时用于保存之前 buckets 的字段 , 大小事buckets的一般
	nevacuate  uintptr        // progress counter for evacuation (buckets less than this have been evacuated)

	extra *mapextra // optional fields
}

// mapextra 主要维护,当hmap中的buckets中有一些桶发生溢出,但有达不到扩容阈值时,存储溢出的桶
type mapextra struct {
	overflow    *[]*bmap
	oldoverflow *[]*bmap

	// nextOverflow holds a pointer to a free overflow bucket.
	nextOverflow *bmap
}

runtime.hmap结构体中,buckets字段是一个unsafe.Pointer , 因为go语言中支持不同类型的键值对,需要在编译时才能确定map的类型。

可以查看编译时如何重建hmap类型reflectdata.MapType()

代码语言:jsx
AI代码解释
复制
func MapType(t *types.Type) *types.Type {
	if t.MapType().Hmap != nil {
		return t.MapType().Hmap
	}

	bmap := MapBucketType(t)  // 构建bmap类型

	fields := []*types.Field{
		makefield("count", types.Types[types.TINT]),
		makefield("flags", types.Types[types.TUINT8]),
		makefield("B", types.Types[types.TUINT8]),
		makefield("noverflow", types.Types[types.TUINT16]),
		makefield("hash0", types.Types[types.TUINT32]), // Used in walk.go for OMAKEMAP.
		makefield("buckets", types.NewPtr(bmap)),       // Used in walk.go for OMAKEMAP.
		makefield("oldbuckets", types.NewPtr(bmap)),
		makefield("nevacuate", types.Types[types.TUINTPTR]),
		makefield("extra", types.Types[types.TUNSAFEPTR]),
	}

	hmap := types.NewStruct(types.NoPkg, fields)
	hmap.SetNoalg(true)
	types.CalcSize(hmap)

	// The size of hmap should be 48 bytes on 64 bit
	// and 28 bytes on 32 bit platforms.
	if size := int64(8 + 5*types.PtrSize); hmap.Size() != size {
		base.Fatalf("hmap size not correct: got %d, want %d", hmap.Size(), size)
	}

	t.MapType().Hmap = hmap
	hmap.StructType().Map = t
	return hmap
}

这里可以看出buckets是指向bmap的指针, bmap也是在编译时通过bmap := MapBucketType(t) 进行构建的,而非runtime.bmap中的定义那般简单:

代码语言:go
AI代码解释
复制
// runtime.bmap
type bmap struct {
	tophash [bucketCnt]uint8
}

可以查看

代码语言:go
AI代码解释
复制
// MapBucketType makes the map bucket type given the type of the map.
func MapBucketType(t *types.Type) *types.Type {
	if t.MapType().Bucket != nil {
		return t.MapType().Bucket
	}

	keytype := t.Key()
	elemtype := t.Elem()
	types.CalcSize(keytype)
	types.CalcSize(elemtype)
	if keytype.Size() > MAXKEYSIZE {
		keytype = types.NewPtr(keytype)
	}
	if elemtype.Size() > MAXELEMSIZE {
		elemtype = types.NewPtr(elemtype)
	}

	field := make([]*types.Field, 0, 5)

	// The first field is: uint8 topbits[BUCKETSIZE].
	arr := types.NewArray(types.Types[types.TUINT8], BUCKETSIZE)
	field = append(field, makefield("topbits", arr))

	arr = types.NewArray(keytype, BUCKETSIZE)
	arr.SetNoalg(true)
	keys := makefield("keys", arr)
	field = append(field, keys)

	arr = types.NewArray(elemtype, BUCKETSIZE)
	arr.SetNoalg(true)
	elems := makefield("elems", arr)
	field = append(field, elems)

	// If keys and elems have no pointers, the map implementation
	// can keep a list of overflow pointers on the side so that
	// buckets can be marked as having no pointers.
	// Arrange for the bucket to have no pointers by changing
	// the type of the overflow field to uintptr in this case.
	// See comment on hmap.overflow in runtime/map.go.
	otyp := types.Types[types.TUNSAFEPTR]
	if !elemtype.HasPointers() && !keytype.HasPointers() {
		otyp = types.Types[types.TUINTPTR]
	}
	overflow := makefield("overflow", otyp)
	field = append(field, overflow)

	// link up fields
	bucket := types.NewStruct(types.NoPkg, field[:])
	bucket.SetNoalg(true)
	types.CalcSize(bucket)
	
	// ......
	
	return bucket
}

重建后的结构体如下所示:

代码语言:go
AI代码解释
复制
type bmap struct {
    topbits  [8]uint8
    keys     [8]keytype
    elems    [8]elemtype
    overflow uintptr
}

示意图

综上所示,map的底层结构如下图所示:

五、map的操作性能分析

1. 时间复杂度:最好和最坏情况

  • 最好情况: 每个键都映射到不同的桶中,没有发生哈希冲突。此时,Map的插入、查找和删除操作的时间复杂度都是O(1)。
  • 最坏情况: 所有的键都映射到同一个桶中,发生了极端的哈希冲突。此时,Map中的操作可能需要遍历整个链表,时间复杂度变为O(n)。不过,由于Go的Map实现会自动扩容,并重新分配键值对,这种情况在实践中很少发生。

2. 空间复杂度

Map的空间复杂度取决于存储的键值对数量以及哈希桶的数量。在Go中,Map的空间复杂度通常可以认为是O(n),其中n是键值对的数量。随着键值对数量的增加,Map可能会进行扩容,增加哈希桶的数量,以保持操作的效率。

3. 性能优化技巧

  • 合理估计Map大小:如果你预先知道将要存储的键值对的大致数量,可以在创建Map时指定一个初始容量,这有助于减少自动扩容的次数,从而提高性能。
代码语言:go
AI代码解释
复制
myMap := make(map[string]int, initialCapacity)
  • 选择合适的键类型:使用较小的、可比较性能较高的键类型,可以减少哈希计算的开销。例如,int类型通常比string类型作为键更高效。
  • 避免复杂的键结构:如果键是一个复杂的结构体,那么比较和哈希计算的开销会更大。如果可能,尝试将复杂的键简化,或者使用能够唯一表示键的简单类型。
  • 使用并发安全的Map:如果需要在多个goroutine中并发访问Map,使用sync.Mapsync.Map提供了一些优化,不需要开发者自己实现同步,可以在并发环境中提供更好的性能。
  • 避免频繁的内存分配:在Map的使用过程中,尽量避免频繁地增加和删除键值对,因为这可能导致频繁的内存分配和垃圾回收。
  • 监控性能指标:在性能关键的应用中,监控Map的大小和性能指标,及时调整策略,可以帮助发现潜在的性能问题。

六、map的扩容策略

在Go语言中,Map的扩容是一个自动的过程,旨在维护Map的性能,特别是插入和查找操作的时间复杂度。

1. 扩容的出发条件

map在写入哈希键值对时runtime.mapassign, 会判断是否需要扩容:

代码语言:go
AI代码解释
复制
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)
}

可以看出扩容的两个条件:

  • 装载因子超过阈值6.5: overLoadFactor(h.count+1, h.B) , 装载因子:=元素数量÷桶数量
  • 使用了太多溢出桶: tooManyOverflowBuckets(h.noverflow, h.B))

h.growing() 用于避免并发扩容,导致混乱。

2. 扩容过程

当Map需要扩容时,Go运行时会进行以下步骤:

  1. 新桶数组:分配一个新的、更大的桶数组。新数组的大小通常是原来大小的两倍,这有助于分散键值对,减少冲突。
  2. 重新哈希:遍历旧的桶数组中的所有键值对,并使用哈希函数重新计算每个键的位置,将它们插入到新的桶数组中。
  3. 逐步迁移:为了避免在扩容时暂停整个程序,Go的Map实现可能会选择逐步迁移键值对。这意味着在扩容期间,旧的桶数组和新的桶数组会同时存在,新插入的键值对会直接放入新的桶中,而对旧桶的访问会触发迁移操作。
  4. 更新内部状态:一旦所有键值对都迁移到新的桶数组中,Map的内部状态会更新,以反映新的结构。
代码语言:go
AI代码解释
复制
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

	...
}

键值对的迁移并摆在hashGrow中进行,而是在runtime.mapassign中逐步迁移的。

代码语言:go
AI代码解释
复制
func mapassign(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {
...
	if h.growing() {
		growWork(t, h, bucket)
	}
...
}

runtime.growWork中执行迁移的具体工作

代码语言:go
AI代码解释
复制
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)
	}
}

3. 扩容对性能的影响

扩容是一个昂贵的操作,它涉及到内存分配和键值对的重新哈希。在扩容期间,Map的性能可能会暂时下降,特别是在插入新元素时。然而,扩容完成后,由于减少了哈希冲突,Map的性能通常会得到提升。

七、map与并发安全

1. Map的并发问题

在Go语言中,标准的Map不是并发安全的。这意味着如果多个goroutine同时对同一个Map进行读写操作,可能会导致竞态条件(race condition),从而引发不可预知的行为,如Map的内部结构损坏、程序崩溃等。

并发问题主要发生在以下情况:

  1. 同时写入:当两个或更多的goroutine尝试同时写入Map时,可能会导致数据冲突和不一致。
  2. 读写同时进行:当一个goroutine在读取Map,而另一个goroutine在写入Map时,读取操作可能会遇到不完整或不一致的数据。

为了避免这些问题,需要采取措施来确保对Map的并发访问是安全的。

2. 使用sync.Map

sync.Map是Go语言在sync包中提供的一个并发安全的Map类型。它使用了一些优化技术来减少锁的粒度,提高并发性能。

sync.Map提供了以下几个主要的方法:

  • Store(key, value):存储键值对。
  • Load(key):根据键获取值。
  • LoadOrStore(key, value):获取或存储键值对。
  • Delete(key):删除键值对。
  • Range(f func(key, value interface{}) bool):遍历Map。

sync.Map适用于以下使用场景:

  1. 键值对的写入操作比读取操作少得多sync.Map在这种场景下性能较好,因为它减少了锁的竞争。
  2. Map中的键值对数量很大,且键的集合相对稳定:在这种情况下,sync.Map可以有效地减少锁的粒度,提高并发访问的性能。
  3. 多个goroutine读取同一个键值对sync.Map可以在不加锁的情况下安全地返回同一个键的值。

关于sync.Map的更多介绍,参考《深入理解Go语言sync.Map》

八、最佳实践与常见问题

1. 何时使用Map

Map适用于以下场景:

  1. 快速查找:当需要快速根据键查找值时,Map提供了平均时间复杂度为O(1)的查找性能。
  2. 去重:当需要存储唯一键时,Map的键不允许重复,自然可以实现去重功能。
  3. 关联数据:当数据以键值对的形式存在,并且需要经常更新或查询时,Map是一个很好的选择。
  4. 动态集合:当需要动态地添加或删除键值对时,Map提供了灵活的操作。

2. Map的性能陷阱

  1. 并发使用:在多个goroutine中使用Map时,如果没有正确的同步机制,会导致竞态条件。
  2. 大量删除操作:频繁的插入和删除操作可能会导致Map的性能下降,因为这可能会引起频繁的内存分配和垃圾回收。
  3. 迭代效率:虽然Map的迭代操作简单,但如果Map很大,迭代可能会比预期慢,尤其是在Map扩容时。
  4. 内存使用:Map的内存使用可能比预期高,特别是当存储大量小对象时,因为每个键值对都有一定的存储开销。

3. 调试与优化Map性能的技巧

  1. 预分配大小:如果能预估Map的大小,使用make(map[KeyType]ValueType, size)预分配足够的空间可以减少扩容带来的性能损耗。
  2. 避免大键:使用较小的键类型,如intint64,可以减少哈希计算的开销。
  3. 使用结构体指针:如果值是大型结构体,使用指向这些结构体的指针作为值,可以减少内存使用和复制开销。
  4. 并发控制:对于并发访问,使用sync.Map或自行实现的并发安全Map,确保使用适当的锁策略。
  5. 性能分析:使用Go的pprof工具进行性能分析,找出热点函数和性能瓶颈。
  6. 监控内存使用:使用内存分析工具监控Map的内存使用情况,及时发现潜在的内存问题。
  7. 适时清理:对于不再需要的键值对,及时删除可以帮助减少内存压力。

通过遵循这些最佳实践和技巧,可以有效地使用Map,并优化其性能。在实际开发中,应该根据具体的应用场景和需求来选择和调整策略。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
深入理解Go语言中的map:结构、性能与最佳实践
哈希表和数组是最常见的数据结构,几乎所有的语言都会有数组和哈希表两种容器类型 。哈希表表示的是键值对之间映射关系,在Go语言中,通过map来表示哈希表。本文将深入浅出介绍map的概念、使用方式、底层结构、性能、最佳实现等话题,帮助开发更好的理解和使用map。
windealli
2024/03/22
3K0
深入理解Go语言中的map:结构、性能与最佳实践
由浅到深,入门Go语言Map实现原理
本篇文章主要以Map的读来展开分析,因为读弄明白了,其他的写、更新、删除等基本操作基本都可以猜出来了,不是么。
twelvecoder
2021/12/24
4240
由浅到深,入门Go语言Map实现原理
由浅到深,入门Go语言Map实现原理
把自己学习知识进行一个总结。同时把一些可能困难、复杂难以理解的东西自我消化吸收后,简单化输出,降低他人的学习成本,提高他人的学习效率,主要为如下两点:
用户1093396
2020/12/23
9450
由浅到深,入门Go语言Map实现原理
深入理解 Go map:初始化和访问元素
从本文开始咱们一起探索 Go map 里面的奥妙吧,看看它的内在是怎么构成的,又分别有什么值得留意的地方?
李海彬
2019/05/08
1.4K0
深入理解 Go map:初始化和访问元素
2万字图解map
上面引用的是维基百科对map的定义,意思是说,在计算机学科中,map是一种抽象的数据结构,它由key和value组成组成键值对的集合,在集合中每个key最多出现一次。像关联数组、符号表、字典数据结构都是map的一种具体实现 map数据结构在实际的项目使用的非常频繁,很多语言都提供了mpa数据结构,像Java语言的HashMap,Go语言中的map和sync.Map数据类型。map基本操作包含添加key和value键值对,获取key对应的value, 删除key,遍历操作。
数据小冰
2022/08/15
1.1K0
2万字图解map
深入理解 Go map:赋值和扩容迁移
在 上一章节 中,数据结构小节里讲解了大量基础字段,可能你会疑惑需要 #&(!……#(!¥! 来干嘛?接下来我们一起简单了解一下基础概念。再开始研讨今天文章的重点内容。我相信这样你能更好的读懂这篇文章
李海彬
2019/05/08
2.6K0
深入理解 Go map:赋值和扩容迁移
Golang map 三板斧第三式:实现原理
Go map 底层实现方式是 Hash 表(C++ map 基于红黑树实现,而 C++ 11 新增的 unordered_map 则与 Go map 类似,都是基于 Hash 表实现)。Go map 的数据被置入一个由桶组成的有序数组中,每个桶最多可以存放 8 个 key/value 对。key 的 Hash 值低位用于在该数组中定位到桶,而高 8 位则用于在桶中区分 key/value 对。
恋喵大鲤鱼
2020/11/12
7.4K0
Golang map 三板斧第三式:实现原理
真希望你也明白runtime.Map和sync.Map
One of the most useful data structures in computer science is the hash table. Many hash table implementations exist with varying properties, but in general they offer fast lookups, adds, and deletes. Go provides a built-in map type that implements a hash table.
面向加薪学习
2022/12/13
4050
真希望你也明白runtime.Map和sync.Map
go哈希
哈希查找表一般会存在“碰撞”的问题,就是说不同的 key 被哈希到了同一个 bucket。一般有两种应对方法:链表法和开放地址法。链表法将一个 bucket 实现成一个链表,落在同一个 bucket 中的 key 都会插入这个链表。开放地址法则是碰撞发生后,通过一定的规律,在数组的后面挑选“空位”,用来放置新的 key。
Michel_Rolle
2023/11/06
3K1
Go Map(集合)和sync.Map
Go语言中的Map是一种无序的键值对集合。Map可以通过key在O(1)的时间复杂度内进行查询、更改、删除操作,key到value间的映射由哈希函数实现。Go的Map相当于C++的Map,Java的HashMap,Python的Dict。
Steve Wang
2020/12/23
1.9K0
为什么说Go的Map是无序的?
Go源码版本1.13.8 系列导读 本系列基于64位平台、1Page=8KB 前言 是的,我也是一个PHPer,对于我们PHPer转Gopher的银?,一定有个困扰:Go语言里每次遍历Map输出
用户1093396
2021/03/11
1.2K0
为什么说Go的Map是无序的?
GO 中 map 的实现原理
要是对 GO 的slice 原理还有点兴趣的话,欢迎查看文章 GO 中 slice 的实现原理
阿兵云原生
2023/02/16
4830
如何设计并实现一个线程安全的 Map ?(上篇)
Map 是一种很常见的数据结构,用于存储一些无序的键值对。在主流的编程语言中,默认就自带它的实现。C、C++ 中的 STL 就实现了 Map,JavaScript 中也有 Map,Java 中有 HashMap,Swift 和 Python 中有 Dictionary,Go 中有 Map,Objective-C 中有 NSDictionary、NSMutableDictionary。
一缕殇流化隐半边冰霜
2018/08/30
2.1K0
如何设计并实现一个线程安全的 Map ?(上篇)
你不知道的Golang map
在开发过程中,map是必不可少的数据结构,在Golang中,使用map或多或少会遇到与其他语言不一样的体验,比如访问不存在的元素会返回其类型的空值、map的大小究竟是多少,为什么会报"cannot take the address of"错误,遍历map的随机性等等。 本文希望通过研究map的底层实现,以解答这些疑惑。 基于Golang 1.8.3
sunsky
2020/08/20
1.2K0
你不知道的Golang map
go map 原理与并发安全map
go map 整体和 java hashmap 差不多, 只是源码阅读的位置不太方便
leobhao
2024/11/29
1370
go map 原理与并发安全map
深度解密Go语言之map
这篇文章主要讲 map 的赋值、删除、查询、扩容的具体执行过程,仍然是从底层的角度展开。结合源码,看完本文一定会彻底明白 map 底层原理。
梦醒人间
2019/05/23
1.2K0
Go语言中的map数据结构是如何实现的?
在 Go 中,map 是一种用于存储键值对的数据结构,它提供了一种快速查找和访问数据的方式。
闻说社
2025/01/13
1510
Go语言中的map数据结构是如何实现的?
golang源码分析:map
map 是由 key-value 对组成的;key 只会出现一次.主要的数据结构有两种:哈希查找表(Hash table)、搜索树(Search tree)。哈希查找表一般会存在“碰撞”的问题,就是说不同的 key 被哈希到了同一个 bucket。一般有两种应对方法:链表法和开放地址法。搜索树法一般采用自平衡搜索树,包括:AVL 树,红黑树。
golangLeetcode
2022/08/02
4810
同样作为非并发安全的数据结构,slice和map在有并发安全问题时,为什么表现相差那么大
Because some cases are easy and cheap to detect for maps, but there
fliter
2023/06/18
2440
同样作为非并发安全的数据结构,slice和map在有并发安全问题时,为什么表现相差那么大
2020-11-25:go中,map的底层数据结构是什么?
福哥答案2020-11-25: 简单回答:hmap映射头、bmap桶、mapextra溢出额外信息 中级回答: // 映射头 type hmap struct { // Note: the format of the hmap is also encoded in cmd/compile/internal/gc/reflect.go. // Make sure this stays in sync with the compiler's definition. count int // map
福大大架构师每日一题
2020/11/25
7030
相关推荐
深入理解Go语言中的map:结构、性能与最佳实践
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档