golang 数据类型
文章目录
- 内置类型
- 值类型
- 内置常量
- 引用类型
- 内置函数
- 内置接口
- 数据类型
- 基本类型
- 整型
- 浮点型
- 布尔型
- 字符和字符串
- 字符
- 字符串
- 类型转换
- 派生类型
- 数组 array
- 多维数组
- 切片 slice
- 哈希表 map
- 指针类型 pointer
- 声明
- 取指针
- 内存
- 类型判断
- comma-ok断言
- type - switch
内置类型
定义在builtin/builtin.go
值类型
boolint int8, int16, int32(rune), int64uint uint8(byte), uint16, uint32, uint64float32, float64stringcomplex64, complex128array //固定长度的数组struct //结构体
内置常量
true //boolfalse iota //int 初始0nil //nil类型值
引用类型
slice //切片map //哈希映射chan //通道// 特殊 零值 nilfunction //函数pointer //指针 interface //接口
内置函数
append // 用来追加元素到数组、slice中,返回修改后的数组、sliceclose // 主要用来关闭channeldelete // 从map中删除key对应的valuepanic // 停止常规的goroutine (panic和recover:用来做错误处理)recover // 允许程序定义goroutine的panic动作real // 返回complex的实部 (complex、real imag:用于创建和操作复数)imag // 返回complex的虚部make // 用来分配内存,返回Type本身(只能应用于slice, map, channel)new // 用来分配内存,主要用来分配值类型,比如int、struct。返回指向Type的指针cap // capacity是容量的意思,用于返回某个类型的最大容量(只能用于切片和 map)copy // 用于复制和连接slice,返回复制的数目len // 来求长度,比如string、array、slice、map、channel ,返回长度print、println // 底层打印函数,在部署环境中建议使用 fmt 包
内置接口
//error接口
type error interface {Error() string
}
数据类型
基本类型
整型
类型 | 描述 |
---|---|
uint8 | 无符号 8 位整型 (0 到 255) |
uint16 | 无符号 16 位整型 (0 到 65535) |
uint32 | 无符号 32 位整型 (0 到 4294967295) |
uint64 | 无符号 64 位整型 (0 到 18446744073709551615) |
int8 | 有符号 8 位整型 (-128 到 127) |
int16 | 有符号 16 位整型 (-32768 到 32767) |
int32 | 有符号 32 位整型 (-2147483648 到 2147483647) |
int64 | 有符号 64 位整型 (-9223372036854775808 到 9223372036854775807) |
byte | uint8,存储字符时选用 |
rune | int32,表示Unicode码 |
uint | 32 或 64 位 |
int | 与 uint 一样大小,整型没有声明具体类型时,默认的类型 |
uintptr | 无符号整型,用于存放一个指针 |
浮点型
类型 | 描述 |
---|---|
float32 | 单精度,IEEE-754 32位浮点型数,有效bit位23个 浮点最精度小数点后15位 |
float64 | 双精度,IEEE-754 64位浮点型数,默认的类型 浮点最精度小数点后15位 |
complex64 | 32 位实数和虚数 |
complex128 | 64 位实数和虚数 |
布尔型
- 常量true false,默认fasle
- 不可与整型计算,不可以强转
package mainimport ("fmt""math""unsafe"
)
func main() {//-------------类型和所占字节----------------num := 2fmt.Printf("num is type %T, size %d\n",num,unsafe.Sizeof(num))//------------超范围,循环显示----------------var n1 int8 = 1var n3 = n1 + 127fmt.Println("n3:",n3)//-----------不同类型,不能操作-------------// var n4 = num + n1 invalid operation//---------------浮点型----------------------var f float64 = 1f1 := 1.23f2 := .23f3 := 1.fmt.Println("f:",f,"f1:",f1,"f2:",f2,"f3:",f3)//-------------科学计数法-----------------f4 := 1.23E2f5 := 123E-2fmt.Println("f4:",f4,"f5:",f5)//---------------保留位数----------------f6 := math.Pifmt.Printf("%.3f\n",f6)//--------------四舍六入五看尾---------------fmt.Printf("3.1249 => %0.2f(四舍)\n", 3.1249)fmt.Printf("3.12671 => %0.2f(六入)\n", 3.12671)fmt.Printf("3.1351 => %0.2f(五后非零就进一)\n", 3.1351)fmt.Printf("3.12501 => %0.2f(五后非零就进一)\n", 3.12501)fmt.Printf("3.1250 => %0.2f(五后为零看奇偶,五前为偶应舍去)\n", 3.1250)fmt.Printf("9.8350 => %0.2f(五后为零看奇偶,五前为奇要进一)\n", 9.8350)fmt.Printf("3.2250 => %0.2f(五后为零看奇偶,五前为偶应舍去???)\n", 3.2250)fmt.Printf("9.7350 => %0.2f(五后为零看奇偶,五前为奇要进一???)\n", 9.7350)// 布尔b := false// n3 = n1 + b invaliad operationfmt.Printf("b的类型:%T,b的值:%v,b的大小:%d",b,b,unsafe.Sizeof(b))}
字符和字符串
字符
没有字符类型。Ascii码使用byte,其他适合使用rune(UTF-8),使用’'包裹
a. 字母一个字节
b. 汉字三个字节转义字符
转义 含义 \r 回车符(返回行首) \n 换行符(直接跳到下一行的同列位置) \t 制表符 ’ 单引号 " 双引号 \ 反斜杠
字符串
- 使用""包裹或者``包裹
- 如果只包含ascii码,为byte数组
- 如果包含中文,为rune数组。rune由1个或多个byte组成
转译
// 双引号支持转译字符
str3 := "我要换行\n换好啦:)\n"
// 反引号不支持转译字符
str4 := `我想换行\n换不了:(\n`
// 反引号支持多行
str5 := `line1
line2
`
切片
fmt.Println("1234567"[:3],"1234567"[3:]) // "123" "4567"
fmt.Println("我是字符串"[:3],"我是字符串"[3:]) // "我" "是字符串"
拼接
"a"+"b"
+"c" ❌"a"+"b"+
"c" ☑️
遍历
a:= "我是字符串"
//读取byte
for i:=0;i<len(a);i=i+3{fmt.Print(a[i:i+3])
}//读取rune 字符
for _,s := range "a"{fmt.Printf("%c",s)
}
长度
a:= "我是字符串"
println(len(a)) // 15 byte
println(len([]rune(a))) // 5 rune
转换
a:= "我是字符串"
b:=[]byte(a)
c:=[]rune(a)
d:=string(b)
e:=string(c)
工具
//常见工具包
strings
bytes
unicode
官网doc
中文
类型转换
参考博客
go不支持隐式转换
转换表达式
// 表达式 T(v) 将值 v 转换为类型 T // type_name 类型 // expression 表达式 type_name(expression)
原类型 | 目标类型 | 方法 | 备注 |
---|---|---|---|
整型、浮点型 | 整型、浮点型 | T(v) |
大数转小数超范围,溢出; 浮点型转整型,小数丢失; 高精度转低精度,精度丢失 |
任何类型 | string | fmt.Sprintf() | 推荐 |
bool | strconv.FormatBool(b bool) string | 根据b的值返回"true"或"false" | |
int64 | strconv.FormatInt(i uint64, base int) string | base 必须在2到36之间,结果中会使用小写字母’a’到’z’表示大于10的数字 | |
uint64 | strconv.FormatUint(i uint64, base int) string | 同上且为无符号 | |
float64/32 | strconv.FormatFloat(f float64, fmt byte, prec, bitSize int) string |
fmt表示格式: ‘f’(-ddd.dddd) ‘b’(-ddddp±ddd,指数为二进制) ‘e’(-d.dddde±dd,十进制指数) ‘E’(-d.ddddE±dd,十进制指数) ‘g’(指数很大时用’e’格式,否则’f’格式) ‘G’(指数很大时用’E’格式,否则’f’格式)。 prec控制精度(排除指数部分): 对’f’、‘e’、‘E’,它表示小数点后的数字个数; 对’g’、‘G’,它控制总的数字个数。 如果prec 为-1,则代表使用最少数量的、但又必需的数字来表示f。 bitSize表示f的来源类型(32:float32、64:float64),会据此进行舍入。 |
|
int | strconv.Itoa() | 相当于strcov.ParseInt(s string, 10,0) (i int64, err error) | |
string | strconv.ParseBool(str string) (value bool, err error) | 返回字符串表示的bool值。它接受1、0、t、f、T、F、true、false、True、False、TRUE、FALSE;否则返回错误。 | |
float64 | strcov.ParseFloat(s string, bitSize int) (f float64, err error) |
bitSize指定了期望的接收类型, 32是float32(返回值可以不改变精确值的赋值给float32), 64是float64; 返回值err是*NumErr类型的,语法有误的,err.Error=ErrSyntax; 结果超出表示范围的,返回值f为±Inf,err.Error= ErrRange。 |
|
int64 | strcov.ParseInt(s string, base int, bitSize int) (i int64, err error) |
返回字符串表示的整数值,接受正负号。
base指定进制(2到36),如果base为0,则会从字符串前置判断,"0x"是16进制,"0"是8进制,否则是10进制; bitSize指定结果必须能无溢出赋值的整数类型,0、8、16、32、64 分别代表 int、int8、int16、int32、int64;返回的err是*NumErr类型的,如果语法有误,err.Error = ErrSyntax;如果结果超出类型范围err.Error = ErrRange。 |
|
uint64 | ParseFloat(s string, bitSize int) (f float64, err error) | 同上,无符号版本 |
派生类型
数组 array
值类型,声明时零值填。
固定长度,编译期决定。
- 类型不一致数组不一致
- 长度不一致数组不一致。
- 0~len-1下标访问 ,越界panic
占用内存连续
// cmd/compile/internal/types/type.go
// array结构
type Array struct {Elem *Type // 类型指针Bound int64 // 数组长度
}// cmd/compile/internal/types/type.go
// 创建array
func NewArray(elem *Type, bound int64) *Type {if bound < 0 {Fatalf("NewArray: invalid bound %v", bound)}t := New(TARRAY)t.Extra = &Array{Elem: elem, Bound: bound}t.SetNotInHeap(elem.NotInHeap())return t
}
//--------------声明---------------
//var variable_name [SIZE]variable_type
var arr [3]int//--------------初始化---------------
//var variable_name [SIZE]variable_type = [SIZE]variable_type{elem...}
//variable_name:= [SIZE]variable_type{elem...}
//variable_name:= [...]variable_type{elem...}
//variable_name:= [SIZE]variable_type{index:elem ...}
var arr1 [3]int = [3]int{1,2,3} //无类型推导
var arr2 = [3]int{1:4,0:5,2:6} //类型推导
arr3 := [...]int{7,8,9} //长度推导
arr4 := [5]int{1:3,3:5} //使用索引初始化
arr5 := [...] struct{ name stringage int
}{{"user1",1},{"user2",2},
}//--------------遍历---------------
n1 := len(arr1)
for i:=0;i<n1;i++{ // forfmt.Println(i,arr1[i])
}for index,n := range arr3{ // rangefmt.Println(index,n)
}
多维数组
//--------------声明---------------
//var variable_name [ROW][COL] variable_type
var arr7 [2][3]int//--------------初始化---------------
//var variable_name [ROW][COL]variable_type = [...][COL]variable_typevariable_type{{elem...}...}
//variable_name:= [ROW][COL]variable_type{{elem...}...}
var arr8 = [...][2]int{{1,2},{2,3}}
arr9 := [1][2]int{{5,6}}//--------------遍历---------------
for row:=0;row<len(arr8);row++{for col:=0;col<len(arr8[0]);col++{fmt.Println(row,col,arr8[row][col])}}for row,rows := range arr8 {for col,val:= range rows{fmt.Println(row,col,arr8[row][col],val)}
}
切片 slice
- 引用类型
- 长度可以变化,容量随长度变化
- 底层是数组
// cmd/compile/internal/types/type.go
// 编译时slice 结构体
type Slice struct {Elem *Type // element type
}// reflect/value.go
// 运行时slcie 结构体
type SliceHeader struct {Data uintptr //指向底层数组指针Len int //切片长度Cap int //切片容量
}// cmd/compile/internal/types/type.go
// 创建切片类型 返回切片指针
func NewSlice(elem *Type) *Type {if t := elem.Cache.slice; t != nil {if t.Elem() != elem {Fatalf("elem mismatch")}return t}t := New(TSLICE)t.Extra = Slice{Elem: elem}elem.Cache.slice = treturn t
}// runtime/slice.go
// 初始化切片时,如果切片内存逃逸或者太大会调用此方法分配内存
func makeslice(et *_type, len, cap int) unsafe.Pointer {mem, overflow := math.MulUintptr(et.size, uintptr(cap)) //内存空间=切片中元素大小×切片容量if overflow || mem > maxAlloc || len < 0 || len > cap {mem, overflow := math.MulUintptr(et.size, uintptr(len))if overflow || mem > maxAlloc || len < 0 {panicmakeslicelen()}panicmakeslicecap()}return mallocgc(mem, et, true)
}
//--------------声明---------------
//var slicename []type
var slice []int // slice = nil len=0 cap=0//--------------初始化---------------
// func make(Type, size ...IntegerType[,capacity]) Type
// capacity必须大雨size
// size 为切片初始长度
// capactity 为切片容量
// var slicename []type = make([]type,len[,cap])
// slicename := []type{elem...}
// slicename := slice[:end[:max]] start<=index<end max<=end<=len(slice) len=end-start cap=max-start
// slicename := slice[start:end[:max]] start<=index<end max<=end<=len(slice) len=end-start cap=max-start
// slicename := slice[start:[:max]] start<=index<len(slice) max<=end<=len(slice) len=end-start cap=max-start
slice1 := make([]int,1,2,3,4) // len=3 cap=4
slice2 := []int{6,7}
slice3 := slice1[1:] //--------------遍历---------------
for i:=0;i<len(slice3);i++{fmt.Print(slice3[i]," ")
}for _,v := range slice3{fmt.Print(v," ")
}//--------------len/cap---------------
//获取切片长度
//获取切片容量
len(slice1) //3
cap(slice1) //4//--------------追加---------------
// func append(slice []Type, elems ...Type) []Type
// slice 被添加的切片
// elems 可变元素
// []Type 新切片可能扩容
slice1=append(slice1,5) // append 追加当个元素
slice1=append(slice1,slice2...) // append 追加切片//--------------拷贝---------------
// copy(taget,soure) 长度以len(soure)为准
s1 := []int{1, 2, 3, 4, 5}
fmt.Println( s1) // [1 2 3 4 5]
s2 := make([]int, 10)
fmt.Println(s2) // [0 0 0 0 0 0 0 0 0 0]
copy(s2, s1)
fmt.Println(s1) //[1 2 3 4 5]
fmt.Println(s2) //[1 2 3 4 5 0 0 0 0 0]
拷贝
//func copy(dst, src []Type) int
//内置函数copy编译时被改写如下
//cmd/compile/internal/gc/walk.go
/*
// Lower copy(a, b) to a memmove call or a runtime call.
//
// init {
// n := len(a)
// if n > len(b) { n = len(b) }
// if a.ptr != b.ptr { memmove(a.ptr, b.ptr, n*sizeof(elem(a))) }
// }
// n;
//
// Also works if b is a string.
//
*/
func copyany(n *Node, init *Nodes, runtimecall bool) *Node {//如果有指针调用 runtime/mbarrier.go typedslicecopy 拷贝if n.Left.Type.Elem().HasPointers() {...fn := writebarrierfn("typedslicecopy", n.Left.Type.Elem(), n.Right.Type.Elem())...return mkcall1(fn, n.Type, init, typename(n.Left.Type.Elem()), ptrL, lenL, ptrR, lenR)}//如果时运行时调用 runtime/slice.go typedslicecopy 拷贝if runtimecall {...fn := syslook("slicecopy")fn = substArgTypes(fn, ptrL.Type.Elem(), ptrR.Type.Elem())return mkcall1(fn, n.Type, init, ptrL, lenL, ptrR, lenR, nodintconst(n.Left.Type.Elem().Width))}....
}// runtime/slice.go
// 无指针类型切片或者string copy到另一个切片
func slicecopy(toPtr unsafe.Pointer, toLen int, fromPtr unsafe.Pointer, fromLen int, width uintptr) int {if fromLen == 0 || toLen == 0 {return 0}n := fromLenif toLen < n {n = toLen}if width == 0 {return n}size := uintptr(n) * width//其他代码....if size == 1 { *(*byte)(toPtr) = *(*byte)(fromPtr) // known to be a byte pointer} else {memmove(toPtr, fromPtr, size)}return n
}//runtime/mbarrier.go
//go:nosplit 有指针类型切片copy到另一个切片
func typedslicecopy(typ *_type, dstPtr unsafe.Pointer, dstLen int, srcPtr unsafe.Pointer, srcLen int) int {n := dstLenif n > srcLen {n = srcLen}if n == 0 {return 0}// 其他代码...size := uintptr(n) * typ.sizeif writeBarrier.needed {pwsize := size - typ.size + typ.ptrdatabulkBarrierPreWrite(uintptr(dstPtr), uintptr(srcPtr), pwsize)}memmove(dstPtr, srcPtr, size)return n
}
追加与扩容
//func append(slice []Type, elems ...Type) []Type
//内置函数append编译时被改写
//cmd/compile/internal/gc/ssa.go
func (s *state) append(n *Node, inplace bool) *ssa.Value {// If inplace is false, 不需要赋值变量 "append(s, e1, e2, e3)"://// ptr, len, cap := s// newlen := len + 3// if newlen > cap {// ptr, len, cap = growslice(s, newlen) // 调用切片扩容// newlen = len + 3 // recalculate to avoid a spill// }// // with write barriers, if needed:// *(ptr+len) = e1// *(ptr+len+1) = e2// *(ptr+len+2) = e3// return makeslice(ptr, newlen, cap)////// If inplace is true, 需要赋值变量 "s = append(s, e1, e2, e3)"://// a := &s// ptr, len, cap := s// newlen := len + 3// if uint(newlen) > uint(cap) {// newptr, len, newcap = growslice(ptr, len, cap, newlen) // 调用切片扩容// vardef(a) // if necessary, advise liveness we are writing a new a// *a.cap = newcap // write before ptr to avoid a spill// *a.ptr = newptr // with write barrier// }// newlen = len + 3 // recalculate to avoid a spill// *a.len = newlen// // with write barriers, if needed:// *(ptr+len) = e1// *(ptr+len+1) = e2// *(ptr+len+2) = e3...
}// runtime/slice.go
// 扩容核心代码
func growslice(et *_type, old slice, cap int) slice {//1. 大致确定扩容容量newcap := old.capdoublecap := newcap + newcapif cap > doublecap {newcap = cap //a.如果期望容量大于当前容量的两倍就会使用期望容量} else {if old.len < 1024 {newcap = doublecap //b.当前切片的长度小于 1024 就会将容量翻倍} else {for 0 < newcap && newcap < cap {newcap += newcap / 4 //c.当前切片的长度大于 1024 就会每次增加 25% 的容量,直到新容量大于期望容量}if newcap <= 0 {newcap = cap}}} //2. 根据元素大小做内存对齐var overflow boolvar lenmem, newlenmem, capmem uintptrswitch {case et.size == 1: // 1lenmem = uintptr(old.len)newlenmem = uintptr(cap)capmem = roundupsize(uintptr(newcap))overflow = uintptr(newcap) > maxAllocnewcap = int(capmem)case et.size == sys.PtrSize: // 8lenmem = uintptr(old.len) * sys.PtrSizenewlenmem = uintptr(cap) * sys.PtrSizecapmem = roundupsize(uintptr(newcap) * sys.PtrSize)overflow = uintptr(newcap) > maxAlloc/sys.PtrSizenewcap = int(capmem / sys.PtrSize)case isPowerOfTwo(et.size): // 2的倍数...default:...}//3. 内存申请,底层数组拷贝var p unsafe.Pointerif et.ptrdata == 0 { //指针类型p = mallocgc(capmem, nil, false)memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem) //清空超出当前长度的内存} else { //非指针类型p = mallocgc(capmem, et, true)if lenmem > 0 && writeBarrier.enabled {bulkBarrierPreWriteSrcOnly(uintptr(p), uintptr(old.array), lenmem-et.size+et.ptrdata)}}memmove(p, old.array, lenmem) //原数组拷贝到新内存中return slice{p, old.len, newcap}
}
https://www.jianshu.com/p/54be5b08a21c
https://blog.csdn.net/qq_35423190/article/details/112803978
https://www.topgoer.cn/docs/golang/chapter03-11
哈希表 map
- 引用类型
- Map 是 hash 表来实现的,一种无序的键值对的集合。
- key 类似于索引,指向数据的value,**key必须可以使用==运算符来比较,不能重复。**int、布尔、string、或包含前面的struct、数组等
- map自动扩容
//--------------声明---------------
// var mapname map[key_type]value_type
var m map[string]string//--------------初始化---------------
// var mapname map[key_type]value_type = make(map[key_type]value_type,[size])
// mapname:=map[key_type]value_type{key:value...}
// func make(Type, size IntegerType) Type
// Type map[key_type]value_type
// size map容量预估
var m1 = make(map[string]string,5)
m2 := map[string]string{"野王":"赵云"}
m3 := make(map[string]string)//--------------查找---------------
// m[key]返回值和是否存在(bool类型)
val,flag := m2["野王"]
if flag{fmt.Println("野王:",val)
}//--------------修改---------------
//map[key] = value 覆盖方式
m1["上官"] = "言为心声"
m1["婉儿"] = "字为心画"
m2["野王"] = "老虎"
m2["上单"] = "吕布"//--------------删除---------------
//func delete(m map[Type]Type1, key Type)
delete(m1,"上官")//--------------清空---------------
// make其他当前
m1 = make(map[string]string,0)//--------------遍历---------------
// for range
// map无序
for key,value := range m2{fmt.Println(key,value)
}// 遍历keys
for key := range m2{fmt.Println(key)
}
源码分析
const(// flagsiterator = 1 // there may be an iterator using bucketsoldIterator = 2 // there may be an iterator using oldbucketshashWriting = 4 // a goroutine is writing to the mapsameSizeGrow = 8 // the current map growth is to a new map of the same size
)// 哈希表结构体
// runtime/map.go
type hmap struct {count int // 哈希表元素个数。len()可获取flags uint8 // 扩容标志位B uint8 // len(buckets) == 2^Bnoverflow uint16 // 溢出的buckets数hash0 uint32 // 哈希种子 创建哈希表时确定 提供随机性buckets unsafe.Pointer // buckets数组指针oldbuckets unsafe.Pointer // 哈希在扩容时用于复制的buckets数组指针,它的大小是当前buckets数组的一半nevacuate uintptr // 搬迁进度(已经搬迁的buckets数量)extra *mapextra
}type mapextra struct {overflow *[]*bmap //存储hamp.buckets 所有溢出桶oldoverflow *[]*bmap //存储hamp.oldbuckets 所有溢出桶nextOverflow *bmap //指向下一个overflow的bucket
}// 哈希表结构体中的 bucket
// runtime/map.go
type bmap struct {// tophash[0] < minTopHash为存储 bucket evacuation 状态.tophash [bucketCnt]uint8 // 因为不支持动态类型,编译时推导内存大小// cmd/compile/internal/gc/refect// func bmap(t *types.Type) *types.Type {} // topbits [8]uint8 //哈希高8位 减少全等对比提高效率// keys [8]keytype //哈希表健// elems [8]elemtype //哈希表值// overflow uintptr //溢出桶指针
}// 编译时生成hmap代码
// cmd/compile/internal/gc/refect
func hmap(t *types.Type) *types.Type {//其他代码...// build a struct:// type hmap struct {// count int// flags uint8// B uint8// noverflow uint16// hash0 uint32// buckets *bmap// oldbuckets *bmap// nevacuate uintptr// extra unsafe.Pointer // *mapextra// }// must match runtime/map.go:hmap.fields := []*types.Field{makefield("count", types.Types[TINT]),makefield("flags", types.Types[TUINT8]),makefield("B", types.Types[TUINT8]),makefield("noverflow", types.Types[TUINT16]),makefield("hash0", 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[TUINTPTR]),makefield("extra", types.Types[TUNSAFEPTR]),}//其他代码...return hmap
}const(bucketCntBits = 3bucketCnt = 1 << bucketCntBitsminTopHash = 5 // minimum tophash for a normal filled cell.
)// 编译时生成bmap代码
// cmd/compile/internal/gc/refect
func bmap(t *types.Type) *types.Type {arr := types.NewArray(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)otyp := types.NewPtr(bucket)if !elemtype.HasPointers() && !keytype.HasPointers() {otyp = types.Types[TUINTPTR]}overflow := makefield("overflow", otyp)field = append(field, overflow)bucket.SetNoalg(true)bucket.SetFields(field[:])//其他代码...return bucket
}// 初始化编译时
// cmd/compile/internal/gc/sinit.go
func maplit(n *Node, m *Node, init *Nodes) {// make the map vara := nod(OMAKE, nil, nil)a.Esc = n.Esca.List.Set2(typenod(n.Type), nodintconst(int64(n.List.Len())))litas(m, a, init)entries := n.List.Slice()// 其他代码...// 元素超过25个使用for循环加入hash表if len(entries) > 25 {tk := types.NewArray(n.Type.Key(), int64(len(entries)))te := types.NewArray(n.Type.Elem(), int64(len(entries)))// 其他代码...// make and initialize static arraysvstatk := readonlystaticname(tk)vstate := readonlystaticname(te)// 其他代码...// loop adding structure elements to map// for i = 0; i < len(vstatk); i++ {// map[vstatk[i]] = vstate[i]// }// 其他代码...return}// 元素不超过25转换为所有键值对一次性加入哈希表// Build list of var[c] = expr.// Use temporaries so that mapassign1 can have addressable key, elem.// 其他代码...
}//运行时初始化
// runtime/map.go
func makemap(t *maptype, hint int, h *hmap) *hmap {mem, overflow := math.MulUintptr(uintptr(hint), t.bucket.size)if overflow || mem > maxAlloc {hint = 0}// initialize Hmapif h == nil {h = new(hmap)}h.hash0 = fastrand() // 获取hash种子// hint为计算的最小需要同的数量// Find the size parameter B which will hold the requested # of elements.// For hint < 0 overLoadFactor returns false since hint < bucketCnt.B := uint8(0)for overLoadFactor(hint, B) {B++}h.B = B// 初始化桶if h.B != 0 {var nextOverflow *bmaph.buckets, nextOverflow = makeBucketArray(t, h.B, nil)if nextOverflow != nil {h.extra = new(mapextra)h.extra.nextOverflow = nextOverflow}}return h
}// runtime/map.go
// makeBucketArray 根据传入的B创建桶数量并分配连续内存存储数据.
func makeBucketArray(t *maptype, b uint8, dirtyalloc unsafe.Pointer) (buckets unsafe.Pointer, nextOverflow *bmap) {base := bucketShift(b)nbuckets := base// 桶数量小于2^4,溢出桶使用可能较小,避免溢出桶创建// 桶数量大于2^4,额外创建2^(b-4)个溢出桶if b >= 4 {nbuckets += bucketShift(b - 4)sz := t.bucket.size * nbucketsup := roundupsize(sz)if up != sz {nbuckets = up / t.bucket.size}}if dirtyalloc == nil {buckets = newarray(t.bucket, int(nbuckets))} else {// dirtyalloc was previously generated by// the above newarray(t.bucket, int(nbuckets))// but may not be empty.buckets = dirtyallocsize := t.bucket.size * nbucketsif t.bucket.ptrdata != 0 {memclrHasPointers(buckets, size)} else {memclrNoHeapPointers(buckets, size)}}if base != nbuckets {// We preallocated some overflow buckets.// To keep the overhead of tracking these overflow buckets to a minimum,// we use the convention that if a preallocated overflow bucket's overflow// pointer is nil, then there are more available by bumping the pointer.// We need a safe non-nil pointer for the last overflow bucket; just use buckets.nextOverflow = (*bmap)(add(buckets, base*uintptr(t.bucketsize)))last := (*bmap)(add(buckets, (nbuckets-1)*uintptr(t.bucketsize)))last.setoverflow(t, (*bmap)(buckets))}return buckets, nextOverflow
}// 访问
/* 编译时会进行如下中代码生成
v := hash[key] // => v := *mapaccess1(maptype, hash, &key)
v, ok := hash[key] // => v, ok := mapaccess2(maptype, hash, &key)
*/
// runtime/map.go
func mapaccess1(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {if raceenabled && h != nil {callerpc := getcallerpc()pc := funcPC(mapaccess1)racereadpc(unsafe.Pointer(h), callerpc, pc)raceReadObjectPC(t.key, key, callerpc, pc)}if msanenabled && h != nil {msanread(key, t.key.size)}if h == nil || h.count == 0 {if t.hashMightPanic() {t.hasher(key, 0) // see issue 23734}return unsafe.Pointer(&zeroVal[0])}if h.flags&hashWriting != 0 {throw("concurrent map read and map write")}// 1. 获取当前key hashhash := t.hasher(key, uintptr(h.hash0))m := bucketMask(h.B)// 2. 获取桶序号 hash取后B位表示在那个桶b := (*bmap)(add(h.buckets, (hash&m)*uintptr(t.bucketsize)))if c := h.oldbuckets; c != nil {if !h.sameSizeGrow() {// There used to be half as many buckets; mask down one more power of two.m >>= 1}oldb := (*bmap)(add(c, (hash&m)*uintptr(t.bucketsize)))if !evacuated(oldb) {b = oldb}}// 获取高8位hashtop := tophash(hash)// 3.循环依次查找正常桶和溢出桶数据
bucketloop:for ; b != nil; b = b.overflow(t) {for i := uintptr(0); i < bucketCnt; i++ {// 高8位比较(低概率)if b.tophash[i] != top {if b.tophash[i] == emptyRest {break bucketloop}continue}// 获取对应kk := add(unsafe.Pointer(b), dataOffset+i*uintptr(t.keysize))if t.indirectkey() {k = *((*unsafe.Pointer)(k))}// 比较k是否符合(低概率)if t.key.equal(key, k) {e := add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+i*uintptr(t.elemsize))if t.indirectelem() {e = *((*unsafe.Pointer)(e))}return e}}}return unsafe.Pointer(&zeroVal[0])
}// 写入与扩容
// runtime/map.go
// Like mapaccess, but allocates a slot for the key if it is not present in the map.
func mapassign(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {if h == nil {panic(plainError("assignment to entry in nil map"))}if raceenabled {callerpc := getcallerpc()pc := funcPC(mapassign)racewritepc(unsafe.Pointer(h), callerpc, pc)raceReadObjectPC(t.key, key, callerpc, pc)}if msanenabled {msanread(key, t.key.size)}if h.flags&hashWriting != 0 {throw("concurrent map writes")}// 1. 获取当前key hashhash := t.hasher(key, uintptr(h.hash0))// Set hashWriting after calling t.hasher, since t.hasher may panic,// in which case we have not actually done a write.h.flags ^= hashWritingif h.buckets == nil {h.buckets = newobject(t.bucket) // newarray(t.bucket, 1)}again:bucket := hash & bucketMask(h.B)// 如果在迁移if h.growing() {growWork(t, h, bucket)}// 2. 获取桶序号 获取桶序号 hash取后B位表示在那个桶b := (*bmap)(add(h.buckets, bucket*uintptr(t.bucketsize)))// 获取高8位hashtop := tophash(hash)var inserti *uint8 //目标元素在桶中的索引var insertk unsafe.Pointer //目标元素健var elem unsafe.Pointer //目标元素值
bucketloop:for {for i := uintptr(0); i < bucketCnt; i++ {// 匹配tophashif b.tophash[i] != top {if isEmpty(b.tophash[i]) && inserti == nil {inserti = &b.tophash[i]insertk = add(unsafe.Pointer(b), dataOffset+i*uintptr(t.keysize))elem = add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+i*uintptr(t.elemsize))}if b.tophash[i] == emptyRest {break bucketloop}continue}k := add(unsafe.Pointer(b), dataOffset+i*uintptr(t.keysize))if t.indirectkey() {k = *((*unsafe.Pointer)(k))}// 匹配keyif !t.key.equal(key, k) {continue}if t.needkeyupdate() {typedmemmove(t.key, k, key)}elem = add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+i*uintptr(t.elemsize))goto done}ovf := b.overflow(t)if ovf == nil {break}// 遍历溢出桶b = ovf}// 桶溢出逻辑处理// 扩容 // 1. 装载因子超过 count/bucket>6.5 (key多桶少,B+1 双倍扩容)// 2. B<15 bucket=2^b overflow>bucket B>=15 overflow>2^16 (桶多key分散,等量扩容减少overflow,紧凑key)if !h.growing() && (overLoadFactor(h.count+1, h.B) || tooManyOverflowBuckets(h.noverflow, h.B)) {hashGrow(t, h)goto again }// 当前桶满了if inserti == nil {// 创建或者使用之前的溢出桶保存数据newb := h.newoverflow(t, b)inserti = &newb.tophash[0]insertk = add(unsafe.Pointer(newb), dataOffset)elem = add(insertk, bucketCnt*uintptr(t.keysize))}// store new key/elem at insert positionif t.indirectkey() {kmem := newobject(t.key)*(*unsafe.Pointer)(insertk) = kmeminsertk = kmem}if t.indirectelem() {vmem := newobject(t.elem)*(*unsafe.Pointer)(elem) = vmem}typedmemmove(t.key, insertk, key)*inserti = toph.count++done://其他代码...return elem
}// 扩容
// runtime/map.go
func hashGrow(t *maptype, h *hmap) {bigger := uint8(1)if !overLoadFactor(h.count+1, h.B) {// 溢出桶过多,等量扩容bigger = 0h.flags |= sameSizeGrow}oldbuckets := h.buckets // 保存旧桶引用newbuckets, nextOverflow := makeBucketArray(t, h.B+bigger, nil) //创建新桶,溢出桶// 标记 h.buckets 将挂在 h.oldbucketsflags := h.flags &^ (iterator | oldIterator)if h.flags&iterator != 0 {flags |= oldIterator}h.B += biggerh.flags = flagsh.oldbuckets = oldbucketsh.buckets = newbucketsh.nevacuate = 0 // 迁移进度 0 h.noverflow = 0 // 溢出桶 0// extra更新// overflow 指向 oldoverflow// overflow 清空// nextOverflow 更新if h.extra != nil && h.extra.overflow != nil {// Promote current overflow buckets to the old generation.if h.extra.oldoverflow != nil {throw("oldoverflow is not nil")}h.extra.oldoverflow = h.extra.overflowh.extra.overflow = nil}if nextOverflow != nil {if h.extra == nil {h.extra = new(mapextra)}h.extra.nextOverflow = nextOverflow}// the actual copying of the hash table data is done incrementally// by growWork() and evacuate().
}// 搬迁bucket
// 迁移结构体
type evacDst struct {b *bmap // 桶 指针i int // 键值 索引k unsafe.Pointer // 健 指针e unsafe.Pointer // 值 指针
}//迁移数据函数
func evacuate(t *maptype, h *hmap, oldbucket uintptr) {// 定位老的bucket数组地址b := (*bmap)(add(h.oldbuckets, oldbucket*uintptr(t.bucketsize)))// 老的bucket桶数量newbit := h.noldbuckets()// 判断是否被搬迁过if !evacuated(b) {// xy contains the x and y (low and high) evacuation destinations.var xy [2]evacDstx := &xy[0]x.b = (*bmap)(add(h.buckets, oldbucket*uintptr(t.bucketsize)))x.k = add(unsafe.Pointer(x.b), dataOffset)x.e = add(x.k, bucketCnt*uintptr(t.keysize))if !h.sameSizeGrow() {// 双倍扩容,初始化第二个evacDst扩容y := &xy[1]y.b = (*bmap)(add(h.buckets, (oldbucket+newbit)*uintptr(t.bucketsize)))y.k = add(unsafe.Pointer(y.b), dataOffset)y.e = add(y.k, bucketCnt*uintptr(t.keysize))}// 遍历所有溢出桶for ; b != nil; b = b.overflow(t) {k := add(unsafe.Pointer(b), dataOffset)e := add(k, bucketCnt*uintptr(t.keysize))for i := 0; i < bucketCnt; i, k, e = i+1, add(k, uintptr(t.keysize)), add(e, uintptr(t.elemsize)) {top := b.tophash[i]if isEmpty(top) { //被搬迁过b.tophash[i] = evacuatedEmptycontinue}if top < minTopHash {throw("bad map state")}k2 := kif t.indirectkey() {k2 = *((*unsafe.Pointer)(k2))}var useY uint8if !h.sameSizeGrow() {//双倍扩容 //计算hash rehashhash := t.hasher(k2, uintptr(h.hash0))if h.flags&iterator != 0 && !t.reflexivekey() && !t.key.equal(k2, k2) {// 1/2概率使用新桶useY = top & 1top = tophash(hash)} else {// hash溢出桶全部迁移新桶if hash&newbit != 0 {useY = 1}}}if evacuatedX+1 != evacuatedY || evacuatedX^1 != evacuatedY {throw("bad evacuatedN")}b.tophash[i] = evacuatedX + useY // evacuatedX + 1 == evacuatedYdst := &xy[useY] // evacuation destinationif dst.i == bucketCnt {dst.b = h.newoverflow(t, dst.b)dst.i = 0dst.k = add(unsafe.Pointer(dst.b), dataOffset)dst.e = add(dst.k, bucketCnt*uintptr(t.keysize))}dst.b.tophash[dst.i&(bucketCnt-1)] = top // mask dst.i as an optimization, to avoid a bounds checkif t.indirectkey() {*(*unsafe.Pointer)(dst.k) = k2 // copy pointer} else {typedmemmove(t.key, dst.k, k) // copy elem}if t.indirectelem() {*(*unsafe.Pointer)(dst.e) = *(*unsafe.Pointer)(e)} else {typedmemmove(t.elem, dst.e, e)}dst.i++dst.k = add(dst.k, uintptr(t.keysize))dst.e = add(dst.e, uintptr(t.elemsize))}}// 迁移完成 清理指针if h.flags&oldIterator == 0 && t.bucket.ptrdata != 0 {b := add(h.oldbuckets, oldbucket*uintptr(t.bucketsize))// Preserve b.tophash because the evacuation// state is maintained there.ptr := add(b, dataOffset)n := uintptr(t.bucketsize) - dataOffsetmemclrHasPointers(ptr, n)}}ß// 更新搬迁进度if oldbucket == h.nevacuate {advanceEvacuationMark(h, t, newbit)}
}
loadFactor | %overflow | bytes/entry | hitprobe | missprobe |
---|---|---|---|---|
4.00 | 2.13 | 20.77 | 3.00 | 4.00 |
4.50 | 4.05 | 17.30 | 3.25 | 4.50 |
5.00 | 6.85 | 14.77ß | 3.50 | 5.00 |
5.50 | 10.55 | 12.94 | 3.75 | 5.50 |
6.00 | 15.27 | 11.67 | 4.00 | 6.00 |
6.50 | 20.90 | 10.79 | 4.25 | 6.50 |
7.00 | 27.14 | 10.15 | 4.50 | 7.00 |
- loadFactor:负载因子
- %overflow:溢出率,具有溢出桶 overflow buckets 的桶的百分比
- bytes/entry:每个键值对所的字节数开销
- hitprobe:查找存在的 key 时,平均需要检索的条目数量
- missprobe:查找不存在的 key 时,平均需要检索的条目数量
todo
// 总结
http://www.zzvips.com/article/194814.html
https://zhuanlan.zhihu.com/p/412581873
指针类型 pointer
- 引用类型
- 取地址符是&
- 指针保存的是地址,地址里的数据才是真正的值,使用*来获取值
int系列、float系列、bool、string、数组、结构体struct,一般在栈
指针、切片、管道、接口、Map是引用类型,一般在堆,GC回收
声明
//空指针 nil
var ptr *int//零指针
ptr1 := new(int)
取指针
// 获取指针 i的地址
i:=1
ptr:=&i
// 获取指针的指针 ptr的地址
ptr2:=&ptr
内存
类型判断
任何类型都是实现了interface空接口,interface可以承接任何类型后进行类型断言
comma-ok断言
// value ,ok := interface{}[(container)].(fieldtype)
// value container值
// ok 类型断言是否正确
// fieldtype 断言类型v,ok:=interface{}("我是string").(string)
println(v,ok) // "我是string" true
type - switch
/*
// data.(type) 获取类型
switch value := interface{}[(container)].(type) {case int:case string:...
}
*/
switch value := interface{}("我是string").(type) {case int:case string:println(value)
}
// string
golang 数据类型相关推荐
- Golang——数据类型使用细节详解
数据类型: 计算机存储设备最小信息单位是位(bit),最小的存储单元是字节(byte) 占用字节的不同,所表示能存储的数据长度不同 作用: 说明数据的数据的结构,便于后面定义变量.参数传递等 默认值: ...
- golang 数据类型之间的转换
一.基本数据类型之间的转换 1.string到int int,err:=strconv.Atoi(string) 2.string到int64 int64, err := strconv.ParseI ...
- 2.7——golang数据类型【字符串类型】
基本介绍 字符串就是一串固定长度的字符连接起来的字符序列.Go的字符串是由单个字节链接起来的. Go语言的字符串的字节使用utf-8编码标识Unicode文本 package main import ...
- golang 数据类型 简介
目录 bool 有符号整型 无符号整型 浮点型 复数类型 其他数字类型 string 类型 下面是 Go 支持的基本类型: bool 数字类型 int8, int16, int32, int64, i ...
- golang数据类型与MySQL数据类型的对应
本文原创文章,转载注明出处,博客地址 https://segmentfault.com/u/to... 第一时间看后续精彩文章.觉得好的话,顺手分享到朋友圈吧,感谢支持.
- java i数据类型_数据类型 I
都来自菜鸟教程,自己总结学习使用, JS 数据类型 没有对比就没有伤害, 字符串(String).数字(Number).布尔(Boolean).数组(Array).对象(Object).空(Null ...
- 使用Golang搭建gRPC服务提供给.NetCore客户端调用
gRPC概述 RPC 说到gRPC就不得不提RPC,所谓RPC(remote procedure call 远程过程调用)框架实际是提供了一套机制,使得应用程序之间可以进行通信,简单点来说就是我A机器 ...
- golang int64转string_(一)Golang从入门到原地起飞
1.Golang 变量定义方法: 1)定义变量 变量名 类型 = 表达式 var go string = "hello" 2)在函数内部,可以使用更简略的 := 方式声明并初始化变 ...
- Golang 的类型与零值
Golang 中定义不同类型的变量,不是通过声明就是通过 make 或 new . 未显式初始化时,将被赋予一个默认值,该默认值便为该类型的零值.不同的类型有不同的零值. 类型 类型名 零值 字符类型 ...
最新文章
- linux centos grub grub2 加密、清除
- 最短路径算法整理(二)
- 清华90后博士后26岁已在《科学》发文6篇
- ORA-01033 的解决
- python write函数换行_Python基础知识(三)
- 【Cocos2dx】资源目录,播放背景音乐,导入外部库
- 使用GMM进行语音性别检测(入门)
- 大话数据结构(个人笔记)
- Win 10 蓝屏,出现DRIVER_POWER_STATE_FAILURE的解决方法
- 西瓜数据集介绍以及获取。
- Java源文件编译出错:类文件包含错误的类 请删除该文件或确保文件位于正确的类路径子目录中
- matlab求两向量夹角_【求精干货】高中数学知识点总结归纳高一学生必须掌握
- 掌上题库V1.2.2全开源版本
- CSDN下载频道2013下半年超人气精华资源汇总
- js 你知道为什么[]==![]是成立的吗(强制类型转换)
- 二、Unity编辑器开发之ContextMenu
- css 按空格键对按钮暂停,当按下回车键后,怎么清空回车键的空格,或者模拟发送按键让光标向上?...
- VScode运行js时,出现 'node' �����
- 如何从ip服务器所用系统,系统运维|如何使用phpIPAM来管理IP地址和子网
- Java使用hotmail的SMTP服务器转发邮件出错
热门文章
- 通信原理:数字信号的载波传输
- xml.parsers.expat.ExpatError: mismatched tag: line 63, column 4
- 关于anaconda navigator打不开问题的解决
- linux在内核中设置开机logo
- 罗马仕php30和wa30,容量大 多接口,充电宝中的战斗机 — 罗马仕WA30充电宝测评...
- 【Unity】Unity 生命周期
- Java基础(19)数据结构概述、ArrayList集合、Vector集合、LinkedList集合、集合框架练习
- 鸿蒙OS安卓版,多款纯鸿蒙OS应用已上线,体积小、运行更流畅,有鸿蒙OS专属图标...
- 学生大本营社区运营专员招聘公告
- Linux进阶学习笔记之——局域网控制者:Proxy(代理)服务器