Go map

前言 go版本: go version go1.17.3 linux/amd64 建议阅读注释 如果没有实现过朴素的哈希表,推荐写一下leetcode 706.设计哈希映射,连最简单的哈希表都没写过就想来看实际工程实践中经过大量性能考量、折衷和优化的哈希表,还是比较困难的。 map总览 map相关的结构体定义在 runtime/map.go中: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 // A header for a Go map. type hmap struct { count int // 表示map中的键值对个数,等于len()操作返回值 flags uint8 B uint8 // B=log_2(len(buckets)) noverflow uint16 // overflow buckets 的数量的近似值 hash0 uint32 // 哈希种子,为哈希函数的结果增加随机性 buckets unsafe....

created: 2021-12-18  |  updated: 2021-12-19  |  阿秀

Go slice、扩容机制、常见用法

Slice详解 基础用法 slice定义,在runtime/slice.go中 1 2 3 4 5 type slice struct { array unsafe.Pointer len int cap int } slice拥有指针、长度、容量这几个字段,其底层是一个数组,用指针array指代 切片操作切片名[startIndex:endIndex:maxIndex],其操作的区间是[startIndex, endIndex),是左闭右开的,最多取到下标maxIndex-1处,endIndex<=maxIndex 注意与python区分: python的切片第二个冒号后是步长step, 不支持像python切片那样的支持逆置slice[::-1]以及slice[-1],逆置只能手动写for循环;访问最后一个元素只能slice[len(slice)-1] slice的5种创建方式: 1 2 3 4 5 6 var slice1 []int // nil slice slice2 := []int{1, 2, 3} slice3 := make([]int, 1, 2) // 长度为1,容量为2,长度必须小于等于容量 slice4 := *new([]int) // nil slice,注意取值运算* slice5 := slice2[1:2] slice6 := []struct{x, y int}{{-1, 0}, {1, 0}, {0, -1}, {0, 1}} 注意事项...

created: 2021-12-18  |  updated: 2021-12-19  |  阿秀

Go testing(test && benchmark)

引言 Go提供了testing包用来进行单元测试和基准测试(benchmark),使用go test指令运行对应目录下的测试用例,并输出测试结果。 单元测试 一个例子引入,文件名为some_func_test.go: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 package main import "testing" func Echo(str string) string { return str } func TestEcho(t *testing.T){ str := "来♂" if Echo(str) != str { t.Errorf("Echo fail\n") } } 在some_func_test.go文件的父级目录下打开shell,执行go test,则会运行测试用例TestEcho() 测试文件的命名格式为xxx_test.go, 如本例子为some_func_test.go 测试用例命名规则为TestXXX, 如例子中的TestEcho(t *testing.T) 测试需要用到的包是testing testing.T用于普通测试用例 testing.M用于TestMain测试用例 testing.B用于基准测试(benchmark) go test其他参数 go test -v 会显示每个测试用例的结果 go test -cover 会显示覆盖率 go test -run TestAdd 用-run指定运行的测试用例, 支持正则表达式 子测试集 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package main import ( "fmt" "testing" ) func TestWalk(t *testing....

created: 2021-12-18  |  updated: 2021-12-19  |  阿秀

Go 文件操作

os.File 在Go中,文件被抽象为File结构体 1 2 3 4 5 6 7 8 9 10 11 12 type File struct { *file // os specific } type file struct { pfd poll.FD name string dirinfo *dirInfo // nil unless directory being read nonblock bool // whether we set nonblocking mode stdoutOrErr bool // whether this is stdout or stderr appendMode bool // whether file is opened for appending } File是操作系统相关的,在linux下,File支持的方法在os/file_posix....

created: 2021-12-18  |  updated: 2021-12-19  |  阿秀

Go-channel详解以及Channel用例大全

Channel基础 核心哲学 Do not communicate by sharing memory; instead, share memory by communicating. “不要通过共享内存的方式进行通信,而是应该通过通信的方式共享内存”,channel便是这一理念的支持和体现 基本操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 var ch chan int // 零值通道,此外,通道的零值是为nil ch1 := make(chan int) // 创建非缓冲通道 close(ch1) // 关闭通道 ch2 := make(chan struct{}) // struct{}类型的通道 ch3 := make(chan int, 1) // 创建容量为1的缓冲通道 ch3 <- 1 // 往通道中写1 data, ok := <- ch2 // 从通道中读取, 根据ok可知是否成功获取值,ok可省 len(ch2) // 通道当前长度 cap(ch2) // 通道容量 for { // 使用select进行多通道操作 select { case <-ch2: // 接收操作 fmt....

created: 2021-12-18  |  updated: 2021-12-19  |  阿秀

《Go语言设计与实现》目录

intro 目录索引 01~02 03-1数组 03-2切片 03-3哈希表 03-4-字符串 04-1函数调用 04-2接口 04-3反射 05-1for和range 05-2select 05-3defer 05-4panic和recovery 05-5make和new 06-1context 06-2同步原语和锁 06-5调度器

created: 2023-04-04  |  updated: 2023-04-04  |  阿秀

《Go语言设计与实现》01~02

调试源码 ./src/make.bash 脚本会编译 Go 语言的二进制、工具链以及标准库和命令并将源代码和编译好的二进制文件移动到对应的位置上 中间代码 go在编译的过程中会经过中间代码生成阶段,Go语言编译器的中间代码具有静态单赋值(Static Single Assignment、SSA)的特性 go build -gcflags -S main.go 汇编指令的优化过程(输出html): GOSSAFUNC=main go build main.go 编译原理 抽象语法树(Abstract Syntax Tree、AST) 静态单赋值(Static Single Assignment、SSA)是中间代码的特性,如果中间代码具有静态单赋值的特性,那么每个变量就只会被赋值一次。因为 SSA 的主要作用是对代码进行优化,所以它是编译器后端的一部分 指令集 x86指令集 arm指令集 Go语言编译器的源代码在src/cmd/compile中 编译器的前端一般承担着词法分析、语法分析、类型检查和中间代码生成几部分工作,而编译器后端主要负责目标代码的生成和优化,也就是将中间代码翻译成目标机器能够运行的二进制机器码。 Go 的编译器在逻辑上可以被分成四个阶段:词法与语法分析、类型检查和 AST 转换、通用 SSA 生成和最后的机器代码生成 词法分析 语法分析 类型检查阶段对make进行改写 Go 语言的很多关键字都依赖类型检查期间的展开和改写 源文件转换为抽象语法树,对整棵树的语法进行解析并进行类型检查之后,则认为树合法,转而将语法树转换为中间代码 Go 语言的编译器入口在 src/cmd/compile/internal/gc/main.go 文件中 会调用 cmd/compile/internal/gc.parseFiles 对输入的文件进行词法与语法分析得到对应的抽象语法树 得到抽象语法树后会分九个阶段对抽象语法树进行更新和编译,就像我们在上面介绍的,抽象语法树会经历类型检查、SSA 中间代码生成以及机器码生成三个阶段: 检查常量、类型和函数的类型; 处理变量的赋值; 对函数的主体进行类型检查; 决定如何捕获变量; 检查内联函数的类型; 进行逃逸分析; 将闭包的主体转换成引用的捕获变量; 编译顶层函数; 检查外部依赖的声明; 类型检查会遍历传入节点的全部子节点,这个过程会展开和重写 make 等关键字,在类型检查会改变语法树中的一些节点,不会生成新的变量或者语法树

created: 2023-04-04  |  updated: 2023-04-04  |  阿秀

《Go语言设计与实现》03-1 数组

我们可以看出 […]T{1, 2, 3} 和 [3]T{1, 2, 3} 在运行时是完全等价的,[…]T 这种初始化方式也只是 Go 语言为我们提供的一种语法糖,当我们不想计算数组中的元素个数时可以通过这种方法减少一些工作量 在不考虑逃逸分析的情况下,如果数组中元素的个数小于或者等于 4 个,那么所有的变量会直接在栈上初始化,如果数组元素大于 4 个,变量就会在静态存储区初始化然后拷贝到栈上 (这里的上下文是数组初始化) Go 语言中可以在编译期间的静态类型检查判断数组越界: 访问数组的索引是非整数时,报错 “non-integer array index %v”; 访问数组的索引是负数时,报错 “invalid array index %v (index must be non-negative)"; 访问数组的索引越界时,报错 “invalid array index %v (out of bounds for %d-element array)"; 数组和字符串的一些简单越界错误都会在编译期间发现,例如:直接使用整数或者常量访问数组;但是如果使用变量去访问数组或者字符串时,编译器就无法提前发现错误,我们需要 Go 语言运行时阻止不合法的访问: Go 语言运行时在发现数组、切片和字符串的越界操作会由运行时的 runtime.panicIndex 和 runtime.goPanicIndex 触发程序的运行时错误并导致崩溃退出

created: 2023-04-04  |  updated: 2023-04-04  |  阿秀