基本变量类型的字节大小

  1. // 查看编译后的汇编
  2. go build -gcflags -S main.go

部分数据结构长度与系统字长有关

  1. // 64位系统
  2. fmt.Printf("int :%d \n",unsafe.Sizeof(int(0))) //8字节
  3. fmt.Printf("int8 :%d \n",unsafe.Sizeof(int8(0))) //1
  4. fmt.Printf("int32 :%d \n",unsafe.Sizeof(int32(0))) //4
  5. fmt.Printf("int64 :%d \n",unsafe.Sizeof(int64(0))) //8
  6. fmt.Printf("uint :%d \n",unsafe.Sizeof(uint(0))) //8
  7. fmt.Printf("uint8 :%d \n",unsafe.Sizeof(uint8(0))) //1
  8. fmt.Printf("uint32 :%d \n",unsafe.Sizeof(uint32(0))) //4
  9. fmt.Printf("uint64 :%d \n",unsafe.Sizeof(uint64(0))) //8
  10. // 32系统
  11. fmt.Printf("int :%d \n",unsafe.Sizeof(int(0))) //4字节

有部分数据类型的字节大小是根据系统字长变化的:int类型

空结构体

空结构体独立使用,

  • 长度为0
  • 地址为zeroBase(zeroBase是所有字节长度为0的地址
  • image.png ```go type k struct {}

a:=k{} b:=”6666” c:=k{} fmt.Printf(“%d \n”,unsafe.Sizeof(a)) //0 fmt.Printf(“%p \n”,&a) //0x6bcde0 fmt.Printf(“%p \n”,&b) //0xc000088230 fmt.Printf(“%p \n”,&c) //0x6bcde0

  1. <a name="fSJyr"></a>
  2. #### 空结构体和其他结构组合使用
  3. - 空结构体的地址为其父结构体的地址
  4. ```go
  5. type k struct {
  6. }
  7. type p struct {
  8. num1 k
  9. nums2 string
  10. }
  11. func main() {
  12. a:=k{}
  13. c:=p{nums2: "666"}
  14. fmt.Printf("%d \n",unsafe.Sizeof(a)) //0
  15. fmt.Printf("%p \n",&a) //0x10cdde0
  16. fmt.Printf("%p \n",&c.nums2)// 0xc000088230
  17. fmt.Printf("%p \n",&c.num1)//0xc000088230
  18. fmt.Printf("%p \n",&c)//0xc000088230
  19. }

空结构体的使用

  1. 实现hashset

    1. m := map[string]struct{}{} //key:null
  2. 实现信息传递

    1. ch := make(chan struct{})

空结构体主要是可以节约内存

数组,字符串,切片底层是否一样

字符串

底层是一个结构体

image.png

  1. type stringStruct struct {
  2. str unsafe.Pointer //指针,可以指向任意数据类型
  3. len int //8字节
  4. }
  5. // len 表示字符串的底层byte数组的长度(字节数)
  1. Data指针指向底层Byte数组
  2. 如何获取字符串结构体的len

go的所有字符均采用的的Unicode字符集,使用的utf-8(utf-8:八位为一个字节,英文字母,英文标点一个字节,西方常用字符需要两个字节如希腊字母,中文至少需要3个字节表示,极少部分需要四个字节)编码
image.png

  1. // 需要借助反射中的字符串结构体
  2. s := "李易峰"
  3. sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
  4. fmt.Println(sh.Len) //9
  5. s2 := "李易峰abcde"
  6. sh2 := (*reflect.StringHeader)(unsafe.Pointer(&s2))
  7. fmt.Println(sh.Len) //14

遍历字符串
  1. 用range遍历字符串,被解码成rune(代表一个utf8字符) 类型的字符

    1. // rune is an alias for int32 and is equivalent to int32 in all ways. It is
    2. // used, by convention, to distinguish character values from integer values.
    3. type rune = int32

    遍历字符串解码(一个字符解码成多个字节)时采用了runtime包utf8.go文件的decoderune方法

    1. // decoderune returns the non-ASCII rune at the start of
    2. // s[k:] and the index after the rune in s.
    3. //
    4. // decoderune assumes that caller has checked that
    5. // the to be decoded rune is a non-ASCII rune.
    6. //
    7. // If the string appears to be incomplete or decoding problems
    8. // are encountered (runeerror, k + 1) is returned to ensure
    9. // progress when decoderune is used to iterate over a string.
    10. func decoderune(s string, k int) (r rune, pos int) {
    11. pos = k
    12. if k >= len(s) {
    13. return runeError, k + 1
    14. }
    15. s = s[k:]
    16. switch {
    17. case t2 <= s[0] && s[0] < t3:
    18. // 0080-07FF two byte sequence
    19. if len(s) > 1 && (locb <= s[1] && s[1] <= hicb) {
    20. r = rune(s[0]&mask2)<<6 | rune(s[1]&maskx)
    21. pos += 2
    22. if rune1Max < r {
    23. return
    24. }
    25. }
    26. case t3 <= s[0] && s[0] < t4:
    27. // 0800-FFFF three byte sequence
    28. if len(s) > 2 && (locb <= s[1] && s[1] <= hicb) && (locb <= s[2] && s[2] <= hicb) {
    29. r = rune(s[0]&mask3)<<12 | rune(s[1]&maskx)<<6 | rune(s[2]&maskx)
    30. pos += 3
    31. if rune2Max < r && !(surrogateMin <= r && r <= surrogateMax) {
    32. return
    33. }
    34. }
    35. case t4 <= s[0] && s[0] < t5:
    36. // 10000-1FFFFF four byte sequence
    37. if len(s) > 3 && (locb <= s[1] && s[1] <= hicb) && (locb <= s[2] && s[2] <= hicb) && (locb <= s[3] && s[3] <= hicb) {
    38. r = rune(s[0]&mask4)<<18 | rune(s[1]&maskx)<<12 | rune(s[2]&maskx)<<6 | rune(s[3]&maskx)
    39. pos += 4
    40. if rune3Max < r && r <= maxRune {
    41. return
    42. }
    43. }
    44. }
    45. return runeError, k + 1
    46. }
  1. s := "李易峰dashi"
  2. for _, v := range s {
  3. fmt.Printf("%c",v)
  4. }
  5. //李 易 峰 d a s h i
  1. 使用下标访问字符串,得到是字节 ```go s := “李易峰dashi” for i := 0; i < len(s); i++ { fmt.Println(s[i]) }

230 157 142 230 152 147 229 179 176 100 97 115 104 105

  1. 5. 字符串需要切分时
  2. - 转为rune数组
  3. - 切片
  4. - 转为字符串
  5. ```go
  6. s := "李易峰dashi"
  7. s = string([]rune(s)[:3])
  8. fmt.Println(s)//李易峰
  1. 7

    切片

    切片结构体,runtime下面的slice.go

    切片的本质是对数组的一种引用

    1. type slice struct {
    2. array unsafe.Pointer
    3. len int //长度
    4. cap int //容量
    5. }

    创建切片
  2. 根据数组创建

    1. arr :=[5]int{1,2,3,4,5}
    2. s := arr[0:3]
  3. 字面量:根据编译时插入创建切片的代码 ```go slice := []int{1,2,3}

// 该方法创建原理 // 1先创建一个数组[3]int{1,2,3} // 2.实例切片结构体

  1. 3. make:运行时创建切片
  2. ```go
  3. slice := make([]int,10)

运行时创建切片的原理,位于runtime的slice.go

  1. func makeslice(et *_type, len, cap int) unsafe.Pointer {
  2. mem, overflow := math.MulUintptr(et.size, uintptr(cap))
  3. if overflow || mem > maxAlloc || len < 0 || len > cap {
  4. // NOTE: Produce a 'len out of range' error instead of a
  5. // 'cap out of range' error when someone does make([]T, bignumber).
  6. // 'cap out of range' is true too, but since the cap is only being
  7. // supplied implicitly, saying len is clearer.
  8. // See golang.org/issue/4085.
  9. mem, overflow := math.MulUintptr(et.size, uintptr(len))
  10. if overflow || mem > maxAlloc || len < 0 {
  11. panicmakeslicelen()
  12. }
  13. panicmakeslicecap()
  14. }
  15. return mallocgc(mem, et, true)
  16. }

切片的访问
  1. 下标直接访问元素
  2. range遍历元素
  3. len(slce)查看切片长度
  4. cap(clice)查看切片容量 ```go s :=[]int{1,2,3} 下标直接访问元素 s1 :=s[0] range遍历元素 for k,v := range s{ fmt.Println(k,v) }

len(s)//3 cap(s)//3

  1. <a name="gXiCX"></a>
  2. ##### 切片追加
  3. 1. 追加的元素,加上原有的元素没有超过切片容量,则不应扩容
  4. 2. 切片扩容会创建底层数组,因为数组内容字节地址是连续的,所以扩容就只能重开内存地址存储新的切片
  5. 3. 如果期望容量大于当前容量的两倍就会使用期望容量
  6. 4. 如果切片的长度小于1024,扩容容量会翻倍
  7. 5. 如果切片长度大于1024,扩容容量每次增加25%
  8. 6. 切片扩容时,**并发不安全**,切片并发要加锁
  9. 7. 切片扩容编译时,会调用runtime.growslice()
  10. <a name="M2txl"></a>
  11. #### 总结
  12. 1. 字符串与切片底层都是对数组的引用
  13. 2. 字符串有UTF-8变长编码的特点
  14. 3. 切片的容量和长度不同
  15. 4. 切片追加时可能会重建底层数组
  16. <a name="xxN7Y"></a>
  17. ### Map
  18. <a name="wTJ7Y"></a>
  19. #### map的底层是一个hashMap
  20. hashMap实现的基本方案
  21. - 开放寻址法
  22. - 拉链法(go的map就是这种)
  23. <a name="dqXWZ"></a>
  24. #### map结构体
  25. ```go
  26. // A header for a Go map.
  27. type hmap struct {
  28. // Note: the format of the hmap is also encoded in cmd/compile/internal/gc/reflect.go.
  29. // Make sure this stays in sync with the compiler's definition.
  30. count int // # live cells == size of map. Must be first (used by len() builtin)
  31. flags uint8
  32. B uint8 // log2为底buckets的对数
  33. noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details
  34. hash0 uint32 // hash seed
  35. buckets unsafe.Pointer // 2的B次方。可以等于0
  36. oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing
  37. nevacuate uintptr // progress counter for evacuation (buckets less than this have been evacuated)
  38. extra *mapextra // optional fields
  39. }

map的初始化

字面量初始化
  1. 元素个数小于25时

    1. // 创建一个map
    2. hash := map[string]int{
    3. "1":2,
    4. "3":4,
    5. "5":6,
    6. }
    7. // 编译时代码为
    8. hash := make(map[string]int,3)
    9. hash["1"] = 2
    10. hash["3"] = 4
    11. hash["5"] = 6
  2. 元素个数大于25 ```go // 创建一个长度26的map hash := map[string]int{ “1”:1, “2”:2, “3”:3, …. “26”:26, } // 编译时的代码 hash := make(map[string]int,26) vstatk := []string{“1”,”2”,”3”….”26”} vstatv := []int{1,2,3…..26} for i:=0;i<len(vstatk);i++{ hash[vstatk[i]] = vstatv[i] }

  1. <a name="EsXlQ"></a>
  2. ##### make初始化
  3. ```go
  4. func makemap(t *maptype, hint int, h *hmap) *hmap {
  5. mem, overflow := math.MulUintptr(uintptr(hint), t.bucket.size)
  6. if overflow || mem > maxAlloc {
  7. hint = 0
  8. }
  9. // initialize Hmap 初始化一个hmap
  10. if h == nil {
  11. h = new(hmap)
  12. }
  13. h.hash0 = fastrand()
  14. // Find the size parameter B which will hold the requested # of elements.
  15. // For hint < 0 overLoadFactor returns false since hint < bucketCnt.
  16. // 根据传进来的数据,计算出B,然后算出桶buckets的数量,如果B=3,桶为8个,B=4,buckets桶为16个
  17. B := uint8(0)
  18. for overLoadFactor(hint, B) {
  19. B++
  20. }
  21. h.B = B
  22. // allocate initial hash table
  23. // if B == 0, the buckets field is allocated lazily later (in mapassign)
  24. // If hint is large zeroing this memory could take a while.
  25. if h.B != 0 {
  26. var nextOverflow *bmap
  27. // 创建buckets数量的桶和一些溢出桶
  28. h.buckets, nextOverflow = makeBucketArray(t, h.B, nil)
  29. // 存储创建的溢出桶
  30. if nextOverflow != nil {
  31. h.extra = new(mapextra)
  32. h.extra.nextOverflow = nextOverflow
  33. }
  34. }
  35. return h
  36. }

image.png

map的访问

image.png

  1. 计算桶号
  • 如果有8个正常桶,则B=3.
  • B为3就要取hash值二进制的后3位
  • 根据后三位得到的数量,就为桶号 2

image.png
image.png

  1. 计算位置
  • 取出key的高八位,计算tophash

image.png

  1. 匹配。

image.png

  • 用计算出的tophash,在topshash数组中查找,或者用key在keys数组中找
  • 如果正常桶中没找到,就去溢出桶overfow中找
  • 如果在溢出桶中没找到,则代表key=“a”不存在
  • tophash,keys,elems数组长度均为8
  • tophash:记录了hash keys值的高八位,用于快速遍历

    map扩容

    扩容
  1. 装载系数或者溢出桶增加,会触发map扩容
  2. “扩容”可能不是增加桶的数量。可能是整理
  3. map采用渐进是式扩容,桶被操作是才会出现分配

    sync.map
  4. 是一种并发安全的map结构

  5. map才扩容时可能会有并发问题
  6. sync.map使用了两个map,分离了扩容问题
  7. 不会引发扩容的操作(查,改)使用了read map
  8. 可能引发扩容的操作(新增)使用dirty map(带锁)

    总结

  • go语言使用了拉链法实现hashmap
  • 每一个桶中存储键哈希的前八位
  • 桶超出8个数据,就会存储到溢出桶overflow

    接口

    go隐式实现接口

  1. 只要实现了go接口的全部方法,就自动实现了这个接口
  2. 不修改代码情况下,抽象总结出新的接口 ```go type People interface { getName() string }

type Man struct { name string }

func (m Man) getName() string { return m.name }

func main(){ var p People = Man{} fmt.Pringtln(p) }

  1. 3. 接口的值的底层结构
  2. ```go
  3. // 上述代码。p接口的底层
  4. type iface struct {
  5. tab *itab //记录了接口类型的信息和实现的方法,方便与类型断言
  6. data unsafe.Pointer //该指针指向的地址是上面代码的Man,
  7. }
  1. 类型断言

    • 类型断言是使用在接口值上的操作

      1. func main(){
      2. var p People = Man{}
      3. m := p.(Man)//类型断言,弱转换
      4. fmt.Pringtln(p)
      5. }
    • 可以将接口值转换为其他类型值(或者兼容接口)

    • 配合switch进行类型判断
      1. var p People = Man{"男人"}
      2. m := p.(Man)
      3. fmt.Pringtln(m)
      4. switch p.(type) {
      5. case Man:
      6. fmt.Println("m")
      7. }
  2. 结构体和指针实现接口 | | 结构体实现接口 | 结构体指针实现接口 | | —- | —- | —- | | 结构体初始化变量 | 通过 | 不通过 | | 结构体指针初始化变量 | 通过 | 通过 |

结构体在实现接口方法时,如果未使用结构体指针实现接口的方法,在编译时会创建一个结构体指针实现接口的方法

  1. package main
  2. import "fmt"
  3. type People interface {
  4. getName() string
  5. }
  6. type Man struct {
  7. name string
  8. }
  9. type Woman struct {
  10. name string
  11. }
  12. func (m Man) getName() string {
  13. return m.name
  14. }
  15. func (w *Woman) getName() string {
  16. return w.name
  17. }
  18. func main() {
  19. // man结构体使用结构体实体实现的接口方法
  20. var p1 People = Man{}
  21. var p2 People = &Man{}
  22. fmt.Println(p1,p2)
  23. // Woman结构体使用结构体指针实现的接口方法,在初始化时只能使用结构体指针
  24. 初始化是只能使用
  25. var w People = &Woman{}
  26. fmt.Println(w)
  27. }

image.png

  1. 6

    空接口

  2. 空接口底层结构体

    1. type eface struct {
    2. _type *_type
    3. data unsafe.Pointer
    4. }
  3. 空接口值

    • runtime.eface结构体
    • 空接口底层不是普通接口
    • 空接口值可以承载任何数据

      总结

  4. go的隐式接口更加方便系统的拓展和重构

  5. 结构体和指针都可以实现接口
  6. 空接口可以承载任何类型的数据

    nil,空接口和空结构体的区别

    nil

    nil的底层位于 builtin内,是go语言最基础的包

    1. // nil是一下6中类型的零值(初始值)
    2. // pointer, channel, func, interface, map, or slice type.
    3. var nil Type // 类型可能是一个指针,通道、函数,接口,map,或切片类型
  7. nil是空,并不一定是空指针

  8. nil是一个变量,其类型可能是一个指针,通道,函数,接口,map,切片,是这6种类型的零值

    1. var a *int
    2. fmt.Println(a==nil)//true
    3. var b map[int]int
    4. fmt.Println(b==nil)//true
  9. nil是一个有类型的变量,每种类型的nil是不同的,无法比较

image.png


  1. 空结构体

  2. 空结构体是go中非常特殊的类型

  3. 空结构体的值不是nil
  4. 空结构体的指针也不是nil,但是都相同(zeroBase)

    空接口

  5. 空接口不一定是nil接口

  6. 空接口的两个属性为nil才为nil接口

    1. var a *int
    2. fmt.Println(a==nil)//true
    3. var b interface{}
    4. fmt.Println(b==nil)//true
    5. // 把空指针a赋值给空接口b后,空接口的结构体的属性 _type就会有值,
    6. b = a
    7. fmt.Println(b==nil)//false

    总结

  7. nil是多个类型的零值或空值

  8. 空结构体的指针和值都不是nil
  9. 空接口零值是nil,一旦有了类型信息就不是nil

    内存对齐

    结构体对齐

  10. 结构体对齐分为内部对齐和结构体之间对齐

  11. 内部对齐:考虑成员大小成员的对齐系数
  12. 结构体长度填充:考虑自身对齐系数和系统字长

    结构体内部对齐

  13. 指结构体内部成员的相对位置(偏移量)

  14. 每个成员的偏移量:自身大小与其对齐系数较小值的倍数
  15. 获取类型对齐系数 ```go func main() { // unsafe.Sizeof()获取大小,unsafe.Alignof()获取对齐系数 fmt.Printf(“bool szie %d, Alignof: %d \n”,unsafe.Sizeof(bool(false)),unsafe.Alignof(bool(false))) fmt.Printf(“int szie %d, Alignof: %d \n”,unsafe.Sizeof(int(0)),unsafe.Alignof(int(0))) fmt.Printf(“int8 szie %d, Alignof: %d \n”,unsafe.Sizeof(int8(0)),unsafe.Alignof(int8(0))) fmt.Printf(“int16 szie %d, Alignof: %d \n”,unsafe.Sizeof(int16(0)),unsafe.Alignof(int16(0))) fmt.Printf(“int32 szie %d, Alignof: %d \n”,unsafe.Sizeof(int32(0)),unsafe.Alignof(int32(0))) fmt.Printf(“int64 szie %d, Alignof: %d \n”,unsafe.Sizeof(int64(0)),unsafe.Alignof(int64(0))) fmt.Printf(“string szie %d, Alignof: %d \n”,unsafe.Sizeof(string(“0”)),unsafe.Alignof(string(0)))

bool szie 1, Alignof: 1
int szie 8, Alignof: 8 int8 szie 1, Alignof: 1 int16 szie 2, Alignof: 2 int32 szie 4, Alignof: 4 int64 szie 8, Alignof: 8 string szie 16, Alignof: 8

}

  1. 4. 试验
  2. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/22399957/1663495668288-e8a01020-c3eb-4b5e-a3a6-25a47eac8bc7.png#clientId=u1326e1bb-d999-4&errorMessage=unknown%20error&from=paste&height=864&id=u04031faa&originHeight=1080&originWidth=1920&originalType=binary&ratio=1&rotation=0&showTitle=false&size=517831&status=error&style=none&taskId=u04c47281-681e-439a-9ade-00375f6aaa2&title=&width=1536)
  3. ```go
  4. type Demo struct{
  5. a bool //大小为1,对齐系数为1
  6. b string //大小为16,对齐系数为8
  7. c int16//大小为2,对齐系数为2
  8. }
  1. 5

    结构体长度对齐

  2. 指的是结构体增加长度,对齐系统字长

  3. 结构体长度是最大成员长度与系统字长较小的整数倍

image.png
image.png

  1. 结构体对齐系数是其成员最大对齐系数
  2. 空结构体对齐

image.png
image.png


  1. 总结

  2. 提高内存操作效率,变量之间需要内存对齐

  3. 基本类型考虑对齐系数
  4. 结构体需要内部对齐,有需要外部填充对齐
  5. 空结构体作为最后一个成员,需要填充对齐