golang底层数据类型原理解析map,slice,chan

Go 语言数据类型:

在 Go 编程语言中,数据类型用于声明函数、参数、返回值、定义变量,学习编程语言的基础就是把数据类型的基础理解清楚,本节课精心准备图文对golang数据类型分析(这篇文章是我录制的视频课程里面的一节课件,本着传播知识的精神发出来到博客中)。
csdn上传png图片总是失败,用的截图可能不是很清晰。等csdn修复bug。

全部数据类型列表:

type Kind uint

const (
	Invalid Kind = iota
	Bool
	Int
	Int8
	Int16
	Int32
	Int64
	Uint
	Uint8
	Uint16
	Uint32
	Uint64
	Uintptr
	Float32
	Float64
	Complex64
	Complex128
	Array
	Chan
	Func
	Interface
	Map
	Ptr
	Slice
	String
	Struct
	UnsafePointer
)

1、基础数据类型:

序号 类型和描述
1 布尔型 布尔型的值只可以是常量 true 或者 false。一个简单的例子:var b bool = true。
2 数字类型 整型 和 浮点型
3 字符串类型: 字符串就是一串固定长度的字符连接起来的字符序列。Go 的字符串是由单个字节连接起来的。Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本。
4 派生类型: 包括:(a) 指针类型(Pointer)(b) 数组类型© 结构化类型(struct)(d) 函数类型(e) 切片类型(f) 接口类型(interface)

bool型:

bool,值为true或false

整型:

Go 也有基于架构的类型,例如:int、uint 和 uintptr。

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)

浮点型:

float32 IEEE-754 32位浮点型数、float64 IEEE-754 64位浮点型数、complex64 32 位实数和虚数、complex128 64 位实数和虚数

派生类型:

var pint *int

var pbool *bool

var c1 chan string

var slice []int64

其他数字类型

byte 类似 uint8、rune 类似 int32、uint 32 或 64 位、int 与 uint 一样大小、uintptr 无符号整型,用于存放一个指针


2、复合数据类型:

序号 类型和描述
1 数组 数组类型是一片size固定的内存区域
2 slice切片 切片类型是一片size可变的内存区域
3 Map map是哈希表的引用,map类型可以写为map[K]V,其中K和V分别对应key和value。map中所有的key都有相同的类型,所有的value也有着相同的类型,但是key和value之间可以是不同的数据类型。key必须是支持==算数|逻辑运算的数据类型
4 结构体: 结构体是一种聚合的数据类型,是由零个或多个任意类型的值聚合成的实体。每个值称为结构体的成员
5 channel: channel类型是golang携程之间线程安全通讯的机制。

数组类型:

数组类型不能动态扩容,其它用法与slice比较相似,其内部结构参照slice类型的结构。

slice类型:

slice由header和body组成。header有3个变量组成:指针、实际数据长度、内存区域最大容量。

在这里插入图片描述

在这里插入图片描述

举个例子: slice:= make(int[], 4, 6) 创建一个长度为4,容量为6的int类型slice,如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TWcjxaLH-1625975123089)(C:\Users\Admin\Desktop\datatype\icon\slice3.png)]

slice在语言包中的定义:

type slice struct {
    
    
   array unsafe.Pointer
   len   int
   cap   int
}

创建切片:

func makeslice(et *_type, len, cap int) slice {
    
    
   p := mallocgc(et.size*uintptr(cap), et, true)
   return slice{
    
    p, len, cap}
}

根据容量cap*元素size,申请一块内存。mallocgc大空间(大于32kb)才会在heap堆上申请,否则在栈上分配。切片的底层就是一片指针指向的内存,也可以看作是一个动态(可以扩容的)数组。

切片扩容:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NC8fLj8R-1625975123091)(C:\Users\Admin\Desktop\datatype\icon\slice4.png)]

// slice 扩容伪代码:
{
    
    
   newcap := old.cap
   doublecap := newcap + newcap
   if cap > doublecap {
    
    
      newcap = cap
   } else {
    
    
      if old.len < 1024 {
    
    
         newcap = doublecap
      } else {
    
    
         for newcap < cap {
    
    
            newcap += newcap / 4
         }
      }
   }
   
   // 如果容量有扩容,则申请信的内存区域、把旧数据从老的内存区域 copy 到新申请的内存区域、将array 指向新申请的内存区域、标记旧的内存区域可被回收 等待gc回收
}

cap增长的策略:

  1. 如果期望大于double,新cap就等于期望;
  2. 如果当前大小小于1024,则两倍增长;
  3. 否则每次增长25%,直到满足期望。

map类型:

// A header for a Go map.
type hmap struct {
    
    
    // 元素个数,调用 len(map) 时,直接返回此值
    count     int
    flags     uint8
    
    
    B         uint8					// buckets 的对数 log_2
    
    // overflow 的 bucket 近似数
    noverflow uint16
    
    // 计算 key 的哈希的时候会传入哈希函数
    hash0     uint32
    
    buckets    unsafe.Pointer	// 指向内存的指针,可以看作是:[]bmap。   其大小为 2^B. 如果元素个数为0,就为 nil
    
    // 扩容的时候,buckets 长度会是 oldbuckets 的两倍
    oldbuckets unsafe.Pointer
    
    // 指示扩容进度,小于此地址的 buckets 迁移完成
    nevacuate  uintptr
    extra *mapextra // optional fields
}

// buckets指向的结构体
type bmap struct {
    
    
    tophash [bucketCnt]uint8				// bucketCnt值固定为8个,也就是每个bmap最大能存储8个key-value对。
}

// go编译器在编译时,会扩展bmap为如下的结构:
type bmap struct {
    
    
    topbits  [8]uint8
    keys     [8]keytype
    values   [8]valuetype
    pad      uintptr
    overflow uintptr
}

map结构图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1HMD53of-1625975123092)(C:\Users\Admin\Desktop\datatype\icon\map.png)]

例如:m1 map[string]string插入一条数据的过程如下:

insert “key1 name”:“乔布斯”

hashvalue = hash(“key1 name”)

slot = hashvalue的低8bit % len(m1),例如m1的槽位是4个,则slot = hashvalue % 4。假设slot = 2

hashvalue的高8bit这条数据应该插入到bmap中的第几个子槽。如果bmap已经写满8个,则读取overflow指向的下一个紧邻着的bmap去插入这条数据

注意:bmap中k-v的存放方式是:key0、key1、key2、key3、key4、key5、key6、key7、val0、val1、val2、val3、val4、val5、val6、val7、pads、overflow (我认为改成叫next更为合适)

关于map扩容:

观察上面的map数据结构,最理想的k-v存储方式就是:哈希1找到槽位,哈希2找到key-value。 考虑现实情况,数据不确定时不可能申请一个很大的数组作为槽位,也不可能让一个槽位下面的链表太长影响索引速度。 因此采用折中的办法,当一个链表过长之后,会对哈希进行横向扩容(增加槽位数),这就改变了哈希1的映射关系,原有的数据需要重新插入到争取的位置。 因此。我认为应该这样去设计: 发现链表太长之后先扩容,然后新插入的数据放入新的槽位。 老的数据放在原位置不动,每次在读取(我认为应该是先用新的哈希1去读取(命中率会逐步降低),如果读取不到再用老的哈希1去读取)到老的数据时,把老数据删掉然后插入新的位置,直到老的槽位数据为0,就可以释放掉老的槽位。 或者开一个go routine逐步的进行k-v数据的迁移。

结构体:

结构体类型,是基本数据类型和派生类型的组合。与其它编程语言的结构体在组成上面差不多。但是引用与其它有一定的差异,会在interface和对象继承等章节穿插讲。

channel类型:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RiQZDBJb-1625975123094)(C:\Users\Admin\Desktop\datatype\icon\channel.png)]

channel 本质只是一个环形队列,有读、写索引、有互斥锁。 能够及时通知go调度器对读写channel的go routine进行调度。

channel写入数据:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VZKEeJUl-1625975123094)(C:\Users\Admin\Desktop\datatype\icon\channel-send.png)]

猜你喜欢

转载自blog.csdn.net/jacky128256/article/details/118652497