go 语言中 map 的相关知识

这篇具有很好参考价值的文章主要介绍了go 语言中 map 的相关知识。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Go map key类型分析

1 map的key类型

map中的key可以是任何的类型,只要它的值能比较是否相等,Go的语言规范已精确定义,Key的类型可以是:

  • 布尔值
  • 数字
  • 字符串
  • 指针
  • 通道
  • 接口类型
  • 结构体
  • 只包含上述类型的数组

但不能是:

  • slice
  • map
  • function

Key类型只要能支持和!=操作符,即可以做为Key,当两个值时,则认为是同一个Key。

2 比较相等

我们先看一下样例代码:

**package** main

**import** "fmt"

**type** _key **struct** {
}

**type** point **struct** {
	x **int**
	y **int**
}

**type** pair **struct** {
	x **int**
	y **int**
}

**type** Sumer **interface** {
	Sum() **int**
}

**type** Suber **interface** {
	Sub() **int**
}

**func** (p *pair) Sum() **int** {
	**return** p.x + p.y
}

**func** (p *point) Sum() **int** {
	**return** p.x + p.y
}

**func** (p pair) Sub() **int** {
	**return** p.x - p.y
}

**func** (p point) Sub() **int** {
	**return** p.x - p.y
}

**func** main() {
	fmt.Println("_key{} == _key{}: ", _key{} == _key{}) // output: true

	fmt.Println("point{} == point{}: ", point{x: 1, y: 2} == point{x: 1, y: 2})     // output: true
	fmt.Println("&point{} == &point{}: ", &point{x: 1, y: 2} == &point{x: 1, y: 2}) // output: false

	fmt.Println("[2]point{} == [2]point{}: ", 
	  [2]point{point{x: 1, y: 2}, point{x: 2, y: 3}} == [2]point{point{x: 1, y: 2}, point{x: 2, y: 3}}) //output: true

	**var** a Sumer = &pair{x: 1, y: 2}
	**var** a1 Sumer = &pair{x: 1, y: 2}
	**var** b Sumer = &point{x: 1, y: 2}
	fmt.Println("Sumer.byptr == Sumer.byptr: ", a == b)        // output: false
	fmt.Println("Sumer.sametype == Sumer.sametype: ", a == a1) // output: false

	**var** c Suber = pair{x: 1, y: 2}
	**var** d Suber = point{x: 1, y: 2}
	**var** d1 point = point{x: 1, y: 2}
	fmt.Println("Suber.byvalue == Suber.byvalue: ", c == d)  // output: false
	fmt.Println("Suber.byvalue == point.byvalue: ", d == d1) // output: true

	ci1 := make(**chan** **int**, 1)
	ci2 := ci1
	ci3 := make(**chan** **int**, 1)
	fmt.Println("chan int == chan int: ", ci1 == ci2) // output: true
	fmt.Println("chan int == chan int: ", ci1 == ci3) // output: false
}

上面的例子让我们较直观地了解结构体,数组,指针,chan在什么场景下是相等。我们再来看Go语言规范中是怎么说的:

  • Pointer values are comparable. Two pointer values are equal if they point to the same variable or if both have value nil. Pointers to distinct zero-size variables may or may not be equal.当指针指向同一变量,或同为nil时指针相等,但指针指向不同的零值时可能不相等。
  • Channel values are comparable. Two channel values are equal if they were created by the same call to make or if both have value nil.Channel当指向同一个make创建的或同为nil时才相等
  • Interface values are comparable. Two interface values are equal if they have identical dynamic types and equal dynamic values or if both have value nil.从上面的例子我们可以看出,当接口有相同的动态类型并且有相同的动态值,或者值为都为nil时相等。要注意的是:参考理解Go Interface
  • A value x of non-interface type X and a value t of interface type T are comparable when values of type X are comparable and X implements T. They are equal if t’s dynamic type is identical to X and t’s dynamic value is equal to x.如果一个是非接口类型X的变量x,也实现了接口T,与另一个接口T的变量t,只t的动态类型也是类型X,并且他们的动态值相同,则他们相等。
  • Struct values are comparable if all their fields are comparable. Two struct values are equal if their corresponding non-blank fields are equal.结构体当所有字段的值相同,并且没有 相应的非空白字段时,则他们相等,
  • Array values are comparable if values of the array element type are comparable. Two array values are equal if their corresponding elements are equal.两个数组只要他们包括的元素,每个元素的值相同,则他们相等。

注意:Go语言里是无法重载操作符的,struct是递归操作每个成员变量,struct也可以称为map的key,但如果struct的成员变量里有不能进行==操作的,例如slice,那么就不能作为map的key。

3 类型判断

判断两个变量是否相等,首先是要判断变量的动态类型是否相同,在runtime中,_type结构是描述最为基础的类型(runtime/type.go),而map, slice, array等内置的复杂类型也都有对应的类型描述(如maptype,slicetype,arraytype)。

type _type struct {
	size       uintptr
	ptrdata    uintptr 
	hash       uint32
	tflag      tflag
	align      uint8
	fieldalign uint8
	kind       uint8
	alg        *typeAlg
	gcdata    *byte
	str       nameOff
	ptrToThis typeOff
}
...
type chantype struct {
	typ  _type
	elem *_type
	dir  uintptr
}
...
type slicetype struct {
	typ  _type
	elem *_type
}
...

其中对于类型的值是否相等,需要使用到成员alg *typeAlg(runtime/alg.go),它则持有此类型值的hash与equal的算法,它也是一个结构体:

type typeAlg struct {
	// function for hashing objects of this type
	// (ptr to object, seed) -> hash
	hash func(unsafe.Pointer, uintptr) uintptr
	// function for comparing objects of this type
	// (ptr to object A, ptr to object B) -> ==?
	equal func(unsafe.Pointer, unsafe.Pointer) bool
}

runtime/alg.go中提供了各种基础的hash func与 equal func,例如:

func strhash(a unsafe.Pointer, h uintptr) uintptr {
	x := (*stringStruct)(a)
	return memhash(x.str, h, uintptr(x.len))
}

func strequal(p, q unsafe.Pointer) bool {
	return *(*string)(p) == *(*string)(q)
}

有了这些基础的hash func与 equal func,再复杂的结构体也可以按字段递归计算hash与相等比较了。那我们再来看一下,当访问map[key]时,其实现对应在runtime/hashmap.go中的mapaccess1函数:

func mapaccess1(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {
	...
	alg := t.key.alg
	hash := alg.hash(key, uintptr(h.hash0)) // 1
	m := uintptr(1)<<h.B - 1
	b := (*bmap)(add(h.buckets, (hash&m)*uintptr(t.bucketsize))) // 2
	...
	top := uint8(hash >> (sys.PtrSize*8 - 8))
	if top < minTopHash {
		top += minTopHash
	}
	for {
		for i := uintptr(0); i < bucketCnt; i++ {
			...
			k := add(unsafe.Pointer(b), dataOffset+i*uintptr(t.keysize))
			if alg.equal(key, k) { // 3
				v := add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+i*uintptr(t.valuesize))
				...
				return v
			}
		}
	...
	}
}

mapaccess1的代码还是比较多的,简化逻辑如下(参考注释上序列):

  1. 调用key类型的hash方法,计算出key的hash值
  2. 根据hash值找到对应的桶bucket
  3. 在桶中找到key值相等的map的value。判断相等需调用key类型的equal方法

到现在我们也就有了初步了解,map中的key访问时同时需要使用该类型的hash func与 equal func,只要key值相等,当结构体即使不是同一对象,也可从map中获取相同的值,例如:

m := make(map[interface{}]interface{})
m[_key{}] = "value"
if v, ok := m[_key{}];ok {
	fmt.Println("%v", v) // output: value
}

判断两个 map 是否相等

比较两个map实例需要使用reflect包的DeepEqual()方法。如果相比较的两个map满足以下条件,方法返回true:

Map values are deeply equal when all of the following are true: they are both nil or both non-nil, they have the same length, and either they are the same map object or their corresponding keys (matched using Go equality) map to deeply equal values.

1.两个map都为nil或者都不为nil,并且长度要相等 they are both nil or both non-nil, they have the same length 2.相同的map对象或者所有key要对应相同 either they are the same map object or their corresponding keys 3.map对应的value也要深度相等 map to deeply equal values

Go 为什么不在语言层面支持 map 并发?

为什么原生不支持

凭什么 Go 官方还不支持,难不成太复杂了,性能太差了,到底是为什么?
官方答复原因如下(via @go faq):

  • 典型使用场景:map 的典型使用场景是不需要从多个 goroutine 中进行安全访问。
  • 非典型场景(需要原子操作):map 可能是一些更大的数据结构或已经同步的计算的一部分。
  • 性能场景考虑:若是只是为少数程序增加安全性,导致 map 所有的操作都要处理 mutex,将会降低大多数程序的性能。

核心来讲就是:Go 团队在经过了长时间的讨论后,认为原生 map 更应适配典型使用场景。
如果为了小部分情况,将会导致大部分程序付出性能代价,决定了不支持原生的并发 map 读写。且在 Go1.6 起,增加了检测机制,并发的话会导致异常。

为什么要崩溃

前面有提到一点,在 Go1.6 起会进行原生 map 的并发检测,这是一些人的 “噩梦”。
在此有人吐槽到:“明明给我抛个错就好了,凭什么要让我的 Go 进程直接崩溃掉,分分钟给我背个 P0”。

场景枚举

这里我们假设一下,如果并发读写 map 是以下两种场景:

  1. 产生 panic:程序 panic -> 默认走进 recover -> 没有对并发 map 进行处理 -> map 存在脏数据 -> 程序使用脏数据 -> 产生**未知((影响。
  2. 产生 crash:程序 crash -> 直接崩溃 -> 保全数据(数据正常)-> 产生**明确((风险。

你会选择哪一种方案呢?Go 官方在两者的风险衡量中选择了第二种。
无论是编程,还是人生。如何在随机性中掌握确定性的部分,也是一门极大的哲学了。

let it crash

Go 官方团队选择的方式是业内经典的 “let it crash” 行为,很多编程语言中,都会将其奉行为设计哲学。
let it crash 是指工程师不必过分担心未知的错误,而去进行面面俱到的防御性编码。
这块理念最经典的就是 erlang 了。文章来源地址https://www.toymoban.com/news/detail-790957.html

到了这里,关于go 语言中 map 的相关知识的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Golang】go编程语言适合哪些项目开发?

    前言 在当今数字化时代,软件开发已成为各行各业的核心需求之一。 而选择适合的编程语言对于项目的成功开发至关重要。 本文将重点探讨Go编程语言适合哪些项目开发,以帮助读者在选择合适的编程语言时做出明智的决策。 Go 编程语言适合哪些项目开发? Go是由Google开发

    2024年02月04日
    浏览(55)
  • 【Golang】VsCode下开发Go语言的环境配置(超详细图文详解)

    📓推荐网站(不断完善中):个人博客 📌个人主页:个人主页 👉相关专栏:CSDN专栏、个人专栏 🏝立志赚钱,干活想躺,瞎分享的摸鱼工程师一枚 ​ 话说在前,Go语言的编码方式是 UTF-8 ,理论上你直接使用文本进行编辑也是可以的,当然为了提升我们的开发效率我们还是需

    2024年02月07日
    浏览(61)
  • 【Go】Go 语言教程--Go 语言Map(集合)(十六)

    往期回顾: Go 语言教程–介绍(一) Go 语言教程–语言结构(二) Go 语言教程–语言结构(三) Go 语言教程–数据类型(四) Go 语言教程–语言变量(五) Go 语言教程–GO语言常量(六) Go 语言教程–GO语言运算符(七) Go 语言教程–GO条件和循环语句(八) Go 语言教程

    2024年02月16日
    浏览(32)
  • go语言(八)---- map

    map的声明方式有以下三种。 map的使用方式 map的增删改查 map的传参

    2024年01月20日
    浏览(28)
  • Go 语言Map(集合)

    Map 是一种无序的键值对的集合。Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。 Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是无序的,我们无法决定它的返回顺序,这是因为 Map 是使用 hash 表来实现的。 定义 Map 可以

    2024年02月05日
    浏览(27)
  • Golang:Go语言结构

    在我们开始学习 Go 编程语言的基础构建模块前,让我们先来了解 Go 语言最简单程序的结构。 Go 语言的基础组成有以下几个部分: 包声明 引入包 函数 变量 语句 表达式 注释 接下来让我们来看下简单的代码,该代码输出了\\\"Hello World!\\\": 让我们来看下以上程序的各个部分: 第一

    2024年02月10日
    浏览(40)
  • golang实现webgis后端开发

    目录 前言 二、实现步骤 1.postgis数据库和model的绑定 2.将pg库中的要素转换为geojson (1)几何定义 (2)将wkb解析为几何类型 (3)定义geojson类型 (4)数据转换 (5)数据返回  2.前端传入的geojson储存到数据库 3、其他功能实现 总结         停更了接近一个月都在研究一门新语言gola

    2024年02月08日
    浏览(38)
  • Go语言基础之map

    Go语言中提供的映射关系容器为map,其内部使用散列表(hash)实现。 map是一种无序的基于key-value的数据结构,Go语言中的map是引用类型,必须初始化才能使用。 map定义 Go语言中 map的定义语法如下: 其中, KeyType:表示键的类型。 ValueType:表示键对应的值的类型。 map类型的变量

    2024年02月11日
    浏览(35)
  • Go 语言 map 如何顺序读取?

    Go 语言中的 map 是一种非常强大的数据结构,它允许我们快速地存储和检索键值对。 然而,当我们遍历 map 时,会有一个有趣的现象,那就是输出的键值对顺序是不确定的。 先看一段代码示例: 当我们多执行几次这段代码时,就会发现,输出的顺序是不同的。 首先,Go 语言

    2024年02月07日
    浏览(33)
  • Go语言入门5(map 哈希表)

    ​哈希表是一种巧妙并且实用的数据结构。它是一个无序的key/value对的集合,其中所有的key 都是不同的,然后通过给定的key可以在常数时间复杂度内检索、更新或删除对应的value。 ​在Go语言中,一个map就是一个哈希表的引用,map类型可以写为map[K]V,其中K和V分别 对应key和

    2023年04月12日
    浏览(28)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包