golang变量(二)——map和slice详解衍生类型,interface{} , map, [] , struct等
【go语言map定义 go语言中的map】 map类似于java的hashmap,python的dict,php的hash array 。
常规的for循环 , 可以用for k,v :=range m {}. 但在下面清空有一个坑注意:
著名的map[string]*struct 副本问题
结果:
Go 中不存在引用传递,所有的参数传递都是值传递 , 而map是等同于指针类型的,所以在把map变量传递给函数时 , 函数对map的修改,也会实质改变map的值 。
slice类似于其他语言的数组(list,array),slice初始化和map一样,这里不在重复
除了Pointer数组外,len表示使用长度 , cap是总容量,make([]int, len, cap)可以预申请 比较大的容量,这样可以减少容量拓展的消耗,前提是要用到 。
cap是计算切片容量,len是计算变量长度的,两者不一样 。具体例子如下:
结果:
分析:cap是计算当前slice已分配的容量大?。捎玫氖窃し峙涞幕锇樗惴ǎǖ比萘柯? ,拓展分配一倍的容量) 。
append是slice非常常用的函数,用于添加数据到slice中 , 但如果使用不好,会有下面的问题:
预期是[1 2 3 4 5 6 7 8 9 10], [1 2 3 4 5 6 7 8 9 10 11 12] , 但实际结果是:
注意slice是值传递,修改一下:
输出如下:
== 只能用于判断常规数据类型,无法使用用于slice和map判断 , 用于判断map和slice可以使用reflect.DeepEqual,这个函数用了递归来判断每层的k,v是否一致 。
当然还有其他方式,比如转换成json,但小心有一些异常的bug,比如html编码 , 具体这个json问题,待后面在分析 。
MAP的定义是什么map对像的元素是键一值对,也即每个元素包含两个部分:键以及由键关联的值 , map的value_type就反映了这个事实 。
map
::key_type
在map容器中,用做索引的键的类型 。map
::mapped_type
在map容器中,键所关联的值的类型 。map
::value_type一个pair类型,它的first元素具有const
map
::value_type类型 , 而second元素侧为map
::value_type类型 。
golang hashmap的使用及实现 由于go语言是一个强类型的语言,因此hashmap也是有类型的,具体体现在key和value都必须指定类型,比如声明一个key为string,value也是string的map,
需要这样做
大部分类型都能做key,某些类型是不能的 , 共同的特点是: 不能使用== 来比较 , 包括: slice, map, function
在迭代的过程中是可以对map进行删除和更新操作的,规则如下:
golang的map是hash结构的,意味着平均访问时间是O(1)的 。同传统的hashmap一样,由一个个bucket组成:
那我们怎么访问到对应的bucket呢,我们需要得到对应key的hash值
各个参数的意思:
目前采用的是这一行:
| 6.50 | 20.90 | 10.79 | 4.25 | 6.50 |
goland map底层原理map 是Go语言中基础go语言map定义的数据结构,在日常go语言map定义的使用中经常被用到 。但是它底层是如何实现的呢?
总体来说golang的map是hashmap,是使用数组 链表的形式实现的 , 使用拉链法消除hash冲突 。
golang的map由两种重要的结构,hmap和bmap(下文中都有解释),主要就是hmap中包含一个指向bmap数组的指针,key经过hash函数之后得到一个数,这个数低位用于选择bmap(当作bmap数组指针的下表) , 高位用于放在bmap的[8]uint8数组中 , 用于快速试错 。然后一个bmap可以指向下一个bmap(拉链) 。
Golang中map的底层实现是一个散列表,因此实现map的过程实际上就是实现散表的过程 。在这个散列表中,主要出现的结构体有两个,一个叫 hmap (a header for a go map),一个叫 bmap (a bucket for a Go map,通常叫其bucket) 。这两种结构的样子分别如下所示:
hmap :
图中有很多字段,但是便于理解map的架构,你只需要关心的只有一个,就是标红的字段: buckets数组。Golang的map中用于存储的结构是bucket数组 。而bucket(即bmap)的结构是怎样的呢?
bucket :
相比于hmap,bucket的结构显得简单一些 , 标红的字段依然是“核心” , 我们使用的map中的key和value就存储在这里 。“高位哈希值”数组记录的是当前bucket中key相关的“索引”,稍后会详细叙述 。还有一个字段是一个指向扩容后的bucket的指针 , 使得bucket会形成一个链表结构 。例如下图:
由此看出hmap和bucket的关系是这样的:
而bucket又是一个链表,所以,整体的结构应该是这样的:
哈希表的特点是会有一个哈希函数,对你传来的key进行哈希运算,得到唯一的值,一般情况下都是一个数值 。Golang的map中也有这么一个哈希函数,也会算出唯一的值 , 对于这个值的使用,Golang也是很有意思 。
Golang把求得的值按照用途一分为二:高位和低位 。
如图所示,蓝色为高位,红色为低位 。然后低位用于寻找当前key属于hmap中的哪个bucket , 而高位用于寻找bucket中的哪个key 。上文中提到:bucket中有个属性字段是“高位哈希值”数组 , 这里存的就是蓝色的高位值,用来声明当前bucket中有哪些“key”,便于搜索查找 。需要特别指出的一点是:我们map中的key/value值都是存到同一个数组中的 。数组中的顺序是这样的:
并不是key0/value0/key1/value1的形式,这样做的好处是:在key和value的长度不同的时候,可 以消除padding(内存对齐)带来的空间浪费。
现在,我们可以得到Go语言map的整个的结构图了:(hash结果的低位用于选择把KV放在bmap数组中的哪一个bmap中,高位用于key的快速预览,用于快速试错)
map的扩容
当以上的哈希表增长的时候,Go语言会将bucket数组的数量扩充一倍,产生一个新的bucket数组,并将旧数组的数据迁移至新数组 。
加载因子
判断扩充的条件,就是哈希表中的加载因子(即loadFactor) 。
加载因子是一个阈值,一般表示为:散列包含的元素数 除以 位置总数 。是一种“产生冲突机会”和“空间使用”的平衡与折中:加载因子越小,说明空间空置率高,空间使用率小,但是加载因子越大,说明空间利用率上去了,但是“产生冲突机会”高了 。
每种哈希表的都会有一个加载因子,数值超过加载因子就会为哈希表扩容 。
Golang的map的加载因子的公式是:map长度 / 2^B(这是代表bmap数组的长度,B是取的低位的位数)阈值是6.5 。其中B可以理解为已扩容的次数 。
当Go的map长度增长到大于加载因子所需的map长度时,Go语言就会将产生一个新的bucket数组,然后把旧的bucket数组移到一个属性字段oldbucket中 。注意:并不是立刻把旧的数组中的元素转义到新的bucket当中,而是,只有当访问到具体的某个bucket的时候,会把bucket中的数据转移到新的bucket中 。
如下图所示:当扩容的时候,Go的map结构体中,会保存旧的数据,和新生成的数组
上面部分代表旧的有数据的bucket,下面部分代表新生成的新的bucket 。蓝色代表存有数据的bucket,橘黄色代表空的bucket 。
扩容时map并不会立即把新数据做迁移,而是当访问原来旧bucket的数据的时候,才把旧数据做迁移,如下图:
注意:这里并不会直接删除旧的bucket,而是把原来的引用去掉,利用GC清除内存 。
map中数据的删除
如果理解了map的整体结构,那么查找、更新、删除的基本步骤应该都很清楚了 。这里不再赘述 。
值得注意的是,找到了map中的数据之后 , 针对key和value分别做如下操作:
1
2
3
4
1、如果``key``是一个指针类型的,则直接将其置为空 , 等待GC清除;
2、如果是值类型的,则清除相关内存 。
3、同理,对``value``做相同的操作 。
4、最后把key对应的高位值对应的数组index置为空 。
Go语言——sync.Map详解 sync.Map是1.9才推荐的并发安全的map,除了互斥量以外,还运用了原子操作,所以在这之前,有必要了解下 Go语言——原子操作
go1.10\src\sync\map.go
entry分为三种情况:
从read中读取key,如果key存在就tryStore 。
注意这里开始需要加锁,因为需要操作dirty 。
条目在read中,首先取消标记,然后将条目保存到dirty里 。(因为标记的数据不在dirty里)
最后原子保存value到条目里面,这里注意read和dirty都有条目 。
总结一下Store:
这里可以看到dirty保存了数据的修改,除非可以直接原子更新read,继续保持read clean 。
有了之前的经验 , 可以猜测下load流程:
与猜测的 区别 :
由于数据保存两份,所以删除考虑:
先看第二种情况 。加锁直接删除dirty数据 。思考下貌似没什么问题,本身就是脏数据 。
第一种和第三种情况唯一的区别就是条目是否被标记 。标记代表删除,所以直接返回 。否则CAS操作置为nil 。这里总感觉少点什么,因为条目其实还是存在的,虽然指针nil 。
看了一圈貌似没找到标记的逻辑 , 因为删除只是将他变成nil 。
之前以为这个逻辑就是简单的将为标记的条目拷贝给dirty , 现在看来大有文章 。
p == nil,说明条目已经被delete了,CAS将他置为标记删除 。然后这个条目就不会保存在dirty里面 。
这里其实就跟miss逻辑串起来了,因为miss达到阈值之后,dirty会全量变成read,也就是说标记删除在这一步最终删除 。这个还是很巧妙的 。
真正的删除逻辑:
很绕 。。。。
Go语言和其他语言的不同之基本语法Go语言作为出现比较晚的一门编程语言,在其原生支持高并发、云原生等领域的优秀表现,像目前比较流行的容器编排技术Kubernetes、容器技术Docker都是用Go语言写的 , 像Java等其他面向对象的语言,虽然也能做云原生相关的开发,但是支持的程度远没有Go语言高,凭借其语言特性和简单的编程方式,弥补了其他编程语言一定程度上的不足,一度成为一个热门的编程语言 。
最近在学习Go语言,我之前使用过C#、Java等面向对象编程的语言,发现其中有很多的编程方式和其他语言有区别的地方 , 好记性不如烂笔头,总结一下,和其他语言做个对比 。这里只总结差异的地方,具体的语法不做详细的介绍 。
种一棵树最好的时间是十年前,其次是现在 。
3)变量初始化时候可以和其他语言一样直接在变量后面加等号,等号后面为要初始化的值,也可以使用变量名:=变量值的简单方式
3)变量赋值 Go语言的变量赋值和多数语言一致 , 但是Go语言提供了多重赋值的功能,比如下面这个交换i、j变量的语句:
在不支持多重赋值的语言中,交换两个变量的值需要引入一个中间变量:
4)匿名变量
在使用其他语言时,有时候要获取一个值 , 却因为该函数返回多个值而不得不定义很多没有的变量,Go语言可以借助多重返回值和匿名变量来避免这种写法,使代码看起来更优雅 。
假如GetName()函数返回3个值,分别是firstName,lastName和nickName
若指向获得nickName,则函数调用可以这样写
这种写法可以让代码更清晰,从而大幅降低沟通的复杂度和维护的难度 。
1)基本常量
常量使用关键字const 定义 , 可以限定常量类型,但不是必须的,如果没有定义常量的类型,是无类型常量
2)预定义常量
Go语言预定义了这些常量 true、false和iota
iota比较特殊,可以被任务是一个可被编译器修改的常量,在每个const关键字出现时被重置为0,然后在下一个const出现之前每出现一个iota , 其所代表的数字会自动加1.
3)枚举
1)int 和int32在Go语言中被认为是两种不同类型的类型
2)Go语言定义了两个浮点型float32和float64,其中前者等价于C语言的float类型,后者等价于C语言的double类型
3)go语言支持复数类型
复数实际上是由两个实数(在计算机中使用浮点数表示)构成,一个表示实部(real)、一个表示虚部(imag) 。也就是数学上的那个复数
复数的表示
实部与虚部
对于一个复数z=complex(x,y),就可以通过Go语言内置函数real(z)获得该复数的实部,也就是x,通过imag(z)获得该复数的虚部,也就是y
4)数组(值类型,长度在定义后无法再次修改,每次传递都将产生一个副本 。)
5)数组切片(slice)
数组切片(slice)弥补了数组的不足 , 其数据结构可以抽象为以下三个变量:
6)Map 在go语言中Map不需要引入任何库,使用很方便
Go循环语句只支持for关键字,不支持while和do-while
goto语句的语义非常简单 , 就是跳转到本函数内的某个标签
今天就介绍到这里,以后我会在总结Go语言在其他方面比如并发编程、面向对象、网络编程等方面的不同及使用方法 。希望对大家有所帮助 。
关于go语言map定义和go语言中的map的介绍到此就结束了,不知道你从中找到你需要的信息了吗 ?如果你还想了解更多这方面的信息 , 记得收藏关注本站 。
推荐阅读
- ios单机手游挂机,ios好玩的挂机类手游
- 笔记本3450cpu换什么CPU,3450 cpu
- 台湾区块链议会,区块链论坛在珠海举行
- mysql怎么防止锁表 mysql什么操作会锁表
- flutter手势管理,flutter快速上手
- mysql不卸载重新安装不了了,mysql卸载不彻底没事吧
- mysql更新语句怎么写 mysql 更新数据语法
- 上海虚拟主机托管,上海虚拟主机托管公司
- 阿里云虚拟主机搬家,阿里云主机迁移