Go语言入门目录
- 第一章:基本程序结构
- 1.变量、常量以及与其他语言的差异
- 2.数据类型
- 3.运算符
- 4.条件和循环
- 5. 数组和切片
- 6.Map声明、元素访问及遍历
- 7.Map与工厂模式
- 8.字符串
- 9.Go语言的函数
- 10.行为的定义和实现
- 11.Go语言的相关接口
- 12.扩展与复用
- 13.不一样对的接口类型,一样 的多态
- 13.编写好的错误处理
- 14.构建可复用的模块(包)
- 15.协程机制
- 16.共享内存并发机制
- 17.CSP并发机制
- 18.多路选择和超时控制
- 19.channel的关闭和广播
- 20.Context与任务取消
- 21.典型并发任务
- 22.测试
- 23.反射编程
- 24.不安全编程
- 25.常用架构模式
- 26.Json解析
- 27.HTTP服务
- 28.性能分析
- 29.性能调优
- 30.别被性能锁住
- 31.GC友好的代码
- 32.高效的字符串连接
- 第二章:算法编程常用构造函数
第一章:基本程序结构
1.变量、常量以及与其他语言的差异
1.0 编写测试程序
1.源码文件以_test结尾:xxx_test.go
2.测试方法名以Test开头:func TestXXX(t *testing.T){…}
1.1 变量赋值
与其他主要编程语言的差异
- 赋值可以进行自动类型推断
- 在一个 赋值语句中可以对多个变量进行同时赋值
func TestExchange(t *testing.T) {
a:=1
b:=2
//tmp:=a
//a=b
//b=tmp
a,b=b,a
t.Log(a,b)
}
1.2 常量定义
与其他主要编程语言的差异
快速设置连续值
const (
Monday = iota+1
Tuesday
Wednestday
)
const (
Readable = 1 << iota
Writable
Exectable
)
func TestConstantTry(t *testing.T) {
t.Log(Monday,Tuesday)
}
func TestConstantTry1(t *testing.T) {
a:=7 //0111
t.Log(a&Readable==Readable,a&Writable==Writable,a&Exectable==Exectable)
}
2.数据类型
类型转化
与其他主要编程语言的差异
1.Go语言不允许隐式类型转换
2.别名和原有类型也不能进行饮食类型转换
func TestImplicit(t *testing.T) {
var a int=1
var b int64
b=a
t.Log(a,b)
}
/*
.\type_test.go:8:4: cannot use a (variable of type int) as int64 value in assignment
*/
type MyInt int64
func TestImplicit(t *testing.T) {
var a int = 1
var b int64
b = int64(a)
var c MyInt
c = b
t.Log(a, b, c)
}
/*
.\type_test.go:12:6: cannot use b (variable of type int64) as MyInt value in assignment
*/
类型的预定义值
1.math.MaxInt64
2.math.MaxFloat64
3.math.MaxUint32
指针类型
与其他主要编程语言的差异
1.不支持指针运算
2.string是值类型,其默认的初始化值为空字符串,而不是nil
func TestPoint(t *testing.T) {
a := 1
aPtr := &a
aPtr = aPtr + 1
t.Log(a, aPtr)
t.Logf("%T %T", a, aPtr)
}
/*
.\type_test.go:19:9: invalid operation: aPtr + 1 (mismatched types *int and untyped int)
*/
func TestString(t *testing.T) {
var s string
t.Log("*" + s + "*") //**
t.Log(len(s)) //0
}
3.运算符
算数运算符
Go语言没有前置的++,–
比较运算符
用==比较数组
- 相同维数且含有相同个数元素的数组才可以比较
- 每个元素都相同的才相等
func TestOperator(t *testing.T) {
a := [...]int{1, 2, 3, 4}
b := [...]int{1, 3, 4, 5}
c := [...]int{1, 2, 3, 4, 5}
d := [...]int{1, 2, 3, 4}
t.Log(a == b) //false
t.Log(a == c) //invalid operation: a == c (mismatched types [4]int and [5]int)
t.Log(a == d) //true
}
逻辑运算符
位运算符
与其他主要编程语言的差异
&^ 按位清零
1 &^ 0 —— 1
1 &^ 1 —— 0
0 &^ 1 —— 0
0 &^ 0 —— 0
左右两个操作数,右操作数的位上为1,无论左操作数对应的位上是0还是1,都会被清零;如果右操作数的位上为0,左操作数对应的位值保持不变。
const (
Readable = 1 << iota
Writable
Exectable
)
func TestBitClear(t *testing.T) {
a := 7 //0111
a = a &^ Readable
a = a &^ Exectable
t.Log(a&Readable == Readable, a&Writable == Writable, a&Exectable == Exectable)
//false true false
}
4.条件和循环
循环
与其他主要编程语言的差异
Go语言仅支持循环关键字 for
//while条件循环
while(n<5)
n:=0
for n<5{
n++
fmt.Println(n)
}
//无限循环
while(true)
for{
}
if条件
与其他主要编程语言的差异
1.condition表达式结果必须为布尔值
2.支持变量赋值:
func TestIfMultiSec(t *testing.T) {
if a := 1 == 1; a {
t.Log("1==1")
}
}
switch条件
与其他主要编程语言的差异
- 条件表达式不限制为常量或者整数;
- 单个case中,可以出现多个结果选项,使用逗号分隔;
- 与C语言等规则相反,Go语言不需要用break来明确退出一个case;
- 可以不设定switch之后的条件表达式,在此种情况下,整个switch结构与多个if…else…的逻辑作用等同
func TestSwitchMultiCase(t *testing.T) {
for i := 0; i < 5; i++ {
switch i {
case 0, 2:
t.Log("Even")
case 1, 3:
t.Log("Odd")
default:
t.Log("It is not 0~3")
}
}
}
/*
=== RUN TestSwitchMultiCase
condition_test.go:15: Even
condition_test.go:17: Odd
condition_test.go:15: Even
condition_test.go:17: Odd
condition_test.go:19: It is not 0~3
--- PASS: TestSwitchMultiCase (0.00s)
PASS
*/
func TestSwitchCaseCondition(t *testing.T) {
for i := 0; i < 5; i++ {
switch {
case i%2 == 0:
t.Log("Even")
case i%2 == 1:
t.Log("Odd")
default:
t.Log("It is not 0~3")
}
}
}
5. 数组和切片
数组的声明
var a [3]int //声明并初始化为默认零值
a[0]=1
b:=[3]int{1,2,3} //声明同时初始化
c:=[2][2]int{{1,2},{3,4}} //多维数组初始化
func TestArrayInit(t *testing.T) {
var arr [3]int
arr1 := [4]int{1, 2, 3, 4}
arr2 := [...]int{1, 3, 4, 5}
arr1[1] = 5
t.Log(arr[1], arr[2])
t.Log(arr1, arr2)
}
数组元素对的遍历
func TestArrayTravel(t *testing.T) {
arr3 := [...]int{1, 2, 3, 4}
for i := 0; i < len(arr3); i++ {
t.Log(arr3[i])
}
for _, e := range arr3 {
t.Log(idx, e)
}
}
数组截取
a[开始索引(包含),结束索引(不包含)]
func TestArraySection(t *testing.T) {
arr := [...]int{1, 2, 3, 4, 5}
arrSec := arr[:3]
t.Log(arrSec)
arrSec = arr[3:]
t.Log(arrSec)
arrSec = arr[:]
t.Log(arrSec)
}
切片声明
func TestSliceInit(t *testing.T) {
var s0 []int
t.Log(len(s0), cap(s0)) //0 0
s0 = append(s0, 1)
t.Log(len(s0), cap(s0)) //1 1
s1 := []int{1, 2, 3, 4}
t.Log(len(s1), cap(s1)) //4 4
s2 := make([]int, 3, 5)
// []type, len, cap 其中len个元素会被初始为默认零值,未初始化元素不可访问
t.Log(len(s2), cap(s2)) //3 5
t.Log(s2[0], s2[1], s2[2]) //0 0 0
s2 = append(s2, 1)
t.Log(s2[0], s2[1], s2[2], s2[3]) //0 0 0 1
t.Log(len(s2), cap(s2)) //4 5
}
切片的自增长
func TestSliceGrowing(t *testing.T) {
s := []int{}
for i := 0; i < 10; i++ {
s = append(s, i)
t.Log(len(s), cap(s))
}
}
/*
slice_test.go:27: 1 1
slice_test.go:27: 2 2
slice_test.go:27: 3 4
slice_test.go:27: 4 4
slice_test.go:27: 5 8
slice_test.go:27: 6 8
slice_test.go:27: 7 8
slice_test.go:27: 8 8
slice_test.go:27: 9 16
slice_test.go:27: 10 16
*/
切片的内存共享
func TestSliceShareMemory(t *testing.T) {
year := []string{"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}
Q2 := year[3:6]
t.Log(Q2, len(Q2), cap(Q2)) //[Apr May Jun] 3 9
summer := year[5:8]
t.Log(summer, len(summer), cap(summer)) //[Jun Jul Aug] 3 7
summer[0] = "Unknow"
t.Log(Q2) //[Apr May Unknow]
t.Log(year) //[Jan Feb Mar Apr May Unknow Jul Aug Sep Oct Nov Dec]
}
数组 vs. 切片
- 容量是否可伸缩:数组容量不可伸缩
- 是否可以进行比较:相同维数相同长度的数组可比较,切片不可比较
func TestSliceCompare(t *testing.T) {
a := []int{1, 2, 3, 4}
b := []int{1, 2, 3, 4}
if a == b { //invalid operation: a == b (slice can only be compared to nil)
t.Log("equal")
}
}
6.Map声明、元素访问及遍历
Map声明
func TestInitMap(t *testing.T) {
m1 := map[int]int{1: 1, 2: 4, 3: 9}
t.Log(m1[2]) //4
t.Logf("len m1=%d", len(m1)) //len m1=3
m2 := map[int]int{}
m2[4] = 16
t.Logf("len m2=%d", len(m2)) //len m2=1
m3 := make(map[int]int, 10) //初始化容量
t.Logf("len m3=%d", len(m3)) //len m3=0
}
Map元素的访问
与其他主要编程语言的差异
在访问的key不存在时,仍会返回零值,不能通过返回nil判断元素是否存在
func TestAccessNotExistingKey(t *testing.T) {
m1 := map[int]int{}
t.Log(m1[1]) //0
m1[2] = 0
t.Log(m1[2]) //0
//主动判断一个是不存在还是其值本身就是0值
if v, ok := m1[3]; ok {
t.Logf("Key 3's value is %d", v)
} else {
t.Log("Key 3 is not exit") //Key 3 is not exit
}
}
Map的遍历
func TestTravelMap(t *testing.T) {
m1 := map[int]int{1: 1, 2: 4, 3: 9}
for k, v := range m1 {
t.Log(k, v)
}
}
7.Map与工厂模式
- Map的value可以是一个方法
- 与Go的Dock type接口方式一起,可以方便的实现单一方法对象的工厂模式
func TestMapWithFunValue(t *testing.T) {
m := map[int]func(op int) int{}
m[1] = func(op int) int {
return op
}
m[2] = func(op int) int {
return op * op
}
m[3] = func(op int) int {
return op * op * op
}
t.Log(m[1](2), m[2](2), m[3](2)) //2 4 8
}
实现Set
Go的内置集合中没有Set实现,可以map[type]bool
1.元素的唯一性
2.基本操作
1)添加元素
2)判断元素是否存在
3)删除元素
4)元素个数
func TestMapForSet(t *testing.T) {
mySet := map[int]bool{}
mySet[1] = true
n := 3
if mySet[n] {
t.Logf("%d is existing", n)
} else {
t.Logf("%d is not existing", n) //3 is not existing
}
mySet[3] = true
t.Log(len(mySet)) //2
delete(mySet, 1)
n = 1
if mySet[n] {
t.Logf("%d is existing", n)
} else {
t.Logf("%d is not existing", n) //1 is not existing
}
}
8.字符串
与其他主要编程语言的差异
- string是数据类型,不是引用或指针类型
- string是只读的byte slice,len函数表示它所包含的byte数
- string的byte数组可以存放任何数据
func TestString(t *testing.T) {
var s string
t.Log(s) //初始化为默认零值,空字符
s = "hello"
t.Log(len(s)) //5
//s[1]='3' //string是不可变的byte slice
s = "\xE4\xB8\xA5" //可以存储任何二进制数据
t.Log(s) //严
t.Log(len(s)) //3
}
Unicode UTF8
- Unicode是一种字符集
- UTF8是unicode的存储实现(转换为字节序列的规则)
func TestString(t *testing.T) {
var s string
s = "中"
t.Log(len(s)) //3 是byte的长度
c := []rune(s)
t.Log(len(c)) //1 是unicode编码字符的长度
t.Logf("中 unicode %x", c[0]) //中 unicode 4e2d
t.Logf("中 UTF8 %x", s) //中 UTF8 e4b8ad
}
编码与存储
字符 | “中” |
---|---|
Unicode | 0x4E2D |
UTF-8 | 0xE4B8AD |
string/[]byte | [0xE4,0XB8,0XAD] |
常用字符串函数 |
- strings包
- strconv包
func TestStringFn(t *testing.T) {
s := "A,B,C"
parts := strings.Split(s, ",")
for _, part := range parts {
t.Log(part)
}
t.Log(strings.Join(parts, "-")) //A-B-C
}
func TestConv(t *testing.T) {
s := strconv.Itoa(10)
t.Log("str" + s) //str10
if i, err := strconv.Atoi("10"); err == nil {
t.Log(10 + i) //20
}
}
9.Go语言的函数
函数是一等公民
与其他主要编程语言的差异
- 可以有多个返回值
func returnMultiValues() (int, int) {
return rand.Intn(10), rand.Intn(20)
}
func TestFn(t *testing.T) {
a, _ := returnMultiValues()
t.Log(a)
}
函数式编程
func TestFn(t *testing.T) {
a, _ := returnMultiValues()
t.Log(a)
tsSF := timeSpent(slowFun)
t.Log(tsSF(10))
}
func timeSpent(inner func(op int) int) func(op int) int {
return func(op int) int {
start := time.Now()
ret := inner(op)
fmt.Println("time spent:", time.Since(start).Seconds())
return ret
}
}
func slowFun(op int) int {
time.Sleep(time.Second)
return op
}
/*
func_test.go:16: 1
time spent: 1.0023888
func_test.go:18: 10
--- PASS: TestFn (1.00s)
*/
- 所有参数都是值传递:slice,map,channel会有传引用的错觉
- 函数可以作为变量的值
- 函数可以作为参数和返回值
可变长参数和defer
func sum(ops ...int) int {
ret := 0
for _, op := range ops {
ret += op
}
return ret
}
func TestVarParam(t *testing.T) {
t.Log(sum(1, 2, 3, 4, 5)) //15
}
func TestDefer(t *testing.T) {
defer func() {
t.Log("Clear resource")
}()
t.Log("Started")
panic("Fatal error") //defer仍会执行
}
10.行为的定义和实现
结构体定义
实例创建及初始化
func TestCreateEmployeeObj(t *testing.T) {
e := Employee{"0", "Bob", 20}
e1 := Employee{Name: "Mike", Age: 30}
e2 := new(Employee) //返回指针
e2.Id = "2"
e2.Age = 22
e2.Name = "Rose"
t.Log(e)
t.Log(e1)
t.Log(e1.Id)
t.Log(e2)
t.Logf("e is %T", e)
t.Logf("e2 is %T", e2)
}
/*
func_test.go:67: {0 Bob 20}
func_test.go:68: { Mike 30}
func_test.go:69:
func_test.go:70: &{2 Rose 22}
func_test.go:71: e is fn_test.Employee
func_test.go:72: e2 is *fn_test.Employee
*/
行为(方法)定义
与其他主要编程语言的差异
//第一种定义方式在实例对应方法被调用时,实例的成员会进行值复制
func (e Employee) String() string {
fmt.Printf("Address is %x\n", unsafe.Pointer(&e.Name)) //Address is c00001c520
return fmt.Sprintf("ID:%s-Name:%s-Age:%d", e.Id, e.Name, e.Age)
}
func TestStructOperation(t *testing.T) {
e := Employee{"0", "Bob", 20}
fmt.Printf("Address is %x\n", unsafe.Pointer(&e.Name)) //Address is c00001c4f0
t.Log(e.String()) //ID:0-Name:Bob-Age:20
}
//通常情况下为了避免内存拷贝我们使用第二种定义方式
func (e *Employee) String() string {
fmt.Printf("Address is %x\n", unsafe.Pointer(&e.Name)) //Address is c00001c4f0
return fmt.Sprintf("ID:%s-Name:%s-Age:%d", e.Id, e.Name, e.Age)
}
func TestStructOperation(t *testing.T) {
e := Employee{"0", "Bob", 20}
fmt.Printf("Address is %x\n", unsafe.Pointer(&e.Name)) //Address is c0000884c0
t.Log(e.String()) //ID:0-Name:Bob-Age:20
}
11.Go语言的相关接口
接口与依赖
与其他主要编程语言的差异
- 接口为非入侵性,实现不依赖于接口定义
- 所以接口的定义可以包含在接口使用者包内
type Programmer interface {
WriteHelloWorld() string
}
type GoProgrammer struct {
}
func (g *GoProgrammer) WriteHelloWorld() string {
return "fmt.Println(\"Hello World\")"
}
func TestClient(t *testing.T) {
var p Programmer //定义接口变量
p = new(GoProgrammer) //实例化类型
t.Log(p.WriteHelloWorld()) //fmt.Println("Hello World")
}
接口变量
//Programmer是一个接口,GoProgrammer是它的一个实现,prog是类型是接口的变量
var prog Programmer = &GoProgrammer{}
//当prog被初始化之后有两个部分:
//1.类型:实现这个接口的类型
type GoProgrammer struct {
}
//2.数据:真正实现这个类型GoProgrammer的一个实例
&GoProgrammer{}
自定义类型
- type IntConv func(n int)int
- type MyPoint int
type IntConv func(op int) int
func timeSpent(inner IntConv) IntConv {
return func(op int) int {
start := time.Now()
ret := inner(op)
fmt.Println("time spent:", time.Since(start).Seconds())
return ret
}
}
func slowFun(op int) int {
time.Sleep(time.Second)
return op
}
func TestCustomerType(t *testing.T) {
tsSF := timeSpent(slowFun)
t.Log(tsSF(10))
}
12.扩展与复用
type Pet struct {
}
func (p *Pet) Speak() {
fmt.Printf("...")
}
func (p *Pet) SpeakTo(host string) {
p.Speak()
fmt.Println(" ", host)
}
type Dog struct {
p *Pet
}
func (d *Dog) Speak() {
//d.p.Speak()
fmt.Println("Wang!") //方法重载
}
func (d *Dog) SpeakTo(host string) {
//d.p.SpeakTo(host)
d.Speak() //方法重载
fmt.Println(" ", host) //方法重载
}
func TestDog(t *testing.T) {
dog := new(Dog)
dog.SpeakTo("hao") //... hao
//重载结果:
//Wang!
// hao
}
type Pet struct {
}
func (p *Pet) Speak() {
fmt.Printf("...")
}
func (p *Pet) SpeakTo(host string) {
p.Speak()
fmt.Println(" ", host)
}
type Dog struct {
Pet
}
func TestDog(t *testing.T) {
dog := new(Dog)
dog.SpeakTo("hao") //... hao
}
内嵌的结构类型完全没法当成继承来用,它不支持访问子类的方法、数据,不支持重载,不支持LSP
type Pet struct {
}
func (p *Pet) Speak() {
fmt.Printf("...")
}
func (p *Pet) SpeakTo(host string) {
p.Speak()
fmt.Println(" ", host)
}
type Dog struct {
Pet
}
func (d *Dog) Speak() {
fmt.Print("Wang!")
}
func TestDog(t *testing.T) {
var dog Pet = new(Dog)
//cannot use new(Dog) (value of type *Dog) as Pet value in variable declaration
dog.SpeakTo("hao")
}
13.不一样对的接口类型,一样 的多态
type Code string
type Programmer interface {
WriteHelloWorld() Code
}
type GoProgrammer struct {
}
func (g *GoProgrammer) WriteHelloWorld() Code {
return "fmt.Println(\"Hello World!\")"
}
type JavaProgrammer struct {
}
func (p *JavaProgrammer) WriteHelloWorld() Code {
return "System.out.Println(\"Hello World!\")"
}
func writeFirstProgram(p Programmer) {
fmt.Printf("%T %v\n", p, p.WriteHelloWorld())
}
func TestPolymorphism(t *testing.T) {
//goProg := new(GoProgrammer)
goProg := &GoProgrammer{}
javaProg := new(JavaProgrammer)
writeFirstProgram(goProg) //*polymorphism.GoProgrammer fmt.Println("Hello World!")
writeFirstProgram(javaProg) //*polymorphism.JavaProgrammer System.out.Println("Hello World!")
}
空接口与断言
- 空接口可以表示任何类型
- 通过断言来将空接口转换为制定类型
v,ok:=p.(int) //ok=true 时为转换成功
func DoSomething(p interface{}) {
switch v := p.(type) {
case int:
fmt.Println("Integer ", v)
case string:
fmt.Println("String ", v)
default:
fmt.Println("Unkown Type")
}
//if i, ok := p.(int); ok {
// fmt.Println("Integer ", i)
// return
//}
//if i, ok := p.(string); ok {
// fmt.Println("String ", i)
// return
//}
//fmt.Println("Unkown Type")
}
func TestEmptyInterfaceAssertion(t *testing.T) {
DoSomething(10) //Integer 10
DoSomething("10") //String 10
}
Go接口最佳实践
- 倾向于使用小的接口定义,很多接口只包含一个方法
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
- 较大的接口定义,可以由多个小接口定义组合而成
type ReadWriter interface {
Reader
Writer
}
- 只依赖于必要功能的最小接口
func StoreData(reader Reader) error {
...
}
13.编写好的错误处理
Go的错误机制
与其他主要编程语言的差异
- 没有异常机制
- error类型实现了error接口
type error interface {
Error() string
}
- 可以通过errors.New来快速创建错误实例
errors.New("n must be in the range [0,100]")
panic
- panic用于不可恢复的错误
- panic退出前会执行defer指定的内容
panic vs. os.Exit - os.Exit退出时不会调用defer指定的函数
- os.Exit退出时不输出当前调用栈信息
func TestPanicVsExit(t *testing.T) {
defer func() {
fmt.Println("Finally!")
}()
fmt.Println("Start")
panic(errors.New("Something wrong!"))
/*
Start
Finally!
--- FAIL: TestPanicVsExit (0.00s)
panic: Something wrong! [recovered]
panic: Something wrong!
*/
}
func TestPanicVsExit(t *testing.T) {
defer func() {
fmt.Println("Finally!")
}()
fmt.Println("Start")
os.Exit(-1)
/*
Start
*/
}
recover
//Java
try{
...
}catch(Throwable t){
}
//C++
try{
...
}catch(...){
}
defer func() {
if err := recover(); err != nil {
//恢复错误
}
}()
当心!recover成为恶魔
- 形成僵尸服务进程,导致health check失效
- "Let it Crash!"往往是我们恢复不确定性错误的最好方法
func TestPanicVsExit(t *testing.T) {
defer func() {
if err := recover(); err != nil {
fmt.Println("recovered from ", err)
}
}()
fmt.Println("Start")
panic(errors.New("Something wrong!"))
/*
Start
recovered from Something wrong!
--- PASS: TestPanicVsExit (0.00s)
PASS
*/
}
14.构建可复用的模块(包)
package
- 基本复用模块单元(以首字母大写来表明可被包外代码访问)
- 代码的package可以和所在的目录不一致
- 同一目录里的Go代码的package要保持一致
//src/ch12/series/my_series.go
package series
func GetFib(n int) []int {
ret := []int{1, 1}
for i := 2; i < n; i++ {
ret = append(ret, ret[i-1]+ret[i-2])
}
return ret
}
//src/ch12/client/package_test.go
package client
import (
"goprojects/golang-beginners-guide/src/ch12/series"
"testing"
)
func TestPackage(t *testing.T) {
t.Log(series.GetFib(5))
}
init方法
- 在main被执行前,所有依赖的package的init方法都会被执行
- 不同包的init函数按照包导入的依赖关系决定执行顺序
- 每个包可以有多个init函数
- 包的每个源文件也可以有多个init函数,这点比较特殊
package series
import "fmt"
func GetFib(n int) []int {
ret := []int{1, 1}
for i := 2; i < n; i++ {
ret = append(ret, ret[i-1]+ret[i-2])
}
return ret
}
func Square(n int) int {
return n*n
}
func init() {
fmt.Println("init1")
}
func init() {
fmt.Println("init2")
}
package client
import (
"goprojects/golang-beginners-guide/src/ch12/series"
"testing"
)
func TestPackage(t *testing.T) {
t.Log(series.GetFib(5))
t.Log(series.Square(2))
}
/*
init1
init2
=== RUN TestPackage
package_test.go:9: [1 1 2 3 5]
package_test.go:10: 4
--- PASS: TestPackage (0.00s)
PASS
*/
package下载
- 通过go get 来获取远程依赖(go get -u 强制从网络更新远程依赖)
- 注意代码在Github上的组织形式,以适应go get(直接以代码路径开始,不要有src)
常用的依赖管理工具
- godep
- glide
- dep
15.协程机制
Thead vs. Groutine
- 创建时默认的stack的大小
- JDK5以后Java Thread stack默认为1M
- Groutine的Stack初始化大小为2K
- 和KSE(Kernel Space Entity)的对应关系
- Java Thread是1:1
- Groutine是M:N
func TestGroutine(t *testing.T) {
for i := 0; i < 10; i++ {
go func(i int) {
fmt.Printf("%d ",i) //0 6 7 8 5 2 1 3 9 4
}(i)
}
time.Sleep(time.Millisecond*50)
}
go的方法调用传递的都是值传递,i被复制了一份,每个协程中所用的i的地址是不一样的,没有竞争关系,是可以正确执行的。
错误写法
func TestErrGroutine(t *testing.T){
for i := 0; i < 10; i++ {
go func() {
fmt.Printf("%d ",i)
}()
}
time.Sleep(time.Millisecond*50) //10 10 10 10 10 10 10 10 10 10
}
因为i这个 变量在test所在的协程以及for遍历中启动的其他的协程中被共享了,存在竞争条件。需要锁的机制来完成。
16.共享内存并发机制
func TestCounter(t *testing.T) {
counter:=0
for i := 0; i < 5000; i++ {
go func() {
counter++
}()
}
time.Sleep(1*time.Second)
t.Logf("counter=%d",counter) //counter=4978
}
因为counter在不同协程中做自增,导致并发的竞争条件,不是线程安全的程序。需要对共享内存做锁的保护。
func TestCounter(t *testing.T) {
var mut sync.Mutex
counter:=0
for i := 0; i < 5000; i++ {
go func() {
defer func() {
mut.Unlock()
}()
mut.Lock()
counter++
}()
}
time.Sleep(1*time.Second)
t.Logf("counter=%d",counter) //counter=5000
}
WaitGroup
WaitGroup同步各个线程之间的方法,只有当所有等待的任务都完成之后才能继续往下执行。
func TestCounter(t *testing.T) {
var mut sync.Mutex
counter:=0
for i := 0; i < 5000; i++ {
go func() {
defer func() {
mut.Unlock()
}()
mut.Lock()
counter++
}()
}
//time.Sleep(1*time.Second)
t.Logf("counter=%d",counter) //counter=4990
}
睡眠时间注释掉后,外面test协程执行的速度超过了for中所有协程执行完的速度。导致for中协程还没执行完就退掉了。
func TestCounter(t *testing.T) {
var mut sync.Mutex
var wg sync.WaitGroup
counter:=0
for i := 0; i < 5000; i++ {
wg.Add(1)
go func() {
defer func() {
mut.Unlock()
}()
mut.Lock()
counter++
wg.Done()
}()
}
wg.Wait()
t.Logf("counter=%d",counter) //counter=5000
}
17.CSP并发机制
CSP vs. Actor
- 和Actor的直接通讯不同,CSP模式则是通过Channel进行通讯的,更松耦合一些。
- Go中channel是有容量限制并且独立于处理Groutine,而如Erlang,Actor模式中的messagebox容量是无限。
- Golang协程会主动从channel中去处理channel传过来的消息,Actor模式进程总是被动地处理消息。
channel机制
串行任务处理
func service() string {
time.Sleep(time.Millisecond * 50)
return "Done"
}
func otherTask() {
fmt.Println("working on something else")
time.Sleep(time.Millisecond * 100)
fmt.Println("Task is done")
}
func TestService(t *testing.T) {
fmt.Println(service())
otherTask()
}
/*
Done
working on something else
Task is done
--- PASS: TestService (0.17s)
*/
改造为异步处理任务(无缓冲通道)
func AsyncService() chan string {
retCh := make(chan string)
go func() {
ret := service()
fmt.Println("return result.")
retCh <- ret
fmt.Println("service exited.")
}()
return retCh
}
func TestAsynService(t *testing.T) {
retCh := AsyncService()
otherTask()
fmt.Println(<-retCh)
}
/*
working on something else
return result.
Task is done
Done
service exited.
--- PASS: TestAsynService (0.11s)
*/
更高效的异步处理任务(有缓冲通道)
func AsyncService() chan string {
retCh := make(chan string, 1)
go func() {
ret := service()
fmt.Println("return result.")
retCh <- ret
fmt.Println("service exited.")
}()
return retCh
}
func TestAsynService(t *testing.T) {
retCh := AsyncService()
otherTask()
fmt.Println(<-retCh)
}
/*
working on something else
return result.
service exited.
Task is done
Done
--- PASS: TestAsynService (0.10s)
*/
18.多路选择和超时控制
多渠道选择
超时控制
func TestSelect(t *testing.T) {
select {
case ret := <-AsyncService():
t.Logf(ret)
case <-time.After(time.Millisecond * 10):
t.Error("time out")
}
}
19.channel的关闭和广播
channel的关闭
- 向关闭的channel发送数据,会导致panic
- v,ok<-ch;ok为bool,true表示正常接受,false表示通道关闭
- 所有的channel接收者都会在channel关闭时,立即从阻塞等待中返回且上述ok值为false。这个广播机制常被利用,进行向多个订阅者发送信号。如:退出信号
func dataProducer(ch chan int, wg *sync.WaitGroup) {
go func() {
for i := 0; i < 10; i++ {
ch <- i
}
close(ch)
wg.Done()
}()
}
func dataReceiver(ch chan int, wg *sync.WaitGroup) {
go func() {
for {
if data, ok := <-ch; ok {
fmt.Println(data)
} else {
break
}
}
wg.Done()
}()
}
func TestCloseChannel(t *testing.T) {
var wg sync.WaitGroup
ch := make(chan int)
wg.Add(1)
dataProducer(ch, &wg)
wg.Add(1)
dataReceiver(ch, &wg)
wg.Add(1)
dataReceiver(ch, &wg)
wg.Wait()
}
任务的取消
func isCancelled(cancelChan chan struct{}) bool {
select {
case <-cancelChan:
return true
default:
return false
}
}
func cancel_1(cancelChan chan struct{}) {
cancelChan <- struct{}{}
}
func cancel_2(cancelChan chan struct{}) {
close(cancelChan)
}
func TestCancel(t *testing.T) {
cancelChan := make(chan struct{}, 0)
for i := 0; i < 5; i++ {
go func(i int, cancelCh chan struct{}) {
for {
if isCancelled(cancelChan) {
break
}
time.Sleep(time.Millisecond * 5)
}
fmt.Println(i, "Cancelled")
}(i, cancelChan)
}
//cancel_1(cancelChan) //4 Cancelled 只取消掉一个任务
cancel_2(cancelChan) //广播处理,取消掉所有任务
/*
4 Cancelled
2 Cancelled
3 Cancelled
1 Cancelled
0 Cancelled
*/
time.Sleep(time.Second * 1)
}
20.Context与任务取消
关联任务的取消
Context
- 根Context:通过context.Background()创建
- 子Context:context.WithCancel(parentContext)创建
- ctx,cancel:=context.WithCancel(context.Background())
- 当前Context被取消时,基于它的子context都会被取消
- 接收取消通知<-ctx.Done()
使用context实现channel的任务取消
func isCancelled(ctx context.Context) bool {
select {
case <-ctx.Done():
return true
default:
return false
}
}
func TestCancel(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
for i := 0; i < 5; i++ {
go func(i int, ctx context.Context) {
for {
if isCancelled(ctx) {
break
}
time.Sleep(time.Millisecond * 5)
}
fmt.Println(i, "Cancelled")
}(i, ctx)
}
cancel()
time.Sleep(time.Second * 1)
}
/*
4 Cancelled
2 Cancelled
3 Cancelled
0 Cancelled
1 Cancelled
*/
21.典型并发任务
只运行一次
单例模式(懒汉式,线程安全)
Go语言实现单例模式(懒汉式,线程 安全)
var once sync.Once
var obj *SingletonObj
func GetSingletonObj() *SingletonObj {
once.Do(func() {
fmt.Println("Create Singleton obj.")
obj = &SingletonObj{}
})
return obj
}
范例如下:
type Singleton struct {
}
var singleInstance *Singleton
var once sync.Once
func GetSingletonObj() *Singleton {
once.Do(func() {
fmt.Println("Create obj")
singleInstance = new(Singleton)
})
return singleInstance
}
func TestGetSingletonObj(t *testing.T) {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
obj := GetSingletonObj()
fmt.Printf("%x\n", unsafe.Pointer(obj))
wg.Done()
}()
}
wg.Wait()
}
/*
Create obj
beba40
beba40
beba40
beba40
beba40
beba40
beba40
beba40
beba40
beba40
*/
仅需任意任务完成
func runTask(id int) string {
time.Sleep(10 * time.Millisecond)
return fmt.Sprintf("The result is from %d", id)
}
func FirstResponse() string {
numOfRunner := 10
//ch := make(chan string)
//无缓冲通道,没人接收消息会阻塞,直到有人接收消息为止,这会导致协程泄露
/*
first_response_test.go:28: Before: 2
first_response_test.go:29: The result is from 6
first_response_test.go:31: After: 11
*/
ch := make(chan string, numOfRunner)
/*
first_response_test.go:34: Before: 2
first_response_test.go:35: The result is from 1
first_response_test.go:37: After: 2
*/
for i := 0; i < numOfRunner; i++ {
go func(i int) {
ret := runTask(i)
ch <- ret
}(i)
}
return <-ch
}
func TestFirstResponse(t *testing.T) {
t.Log("Before:", runtime.NumGoroutine()) //输出当前系统中的协程数
t.Log(FirstResponse())
time.Sleep(time.Second * 1)
t.Log("After:", runtime.NumGoroutine())
}
CSP机制实现所有任务完成
func runTask(id int) string {
time.Sleep(10 * time.Millisecond)
return fmt.Sprintf("The result is from %d", id)
}
func AllResponse() string {
numOfRunner := 10
ch := make(chan string, numOfRunner)
for i := 0; i < numOfRunner; i++ {
go func(i int) {
ret := runTask(i)
ch <- ret
}(i)
}
finalRet := ""
for j := 0; j < numOfRunner; j++ {
finalRet += <-ch + "\n"
}
return finalRet
}
func TestFirstResponse(t *testing.T) {
t.Log("Before:", runtime.NumGoroutine()) //输出当前系统中的协程数
t.Log(AllResponse())
time.Sleep(time.Second * 1)
t.Log("After:", runtime.NumGoroutine())
}
/*
util_all_done_test.go:32: Before: 2
util_all_done_test.go:33: The result is from 3
The result is from 2
The result is from 1
The result is from 9
The result is from 8
The result is from 5
The result is from 6
The result is from 0
The result is from 4
The result is from 7
util_all_done_test.go:35: After: 2
*/
对象池
使用buffered channel实现对象池
type ReusableObj struct {
}
type ObjPool struct {
bufChan chan *ReusableObj //用于缓冲可重用对象
}
func NewObjPool(numOfObj int) *ObjPool {
objPool := ObjPool{}
objPool.bufChan = make(chan *ReusableObj, numOfObj)
for i := 0; i < numOfObj; i++ {
objPool.bufChan <- &ReusableObj{}
}
return &objPool
}
func (p *ObjPool) GetObj(timeout time.Duration) (*ReusableObj, error) {
select {
case ret := <-p.bufChan:
return ret, nil
case <-time.After(timeout):
return nil, errors.New("time out")
}
}
func (p *ObjPool) ReleaseObj(obj *ReusableObj) error {
select {
case p.bufChan <- obj:
return nil
default:
return errors.New("overflow")
}
}
func TestObjPool(t *testing.T) {
pool := NewObjPool(10)
for i := 0; i < 11; i++ {
if v, err := pool.GetObj(time.Second * 1); err != nil {
t.Error(err)
} else {
fmt.Printf("%T\n", v)
/*
*obj_pool.ReusableObj
*obj_pool.ReusableObj
*obj_pool.ReusableObj
*obj_pool.ReusableObj
*obj_pool.ReusableObj
*obj_pool.ReusableObj
*obj_pool.ReusableObj
*obj_pool.ReusableObj
*obj_pool.ReusableObj
*obj_pool.ReusableObj
obj_pool_test.go:13: time out
Done
*/
if err := pool.ReleaseObj(v); err != nil {
t.Error(err)
}
}
}
fmt.Println("Done")
}
sync.Pool对象缓存
sync.Pool对象的获取
- 尝试从私有对象获取
- 私有对象不存在,尝试从当前Processor的共享池获取
- 如果当前Processor共享池也是空的,那么就尝试去其他Processor的共享池获取
- 如果所有子池都是空的,最后就用用户指定 的New函数产生一个新的对象返回
sync.Pool对象的放回
- 如果私有对象不存在则保存为私有对象
- 如果私有对象存在,放入当前Processor子池的共享池中
使用sync.Pool
pool := &sync.Pool{
New: func() interface{} {
return 0
},
}
arry := pool.Get().(int)
...
pool.Put(10)
应用举例
func TestSyncPool(t *testing.T) {
pool := &sync.Pool{New: func() interface{} {
fmt.Println("Create a new object.")
return 100
}}
v := pool.Get().(int)
fmt.Println(v)
pool.Put(3)
v1, _ := pool.Get().(int)
fmt.Println(v1)
}
/*
Create a new object.
100
3
*/
sync.Pool对象的声明周期
- GC会清除sync.Pool缓存的对象
- 对象的缓存有效期为下一次GC之前
func TestSyncPool(t *testing.T) {
pool := &sync.Pool{New: func() interface{} {
fmt.Println("Create a new object.")
return 100
}}
v := pool.Get().(int)
fmt.Println(v)
pool.Put(3)
runtime.GC() //GC会清除sync.Pool中缓存的对象
v1, _ := pool.Get().(int)
fmt.Println(v1)
}
/*
Create a new object.
100
Create a new object.
100
*/
多协程下池对的对象缓存机制
func TestSyncPoolInMultiGroutine(t *testing.T) {
pool := &sync.Pool{New: func() interface{} {
fmt.Println("Create a new object.")
return 10
}}
pool.Put(100)
pool.Put(100)
pool.Put(100)
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(id int) {
fmt.Println(pool.Get())
wg.Done()
}(i)
}
wg.Wait()
}
/*
100
100
100
Create a new object.
10
Create a new object.
Create a new object.
10
Create a new object.
10
10
Create a new object.
10
Create a new object.
10
Create a new object.
10
*/
sync.Poo.总结
- 适用于通过复用,降低复杂对象的创建和GC代价
- 协程安全,会有锁的开销
- 生命周期受GC影响,不适合于做连接池等,需要自己管理生命周期的资源的池化
22.测试
内置单元测试框架
- Fail,Error:该测试失败,该测试继续,其他测试继续执行
- FailNow,Fatal:该测试失败,该测试中止,其他测试继续执行
- 代码覆盖率(go test -v -cover)
- 断言
Benchmark
func BenchmarkConcatStringByAdd(b *testing.B) {
//与性能测试无关的代码
b.ResetTimer()
for i := 0; i < b.N; i++ {
//测试代码
}
b.StopTimer()
//与性能测试无关的代码
}
func TestConcatStringByAdd(t *testing.T) {
assertion := assert.New(t)
elems := []string{"1", "2", "3", "4", "5"}
ret := ""
for _, elem := range elems {
ret += elem
}
assertion.Equal("12345", ret)
}
func TestConcatStringByBytesBuffer(t *testing.T) {
assertion := assert.New(t)
var buf bytes.Buffer
elems := []string{"1", "2", "3", "4", "5"}
for _, elem := range elems {
buf.WriteString(elem)
}
assertion.Equal("12345", buf.String())
}
func BenchmarkConcatStringByAdd(b *testing.B) {
//与性能测试无关的代码
elems := []string{"1", "2", "3", "4", "5"}
b.ResetTimer()
for i := 0; i < b.N; i++ {
//测试代码
ret := ""
for _, elem := range elems {
ret += elem
}
}
b.StopTimer()
//与性能测试无关的代码
}
/*
goos: windows
goarch: amd64
pkg: goprojects/golang-beginners-guide/src/ch15/benchmark
cpu: AMD Ryzen 7 4800U with Radeon Graphics
BenchmarkConcatStringByAdd
BenchmarkConcatStringByAdd-16 10967497 110.8 ns/op
PASS
*/
func BenchmarkConcatStringByBytesBuffer(b *testing.B) {
elems := []string{"1", "2", "3", "4", "5"}
b.ResetTimer()
for i := 0; i < b.N; i++ {
var buf bytes.Buffer
for _, elem := range elems {
buf.WriteString(elem)
}
}
b.StopTimer()
}
/*
goos: windows
goarch: amd64
pkg: goprojects/golang-beginners-guide/src/ch15/benchmark
cpu: AMD Ryzen 7 4800U with Radeon Graphics
BenchmarkConcatStringByBytesBuffer
BenchmarkConcatStringByBytesBuffer-16 18415782 68.19 ns/op
PASS
*/
go test 的使用补充
参考链接
基础用法
源文件和测试文件放在同一目录下,测试文件以 _test 结尾,这个是固定格式,使用 go build 进行编译时,_test 文件不会编译。
- 每个测试函数需要以 Test 为前缀,例如:Test_Division, TestDivision
- 每个性能测试函数需要以 Benchmark 为前缀,例如:Benchmark_Division, BenchmarkDivision
go test 参数
打印测试函数的所有细节 -v
go test -v
benchmark> go test -v
=== RUN TestConcatStringByAdd
--- PASS: TestConcatStringByAdd (0.00s)
=== RUN TestConcatStringByBytesBuffer
--- PASS: TestConcatStringByBytesBuffer (0.00s)
PASS
ok goprojects/golang-beginners-guide/src/ch15/benchmark 2.361s
指定运行某一测试函数 -run
go test -run regexp
只运行 regexp 匹配的函数
PS D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch15\benchmark> go test -run="TestConcatStringByBytesBuffer"
PASS
ok goprojects/golang-beginners-guide/src/ch15/benchmark 0.043s
性能测试 -bench
go test -bench regexp
go test -bench . "."表示执行包下所有的性能测试函数
PS D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch15\benchmark> go test -bench .
goos: windows
goarch: amd64
pkg: goprojects/golang-beginners-guide/src/ch15/benchmark
cpu: AMD Ryzen 7 4800U with Radeon Graphics
BenchmarkConcatStringByAdd-16 11043835 112.1 ns/op
BenchmarkConcatStringByBytesBuffer-16 18990766 64.75 ns/op
PASS
ok goprojects/golang-beginners-guide/src/ch15/benchmark 2.687s
内存测试 -benchmem
go test -bench . -benchmem
PS D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch15\benchmark> go test -bench="." -benchmem
goos: windows
goarch: amd64
pkg: goprojects/golang-beginners-guide/src/ch15/benchmark
cpu: AMD Ryzen 7 4800U with Radeon Graphics
BenchmarkConcatStringByAdd-16 11211181 110.8 ns/op 16 B/op 4 allocs/op
BenchmarkConcatStringByBytesBuffer-16 19527594 65.43 ns/op 64 B/op 1 allocs/op
PASS
ok goprojects/golang-beginners-guide/src/ch15/benchmark 2.751s
“16 B/op”表示每一次调用需要分配 16 个字节,“1 allocs/op”表示每一次调用有一次分配。
自定义测试时间 -benchtime
go test -v -bench=. -benchtime=5s
基准测试框架的默认测试时间为1s,可以通过 -benchtime 参数来指定测试时间。
开启覆盖测试 -cover
go test -cover
23.反射编程
reflect.TypeOf vs. reflect.ValueOf
- reflect.TypeOf 返回类型(reflect.Type)
- reflect.ValueOf 返回值(reflect.Value)
- 可以从reflect.Value获得类型
- 通过kind来判断类型
判断类型——Kind()
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
Pointer
Slice
String
Struct
UnsafePointer
)
func CheckType(v interface{}) {
t := reflect.TypeOf(v)
switch t.Kind() {
case reflect.Float32, reflect.Float64:
fmt.Println("Float")
case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64:
fmt.Println("Integer")
default:
fmt.Println("Unknown", t)
}
}
func TestBasicType(t *testing.T) {
var f float64 = 12
CheckType(f) //Float
CheckType(&f) //Unknown *float64
}
func TestTypeAndValue(t *testing.T) {
var f int64 = 10
t.Log(reflect.TypeOf(f), reflect.ValueOf(f)) //int64 10
t.Log(reflect.ValueOf(f).Type()) //int64
}
利用反射编写灵活的代码
按名字访问结构的成员
reflect.ValueOf(*e).FieldByName("Name")
按名字访问结构的方法
reflect.ValueOf(e).MethodByName("UpdateAge").Call([]reflect.Value{reflect.ValueOf(1)})
type Employee struct {
EmployeeID string
Name string
Age int
}
func (e *Employee) UpdateAge(newVal int) {
e.Age = newVal
}
type Customer struct {
CookieID string
Name string
Age int
}
func TestInvokeByName(t *testing.T) {
e := &Employee{
EmployeeID: "1",
Name: "Mike",
Age: 30,
}
//按名字获取成员
t.Logf("Name:value(%[1]v), Type(%[1]T)", reflect.ValueOf(*e).FieldByName("Name"))
//Name:value(Mike), Type(reflect.Value)
if nameField, ok := reflect.TypeOf(*e).FieldByName("Name"); !ok {
t.Error("Failed to get 'Name' field.")
} else {
t.Log("Tag:format", nameField.Tag.Get("format")) //Tag:format
}
reflect.ValueOf(e).MethodByName("UpdateAge").Call([]reflect.Value{reflect.ValueOf(1)})
t.Log("Updated Age:", e) //Updated Age: &{1 Mike 1}
}
Struct Tag
type BasicInfo struct {
Name string `json:"name"`
Age int `json:"age"`
}
type Employee struct {
EmployeeID string
Name string `format:"normal"`
Age int
}
func (e *Employee) UpdateAge(newVal int) {
e.Age = newVal
}
type Customer struct {
CookieID string
Name string
Age int
}
func TestInvokeByName(t *testing.T) {
e := &Employee{
EmployeeID: "1",
Name: "Mike",
Age: 30,
}
//按名字获取成员
t.Logf("Name:value(%[1]v), Type(%[1]T)", reflect.ValueOf(*e).FieldByName("Name"))
//Name:value(Mike), Type(reflect.Value)
if nameField, ok := reflect.TypeOf(*e).FieldByName("Name"); !ok {
t.Error("Failed to get 'Name' field.")
} else {
t.Log("Tag:format", nameField.Tag.Get("format")) //Tag:format normal
}
reflect.ValueOf(e).MethodByName("UpdateAge").Call([]reflect.Value{reflect.ValueOf(1)})
t.Log("Updated Age:", e) //Updated Age: &{1 Mike 1}
}
访问StructTag
reflect.Type和reflect.Value都有FieldByName方法,注意二者的区别
reflect.Value的FieldByName方法实现如下:
func (v Value) FieldByName(name string) Value {
v.mustBe(Struct)
if f, ok := toRType(v.typ()).FieldByName(name); ok {
return v.FieldByIndex(f.Index)
}
return Value{}
}
reflect.Type的FieldByName方法实现如下:
func (t *rtype) FieldByName(name string) (StructField, bool) {
if t.Kind() != Struct {
panic("reflect: FieldByName of non-struct type " + t.String())
}
tt := (*structType)(unsafe.Pointer(t))
return tt.FieldByName(name)
}
DeepEqual(比较切片和map)
func TestDeepEqual(t *testing.T) {
a := map[int]string{1: "one", 2: "two", 3: "three"}
b := map[int]string{1: "one", 2: "two", 3: "three"}
//t.Log(a == b) //invalid operation: a == b (map can only be compared to nil)
t.Log("a==b?", reflect.DeepEqual(a, b)) //a==b? true
s1 := []int{1, 2, 3}
s2 := []int{1, 2, 3}
s3 := []int{2, 3, 1}
//t.Log(s1 == s2) //invalid operation: s1 == s2 (slice can only be compared to nil)
t.Log("s1==s2?", reflect.DeepEqual(s1, s2)) //s1==s2? true
t.Log("s1==s3?", reflect.DeepEqual(s1, s3)) //s1==s3? false
}
万能程序
type Customer struct {
CookieID string
Name string
Age int
}
func fillBySetting(st interface{}, settings map[string]interface{}) error {
if reflect.TypeOf(st).Kind() != reflect.Ptr {
return errors.New("The first param should be a pointer to the struct type. ")
}
if reflect.TypeOf(st).Elem().Kind() != reflect.Struct {
return errors.New("The first param should be a pointer to ths struct type.")
}
if settings == nil {
return errors.New("settings is nil.")
}
var (
field reflect.StructField
ok bool
)
for k, v := range settings {
if field, ok = (reflect.ValueOf(st)).Elem().Type().FieldByName(k); !ok {
continue
}
if field.Type == reflect.TypeOf(v) {
vstr := reflect.ValueOf(st)
vstr = vstr.Elem()
vstr.FieldByName(k).Set(reflect.ValueOf(v))
}
}
return nil
}
func TestFillNameAndAge(t *testing.T) {
settings := map[string]interface{}{"Name": "Mike", "Age": 30}
e := Employee{}
if err := fillBySetting(&e, settings); err != nil {
t.Fatal(err)
}
t.Log(e)//{ Mike 30}
c := new(Customer)
if err := fillBySetting(c, settings); err != nil {
t.Fatal(err)
}
t.Log(*c)//{ Mike 30}
}
24.不安全编程
不安全行为的危险性
func TestUnsafe(t *testing.T) {
i := 10
f := *(*float64)(unsafe.Pointer(&i))
t.Log(unsafe.Pointer(&i)) //0xc00000a3a8
t.Log(f) //5e-323
}
**合理的类型转换 **
type MyInt int
func TestConvert(t *testing.T) {
a := []int{1, 2, 3, 4}
b := *(*[]MyInt)(unsafe.Pointer(&a))
t.Log(b) //[1 2 3 4]
}
// 原子类型操作
func TestAtomic(t *testing.T) {
var shareBufPtr unsafe.Pointer
writeDataFn := func() {
data := []int{}
for i := 0; i < 10; i++ {
data = append(data, i)
}
atomic.StorePointer(&shareBufPtr, unsafe.Pointer(&data))
}
readDataFn := func() {
data := atomic.LoadPointer(&shareBufPtr)
fmt.Println(data, *(*[]int)(data))
}
var wg sync.WaitGroup
writeDataFn()
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
for i := 0; i < 5; i++ {
writeDataFn()
time.Sleep(time.Microsecond * 100)
}
wg.Done()
}()
wg.Add(1)
go func() {
for i := 0; i < 5; i++ {
readDataFn()
time.Sleep(time.Microsecond * 100)
}
wg.Done()
}()
}
wg.Wait()
}
/*
0xc0000080f0 [0 1 2 3 4 5 6 7 8 9]
0xc000008210 [0 1 2 3 4 5 6 7 8 9]
0xc000008240 [0 1 2 3 4 5 6 7 8 9]
0xc000008288 [0 1 2 3 4 5 6 7 8 9]
0xc000008288 [0 1 2 3 4 5 6 7 8 9]
0xc000200018 [0 1 2 3 4 5 6 7 8 9]
0xc000200018 [0 1 2 3 4 5 6 7 8 9]
0xc000200018 [0 1 2 3 4 5 6 7 8 9]
0xc000200018 [0 1 2 3 4 5 6 7 8 9]
0xc0000082b8 [0 1 2 3 4 5 6 7 8 9]
0xc0000082b8 [0 1 2 3 4 5 6 7 8 9]
0xc000100030 [0 1 2 3 4 5 6 7 8 9]
0xc000100030 [0 1 2 3 4 5 6 7 8 9]
0xc000008330 [0 1 2 3 4 5 6 7 8 9]
0xc000008330 [0 1 2 3 4 5 6 7 8 9]
0xc000100078 [0 1 2 3 4 5 6 7 8 9]
0xc000100078 [0 1 2 3 4 5 6 7 8 9]
0xc0001860f0 [0 1 2 3 4 5 6 7 8 9]
0xc0001860f0 [0 1 2 3 4 5 6 7 8 9]
0xc000200078 [0 1 2 3 4 5 6 7 8 9]
0xc000200078 [0 1 2 3 4 5 6 7 8 9]
0xc000200078 [0 1 2 3 4 5 6 7 8 9]
0xc000200078 [0 1 2 3 4 5 6 7 8 9]
0xc0001000c0 [0 1 2 3 4 5 6 7 8 9]
0xc000008360 [0 1 2 3 4 5 6 7 8 9]
*/
25.常用架构模式
pipe-filter架构
- 非常适合于数据处理及数据分析系统
- Filter封装数据处理的功能
- Pipe用于连接Filter传递数据或者在异步处理过程中缓冲数据(进程内同步调用时,pipe演变为数据在方法调用间传递)
- 松耦合:Filter只跟数据(格式)耦合
Filter和组合模式
微内核架构(Micro-Kernel)
特点
- 易于扩展
- 错误隔离
- 保持架构一致性
要点 - 内核包含公共流程或通用逻辑
- 将可变或可扩展部分规划为扩展点
- 抽象扩展点行为,定义接口
- 利用插件进行可扩展
26.Json解析
内置的Json解析
利用反射实现,通过FeildTag来标识对应的json值
type BasicInfo struct {
Name string `json:"name"`
Age int `json:"age"`
}
type JobInfo struct {
Skills []string `json:"skills"`
}
type Employee struct {
BasicInfo BasicInfo `json:"basic_info"`
JobInfo JobInfo `json:"job_info"`
}
var jsonStr = `{
"basic_info":{
"name":"Mike",
"age":30
},
"job_info":{
"skills":["Java","Go","C"]
}
} `
func TestEmbeddedJson(t *testing.T) {
e := new(Employee)
err := json.Unmarshal([]byte(jsonStr), e)
if err != nil {
t.Error(err)
}
fmt.Println(*e) //{{Mike 30} {[Java Go C]}}
if v, err := json.Marshal(e); err == nil {
fmt.Println(string(v)) //{"basic_info":{"name":"Mike","age":30},"job_info":{"skills":["Java","Go","C"]}}
} else {
t.Error(err)
}
}
更快的JSON解析
EasyJSON采用代码生成而非反射
安装指令:go get -u github.com/mailru/easyjson/…
使用:easyjson -all <结构定义>.go
关于easyjson的安装与使用参考【Go】EasyJson使用
var jsonStr = `{
"basic_info":{
"name":"Mike",
"age":30
},
"job_info":{
"skills":["Java","Go","C"]
}
} `
func TestEmbeddedJson(t *testing.T) {
e := new(Employee)
err := json.Unmarshal([]byte(jsonStr), e)
if err != nil {
t.Error(err)
}
fmt.Println(*e)
if v, err := json.Marshal(e); err == nil {
fmt.Println(string(v))
} else {
t.Error(err)
}
}
func TestEasyJson(t *testing.T) {
e := Employee{}
e.UnmarshalJSON([]byte(jsonStr))
fmt.Println(e)
if v, err := e.MarshalJSON(); err != nil {
t.Error(err)
} else {
fmt.Println(string(v))
}
}
func BenchmarkEmbeddedJson(b *testing.B) {
b.ResetTimer()
e := new(Employee)
for i := 0; i < b.N; i++ {
err := json.Unmarshal([]byte(jsonStr), e)
if err != nil {
b.Error(err)
}
if _, err = json.Marshal(e); err != nil {
b.Error(err)
}
}
}
func BenchmarkEasyJson(b *testing.B) {
b.ResetTimer()
e := Employee{}
for i := 0; i < b.N; i++ {
err := e.UnmarshalJSON([]byte(jsonStr))
if err != nil {
b.Error(err)
}
if _, err = e.MarshalJSON(); err != nil {
b.Error(err)
}
}
}
/*
PS D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch18\easyjson> go test -bench .
{{Mike 30} {[Java Go C]}}
{"basic_info":{"name":"Mike","age":30},"job_info":{"skills":["Java","Go","C"]}}
{{Mike 30} {[Java Go C]}}
{"basic_info":{"name":"Mike","age":30},"job_info":{"skills":["Java","Go","C"]}}
goos: windows
goarch: amd64
pkg: goprojects/golang-beginners-guide/src/ch18/easyjson
cpu: AMD Ryzen 7 4800U with Radeon Graphics
BenchmarkEmbeddedJson-16 495374 2515 ns/op
BenchmarkEasyJson-16 1571302 765.1 ns/op
PASS
ok goprojects/golang-beginners-guide/src/ch18/easyjson 3.294s
*/
27.HTTP服务
Handler
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World!")
})
http.HandleFunc("/time/", func(w http.ResponseWriter, r *http.Request) {
t := time.Now()
timeStr := fmt.Sprintf("{\"time\":\"%s\"}", t)
w.Write([]byte(timeStr))
})
http.ListenAndServe(":8080", nil)
}
路由规则
- URL分为两种,末尾是/:表示一个子树,后面可以跟其他子路径;末尾不是/,表示一个叶子,固定的路径;
- 以/结尾的URL可以匹配它的任何子路径,比如/images会匹配/images/cute-cat.jpg;
- 它采用最长匹配原则,如果有多个匹配,一定采用匹配路径最长的那个进行处理;
- 如果没有找到任何匹配项,会返回404错误。
更好的Router
func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
fmt.Fprint(w, "Welcome!\n")
}
func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
fmt.Fprintf(w, "hello %s", ps.ByName("name"))
}
func main() {
router := httprouter.New()
router.GET("/", Index)
router.GET("/hello/:name", Hello)
log.Fatal(http.ListenAndServe(":8080", router))
}
28.性能分析
性能分析工具
- 安装graphviz(graphviz下载地址)
- 将$GOPATH/bin加入$PATH
- 安装go-torch
go get -u github.com/uber/go-torch
下载并复制flamegraph.pl至$GOPATH/bin路径下
将$GOPATH/bin加入$PATH
通过文件方式输出Profile - 灵活性高,适用于特定代码段的分析
- 通过手动调用runtime/pprof的API
- API相关文档
- go tool pprof [binary] [binary.prof]
具体应用如下
//src/ch21/tools/file/prof.go
const (
col = 10000
row = 10000
)
func fillMatrix(m *[row][col]int) {
s := rand.New(rand.NewSource(time.Now().UnixNano()))
for i := 0; i < row; i++ {
for j := 0; j < col; j++ {
m[i][j] = s.Intn(100000)
}
}
}
func calculate(m *[row][col]int) {
for i := 0; i < row; i++ {
tmp := 0
for j := 0; j < col; j++ {
tmp += m[i][j]
}
}
}
func main() {
//创建输出文件
f, err := os.Create("cpu.prof")
if err != nil {
log.Fatal("could not create CPU profile: ", err)
}
// 获取系统信息
if err := pprof.StartCPUProfile(f); err != nil { //监控cpu
log.Fatal("could not start CPU profile: ", err)
}
defer pprof.StopCPUProfile()
// 主逻辑区,进行一些简单的代码运算
x := [row][col]int{}
fillMatrix(&x)
calculate(&x)
f1, err := os.Create("mem.prof")
if err != nil {
log.Fatal("could not create memory profile: ", err)
}
runtime.GC() // GC,获取最新的数据信息
if err := pprof.WriteHeapProfile(f1); err != nil { // 写入内存信息
log.Fatal("could not write memory profile: ", err)
}
f1.Close()
f2, err := os.Create("goroutine.prof")
if err != nil {
log.Fatal("could not create groutine profile: ", err)
}
if gProf := pprof.Lookup("goroutine"); gProf == nil {
log.Fatal("could not write groutine profile: ")
} else {
gProf.WriteTo(f2, 0)
}
f2.Close()
}
在文件所在路径下执行
go build .\prof.go
.\prof.exe
执行完毕后生成下列文件:
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 2024/7/31 10:23 2894 cpu.prof
-a---- 2024/7/31 10:23 1402 goroutine.prof
-a---- 2024/7/31 10:23 1219 mem.prof
-a---- 2024/7/31 10:23 2785792 prof.exe
-a---- 2024/7/27 18:53 1547 prof.go
查看prof文件
go tool pprof prof .\cpu.prof
执行结果如下
PS D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch21\
tools\file> go tool pprof prof
prof: open prof: The system cannot find the file specified.
failed to fetch any source profiles
PS D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch21\
tools\file> go tool pprof prof .\cpu.prof
prof: open prof: The system cannot find the file specified.
Fetched 1 source profiles out of 2
File: prof.exe
Build ID: D:\Environment\GoPath\src\goprojects\golang-beginners-guide\sr
c\ch21\tools\file\prof.exe2024-07-31 10:23:38.9839107 +0800 CST
Type: cpu
Time: Jul 31, 2024 at 10:23am (CST)
Duration: 1.38s, Total samples = 1.19s (86.04%)
Entering interactive mode (type "help" for commands, "o" for options)
在pprof环境下查看cpu占用
(pprof) top
Showing nodes accounting for 1.19s, 100% of 1.19s total
Showing top 10 nodes out of 44
flat flat% sum% cum cum%
0.76s 63.87% 63.87% 0.87s 73.11% math/rand.(*Rand).Int31n
0.22s 18.49% 82.35% 1.15s 96.64% main.fillMatrix
0.07s 5.88% 88.24% 0.07s 5.88% math/rand.(*rngSource).Uint6
4 (inline)
0.06s 5.04% 93.28% 0.93s 78.15% math/rand.(*Rand).Intn
0.02s 1.68% 94.96% 0.02s 1.68% main.calculate
0.02s 1.68% 96.64% 0.11s 9.24% math/rand.(*Rand).Int63 (inl
ine)
0.02s 1.68% 98.32% 0.09s 7.56% math/rand.(*rngSource).Int63
0.01s 0.84% 99.16% 0.01s 0.84% runtime.cgocall
0.01s 0.84% 100% 0.01s 0.84% runtime.memclrNoHeapPointers
0 0% 100% 0.01s 0.84% compress/flate.(*compressor)
.init
详细分析某个方法
(pprof) list fillMatrix
Total: 1.19s
ROUTINE ======================== main.fillMatrix in D:\Environment\GoPat
h\src\goprojects\golang-beginners-guide\src\ch21\tools\file\prof.go
220ms 1.15s (flat, cum) 96.64% of Total
. . 17:func fillMatrix(m *[row][col]int) {
. . 18: s := rand.New(rand.NewSource(time.Now().
UnixNano()))
. . 19:
. . 20: for i := 0; i < row; i++ {
10ms 10ms 21: for j := 0; j < col; j++ {
210ms 1.14s 22: m[i][j] = s.Intn(100000)
. . 23: }
. . 24: }
. . 25:}
. . 26:
. . 27:func calculate(m *[row][col]int) {
图形化方式查看各函数间CPU耗时
(pprof) svg
Generating report in profile001.svg
(pprof) exit
PS D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch21\
tools\file> go tool pprof prof .\mem.prof
prof: open prof: The system cannot find the file specified.
Fetched 1 source profiles out of 2
File: prof.exe
Build ID: D:\Environment\GoPath\src\goprojects\golang-beginners-guide\sr
c\ch21\tools\file\prof.exe2024-07-31 10:23:38.9839107 +0800 CST
Type: inuse_space
Time: Jul 31, 2024 at 10:23am (CST)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top
Showing nodes accounting for 1.16MB, 100% of 1.16MB total
flat flat% sum% cum cum%
1.16MB 100% 100% 1.16MB 100% runtime/pprof.StartCPUProfil
e
0 0% 100% 1.16MB 100% main.main
0 0% 100% 1.16MB 100% runtime.main
(pprof) list main.main
Total: 1.16MB
ROUTINE ======================== main.main in D:\Environment\GoPath\src\
goprojects\golang-beginners-guide\src\ch21\tools\file\prof.go
0 1.16MB (flat, cum) 100% of Total
. . 36:func main() {
. . 37: //创建输出文件
. . 38: f, err := os.Create("cpu.prof")
. . 39: if err != nil {
. . 40: log.Fatal("could not create CPU
profile: ", err)
. . 41: }
. . 42:
. . 43: // 获取系统信息
. 1.16MB 44: if err := pprof.StartCPUProfile(f); err
!= nil { //监控cpu
. . 45: log.Fatal("could not start CPU p
rofile: ", err)
. . 46: }
. . 47: defer pprof.StopCPUProfile()
. . 48:
. . 49: // 主逻辑区,进行一些简单的代码运算
(pprof)
通过HTTP方式输出Profile
- 简单,适用于持续性运行的应用
- 在应用程序中导入import _ “net/http/pprof”,并启动http server即可
- http://<host>:<port>/debug/pprof/
- go tool pprof http://<host>:<port>/debug/pprof/profile?seconds=10(默认值为30秒)
- go-torch -seconds 10 http://<host>:<port>/debug/pprof/profile
package main
import (
"fmt"
"log"
"net/http"
_ "net/http/pprof"
)
func GetFibonacciSerie(n int) []int {
ret := make([]int, 2, n)
ret[0] = 1
ret[1] = 1
for i := 2; i < n; i++ {
ret = append(ret, ret[i-2]+ret[i-1])
}
return ret
}
func index(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Welcome!"))
}
func createFBS(w http.ResponseWriter, r *http.Request) {
var fbs []int
for i := 0; i < 1000000; i++ {
fbs = GetFibonacciSerie(50)
}
w.Write([]byte(fmt.Sprintf("%v", fbs)))
}
func main() {
http.HandleFunc("/", index)
http.HandleFunc("/fb", createFBS)
log.Fatal(http.ListenAndServe(":8081", nil))
}
PS D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch21\
tools\file> go tool pprof http://127.0.0.1:8081/debug/pprof/profile
Fetching profile over HTTP from http://127.0.0.1:8081/debug/pprof/profil
e
Saved profile in C:\Users\37471\pprof\pprof.___go_build_goprojects_golan
g_beginners_guide_src_ch21_tools_http.exe.samples.cpu.001.pb.gz
File: ___go_build_goprojects_golang_beginners_guide_src_ch21_tools_http.
exe
Build ID: C:\Users\37471\AppData\Local\Temp\GoLand\___go_build_goproject
s_golang_beginners_guide_src_ch21_tools_http.exe2024-07-31 13:28:01.5849
09 +0800 CST
Type: cpu
Time: Jul 31, 2024 at 1:31pm (CST)
Duration: 30.01s, Total samples = 290ms ( 0.97%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top
Showing nodes accounting for 260ms, 89.66% of 290ms total
Showing top 10 nodes out of 83
flat flat% sum% cum cum%
140ms 48.28% 48.28% 160ms 55.17% main.GetFibonacciSerie
30ms 10.34% 58.62% 30ms 10.34% runtime.stdcall2
20ms 6.90% 65.52% 20ms 6.90% runtime.cgocall
10ms 3.45% 68.97% 10ms 3.45% runtime.(*activeSweep).begin (inline)
10ms 3.45% 72.41% 10ms 3.45% runtime.(*spanSet).reset
10ms 3.45% 75.86% 30ms 10.34% runtime.gcBgMarkWorker
10ms 3.45% 79.31% 10ms 3.45% runtime.osyield
10ms 3.45% 82.76% 10ms 3.45% runtime.pMask.read
10ms 3.45% 86.21% 10ms 3.45% runtime.stdcall3
10ms 3.45% 89.66% 10ms 3.45% runtime.stdcall7
(pprof) top -cum
Showing nodes accounting for 150ms, 51.72% of 290ms total
Showing top 10 nodes out of 83
flat flat% sum% cum cum%
140ms 48.28% 48.28% 160ms 55.17% main.GetFibonacciSerie (inline)
0 0% 48.28% 160ms 55.17% main.createFBS
0 0% 48.28% 160ms 55.17% net/http.(*ServeMux).ServeHTTP
0 0% 48.28% 160ms 55.17% net/http.(*conn).serve
0 0% 48.28% 160ms 55.17% net/http.HandlerFunc.ServeHTTP
0 0% 48.28% 160ms 55.17% net/http.serverHandler.ServeHTTP
0 0% 48.28% 60ms 20.69% runtime.systemstack
10ms 3.45% 51.72% 30ms 10.34% runtime.gcBgMarkWorker
0 0% 51.72% 30ms 10.34% runtime.lock
0 0% 51.72% 30ms 10.34% runtime.lock2
(pprof) list main.GetFibonacciSerie
Total: 290ms
ROUTINE ======================== main.GetFibonacciSerie in D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch21\tools\http\fb_server.go
140ms 160ms (flat, cum) 55.17% of Total
. . 10:func GetFibonacciSerie(n int) []int {
. 20ms 11: ret := make([]int, 2, n)
. . 12: ret[0] = 1
. . 13: ret[1] = 1
10ms 10ms 14: for i := 2; i < n; i++ {
130ms 130ms 15: ret = append(ret, ret[i-2]+ret[i-1])
. . 16: }
. . 17: return ret
. . 18:}
. . 19:
. . 20:func index(w http.ResponseWriter, r *http.Request) {
(pprof)
29.性能调优
常见分析指标
- Wall Time
- CPU Time
- Block Time
- Memory allocation
- GC times/time spent
go test输出profile
go test -bench . -cpuprofile=cpu.prof
go test -bench . -blockprofile=block.prof
go tool pprof cpu.prof
go help testflag
type Request struct {
TransactionID string `json:"transaction_id"`
PayLoad []int `json:"payload"`
}
type Response struct {
TransactionID string `json:"transaction_id"`
Expression string `json:"exp"`
}
func createRequest() string {
payload := make([]int, 100, 100)
for i := 0; i < 100; i++ {
payload[i] = i
}
req := Request{"demo_transaction", payload}
v, err := json.Marshal(&req)
if err != nil {
panic(err)
}
return string(v)
}
func processRequest(reqs []string) []string {
reps := []string{}
for _, req := range reqs {
reqObj := &Request{}
reqObj.UnmarshalJSON([]byte(req))
// json.Unmarshal([]byte(req), reqObj)
var buf strings.Builder
for _, e := range reqObj.PayLoad {
buf.WriteString(strconv.Itoa(e))
buf.WriteString(",")
}
repObj := &Response{reqObj.TransactionID, buf.String()}
repJson, err := repObj.MarshalJSON()
//repJson, err := json.Marshal(&repObj)
if err != nil {
panic(err)
}
reps = append(reps, string(repJson))
}
return reps
}
func processRequestOld(reqs []string) []string {
reps := []string{}
for _, req := range reqs {
reqObj := &Request{}
json.Unmarshal([]byte(req), reqObj)
ret := ""
for _, e := range reqObj.PayLoad {
ret += strconv.Itoa(e) + ","
}
repObj := &Response{reqObj.TransactionID, ret}
repJson, err := json.Marshal(&repObj)
if err != nil {
panic(err)
}
reps = append(reps, string(repJson))
}
return reps
}
func TestCreateRequest(t *testing.T) {
str := createRequest()
t.Log(str)
}
func TestProcessRequest(t *testing.T) {
reqs := []string{}
reqs = append(reqs, createRequest())
reps := processRequest(reqs)
t.Log(reps[0])
}
func BenchmarkProcessRequest(b *testing.B) {
reqs := []string{}
reqs = append(reqs, createRequest())
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = processRequest(reqs)
}
b.StopTimer()
}
func BenchmarkProcessRequestOld(b *testing.B) {
reqs := []string{}
reqs = append(reqs, createRequest())
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = processRequestOld(reqs)
}
b.StopTimer()
}
CPU耗时分析
D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch22>go test -v
=== RUN TestCreateRequest
optimization_test.go:7: {"transaction_id":"demo_transaction","payload":[0,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,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99]}
--- PASS: TestCreateRequest (0.00s)
=== RUN TestProcessRequest
optimization_test.go:14: {"transaction_id":"demo_transaction","exp":"0,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,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,"}
--- PASS: TestProcessRequest (0.00s)
PASS
ok goprojects/golang-beginners-guide/src/ch22 1.605s
D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch22>go test -bench . -cpuprofile=cpu.prof
goos: windows
goarch: amd64
pkg: goprojects/golang-beginners-guide/src/ch22
cpu: AMD Ryzen 7 4800U with Radeon Graphics
BenchmarkProcessRequest-16 168573 7218 ns/op
BenchmarkProcessRequestOld-16 51066 23125 ns/op
PASS
ok goprojects/golang-beginners-guide/src/ch22 4.628s
D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch22>dir
驱动器 D 中的卷是 Data
卷的序列号是 40CC-CBCE
D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch22 的目录
2024/07/31 13:55 <DIR> .
2024/07/31 13:46 <DIR> ..
2024/07/31 13:55 5,605,376 ch22.test.exe
2024/07/31 13:55 15,675 cpu.prof
2024/07/27 18:53 700 optimization_test.go
2024/07/27 18:53 1,353 optmization.go
2024/07/27 18:53 246 structs.go
2024/07/27 18:53 4,760 structs_easyjson.go
6 个文件 5,628,110 字节
2 个目录 229,622,636,544 可用字节
D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch22>go tool pprof cpu.prof
File: ch22.test.exe
Build ID: C:\Users\37471\AppData\Local\Temp\go-build2577889608\b001\ch22.test.exe2024-07-31 13:55:08.6787643 +0800 CST
Type: cpu
Time: Jul 31, 2024 at 1:55pm (CST)
Duration: 2.87s, Total samples = 3.44s (119.67%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top -cum
Showing nodes accounting for 0.11s, 3.20% of 3.44s total
Dropped 86 nodes (cum <= 0.02s)
Showing top 10 nodes out of 175
flat flat% sum% cum cum%
0 0% 0% 2.35s 68.31% testing.(*B).runN
0 0% 0% 2.34s 68.02% testing.(*B).launch
0 0% 0% 1.21s 35.17% goprojects/golang-beginners-guide/src/ch22.BenchmarkProcessRequestOld
0.02s 0.58% 0.58% 1.21s 35.17% goprojects/golang-beginners-guide/src/ch22.processRequestOld
0 0% 0.58% 1.13s 32.85% goprojects/golang-beginners-guide/src/ch22.BenchmarkProcessRequest
0.04s 1.16% 1.74% 1.13s 32.85% goprojects/golang-beginners-guide/src/ch22.processRequest
0 0% 1.74% 0.97s 28.20% goprojects/golang-beginners-guide/src/ch22.(*Request).UnmarshalJSON (partial-inline)
0.05s 1.45% 3.20% 0.97s 28.20% goprojects/golang-beginners-guide/src/ch22.easyjson6a975c40DecodeCh471
0 0% 3.20% 0.92s 26.74% runtime.systemstack
0 0% 3.20% 0.52s 15.12% encoding/json.Unmarshal
(pprof) list processRequest
Total: 3.44s
ROUTINE ======================== goprojects/golang-beginners-guide/src/ch22.processRequest in D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch22\optmization.go
40ms 1.13s (flat, cum) 32.85% of Total
. . 22:func processRequest(reqs []string) []string {
. . 23: reps := []string{}
. . 24: for _, req := range reqs {
. . 25: reqObj := &Request{}
. 740ms 26: reqObj.UnmarshalJSON([]byte(req))
. . 27: // json.Unmarshal([]byte(req), reqObj)
. . 28:
. . 29: var buf strings.Builder
20ms 20ms 30: for _, e := range reqObj.PayLoad {
20ms 180ms 31: buf.WriteString(strconv.Itoa(e))
. 30ms 32: buf.WriteString(",")
. . 33: }
. . 34: repObj := &Response{reqObj.TransactionID, buf.String()}
. 120ms 35: repJson, err := repObj.MarshalJSON()
. . 36: //repJson, err := json.Marshal(&repObj)
. . 37: if err != nil {
. . 38: panic(err)
. . 39: }
. 40ms 40: reps = append(reps, string(repJson))
. . 41: }
. . 42: return reps
. . 43:}
. . 44:
. . 45:func processRequestOld(reqs []string) []string {
ROUTINE ======================== goprojects/golang-beginners-guide/src/ch22.processRequestOld in D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch22\optmization.go
20ms 1.21s (flat, cum) 35.17% of Total
. . 45:func processRequestOld(reqs []string) []string {
. . 46: reps := []string{}
. . 47: for _, req := range reqs {
. 10ms 48: reqObj := &Request{}
. 520ms 49: json.Unmarshal([]byte(req), reqObj)
. . 50: ret := ""
. . 51: for _, e := range reqObj.PayLoad {
10ms 480ms 52: ret += strconv.Itoa(e) + ","
. . 53: }
. . 54: repObj := &Response{reqObj.TransactionID, ret}
. 180ms 55: repJson, err := json.Marshal(&repObj)
. . 56: if err != nil {
. . 57: panic(err)
. . 58: }
. 10ms 59: reps = append(reps, string(repJson))
. . 60: }
10ms 10ms 61: return reps
. . 62:}
(pprof)
内存分析
D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch22>go tool pprof mem.prof
File: ch22.test.exe
Build ID: D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch22\ch22.test.exe2024-07-31 13:55:13.3139604 +0800 CST
Type: alloc_space
Time: Jul 31, 2024 at 2:11pm (CST)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top
Showing nodes accounting for 1928.14MB, 99.69% of 1934.14MB total
Dropped 21 nodes (cum <= 9.67MB)
Showing top 10 nodes out of 29
flat flat% sum% cum cum%
946.19MB 48.92% 48.92% 1157.79MB 59.86% goprojects/golang-beginners-guide/src/ch22.processRequestOld
462.80MB 23.93% 72.85% 464.80MB 24.03% goprojects/golang-beginners-guide/src/ch22.easyjson6a975c40DecodeCh471
153.55MB 7.94% 80.79% 153.55MB 7.94% strings.(*Builder).WriteString (inline)
109.03MB 5.64% 86.42% 775.35MB 40.09% goprojects/golang-beginners-guide/src/ch22.processRequest
92.02MB 4.76% 91.18% 92.02MB 4.76% github.com/mailru/easyjson/buffer.getBuf
81.53MB 4.22% 95.40% 81.53MB 4.22% github.com/mailru/easyjson/buffer.(*Buffer).BuildBytes
50.51MB 2.61% 98.01% 142.53MB 7.37% github.com/mailru/easyjson/buffer.(*Buffer).ensureSpaceSlow
24.01MB 1.24% 99.25% 76.02MB 3.93% encoding/json.Marshal
8.50MB 0.44% 99.69% 135.58MB 7.01% encoding/json.Unmarshal
0 0% 99.69% 124.58MB 6.44% encoding/json.(*decodeState).object
(pprof) list processRequest
Total: 1.89GB
ROUTINE ======================== goprojects/golang-beginners-guide/src/ch22.processRequest in D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch22\optmization.go
109.03MB 775.35MB (flat, cum) 40.09% of Total
. . 22:func processRequest(reqs []string) []string {
. . 23: reps := []string{}
. . 24: for _, req := range reqs {
. . 25: reqObj := &Request{}
50.52MB 390.74MB 26: reqObj.UnmarshalJSON([]byte(req))
. . 27: // json.Unmarshal([]byte(req), reqObj)
. . 28:
. . 29: var buf strings.Builder
. . 30: for _, e := range reqObj.PayLoad {
. 59.01MB 31: buf.WriteString(strconv.Itoa(e))
. 94.54MB 32: buf.WriteString(",")
. . 33: }
. . 34: repObj := &Response{reqObj.TransactionID, buf.String()}
. 172.54MB 35: repJson, err := repObj.MarshalJSON()
. . 36: //repJson, err := json.Marshal(&repObj)
. . 37: if err != nil {
. . 38: panic(err)
. . 39: }
58.52MB 58.52MB 40: reps = append(reps, string(repJson))
. . 41: }
. . 42: return reps
. . 43:}
. . 44:
. . 45:func processRequestOld(reqs []string) []string {
ROUTINE ======================== goprojects/golang-beginners-guide/src/ch22.processRequestOld in D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch22\optmization.go
946.19MB 1.13GB (flat, cum) 59.86% of Total
. . 45:func processRequestOld(reqs []string) []string {
. . 46: reps := []string{}
. . 47: for _, req := range reqs {
1.50MB 1.50MB 48: reqObj := &Request{}
19.51MB 155.09MB 49: json.Unmarshal([]byte(req), reqObj)
. . 50: ret := ""
. . 51: for _, e := range reqObj.PayLoad {
900.67MB 900.67MB 52: ret += strconv.Itoa(e) + ","
. . 53: }
2MB 2MB 54: repObj := &Response{reqObj.TransactionID, ret}
. 76.02MB 55: repJson, err := json.Marshal(&repObj)
. . 56: if err != nil {
. . 57: panic(err)
. . 58: }
22.51MB 22.51MB 59: reps = append(reps, string(repJson))
. . 60: }
. . 61: return reps
. . 62:}
(pprof)
30.别被性能锁住
var cache map[string]string
const NUM_OF_READER int = 40
const READ_TIMES = 100000
func init() {
cache = make(map[string]string)
cache["a"] = "aa"
cache["b"] = "bb"
}
func lockFreeAccess() {
var wg sync.WaitGroup
wg.Add(NUM_OF_READER)
for i := 0; i < NUM_OF_READER; i++ {
go func() {
for j := 0; j < READ_TIMES; j++ {
_, err := cache["a"]
if !err {
fmt.Println("Nothing")
}
}
wg.Done()
}()
}
wg.Wait()
}
func lockAccess() {
var wg sync.WaitGroup
wg.Add(NUM_OF_READER)
m := new(sync.RWMutex)
for i := 0; i < NUM_OF_READER; i++ {
go func() {
for j := 0; j < READ_TIMES; j++ {
m.RLock()
_, err := cache["a"]
if !err {
fmt.Println("Nothing")
}
m.RUnlock()
}
wg.Done()
}()
}
wg.Wait()
}
func BenchmarkLockFree(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
lockFreeAccess()
}
}
func BenchmarkLock(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
lockAccess()
}
}
D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch23\lock>go test -bench . -cpuprofile=cpu.prof
goos: windows
goarch: amd64
pkg: goprojects/golang-beginners-guide/src/ch23/lock
cpu: AMD Ryzen 7 4800U with Radeon Graphics
BenchmarkLockFree-16 493 2348541 ns/op
BenchmarkLock-16 14 76886436 ns/op
PASS
ok goprojects/golang-beginners-guide/src/ch23/lock 4.467s
D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch23\lock>go tool pprof cpu.prof
File: lock.test.exe
Build ID: C:\Users\37471\AppData\Local\Temp\go-build263731560\b001\lock.test.exe2024-07-31 14:47:29.0027346 +0800 CST
Type: cpu
Time: Jul 31, 2024 at 2:47pm (CST)
Duration: 2.75s, Total samples = 33.55s (1218.32%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top
Showing nodes accounting for 33.44s, 99.67% of 33.55s total
Dropped 42 nodes (cum <= 0.17s)
flat flat% sum% cum cum%
16.33s 48.67% 48.67% 16.33s 48.67% sync/atomic.(*Int32).Add (inline)
13.78s 41.07% 89.75% 14.56s 43.40% runtime.mapaccess2_faststr
2.14s 6.38% 96.13% 16.98s 50.61% goprojects/golang-beginners-guide/src/ch23/lock_test.lockFreeAccess.func1
0.78s 2.32% 98.45% 0.78s 2.32% runtime.add (inline)
0.34s 1.01% 99.46% 0.34s 1.01% sync.(*WaitGroup).Done (inline)
0.04s 0.12% 99.58% 16.46s 49.06% goprojects/golang-beginners-guide/src/ch23/lock_test.lockAccess.func1
0.02s 0.06% 99.64% 6.43s 19.17% sync.(*RWMutex).RLock (inline)
0.01s 0.03% 99.67% 9.93s 29.60% sync.(*RWMutex).RUnlock (inline)
(pprof) list lockAccess
Total: 33.55s
ROUTINE ======================== goprojects/golang-beginners-guide/src/ch23/lock_test.lockAccess.func1 in D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch23\lock\lock_test.go
40ms 16.46s (flat, cum) 49.06% of Total
. . 45: go func() {
20ms 20ms 46: for j := 0; j < READ_TIMES; j++ {
. . 47:
. 6.43s 48: m.RLock()
10ms 70ms 49: _, err := cache["a"]
10ms 10ms 50: if !err {
. . 51: fmt.Println("Nothing")
. . 52: }
. 9.93s 53: m.RUnlock()
. . 54: }
. . 55: wg.Done()
. . 56: }()
. . 57: }
. . 58: wg.Wait()
(pprof)
sync.Map
- 适合读多写少,且Key相对稳定对的环境
- 采用了空间换时间的方案,并且采用指针的方式间接实现值的映射,所以存储空间会较built-in map大
Concurrent Map - 适用于读写都很频繁的情况
别被性能锁住 - 减少锁的影响范围
- 减少发生锁冲突的概率
sync.Map
ConcurrentMap - 避免锁的使用
31.GC友好的代码
避免内存分配和复制
- 复杂对象尽量传递引用
- 数组的传递
- 结构体传递
打开GC日志
只要在程序执行之前加上环境变量GODEBUG=gctrace=1,
如:GODEBUG=gctrace=1 go test -bench .
GODEBUG=gctrace=1 go run main.go
日志详细信息参考
package gc_friendly
import (
"testing"
)
const NumOfElems = 1000
type Content struct {
Detail [10000]int
}
func withValue(arr [NumOfElems]Content) int {
// fmt.Println(&arr[2])
return 0
}
func withReference(arr *[NumOfElems]Content) int {
//b := *arr
// fmt.Println(&arr[2])
return 0
}
func TestFn(t *testing.T) {
var arr [NumOfElems]Content
//fmt.Println(&arr[2])
withValue(arr)
withReference(&arr)
}
func BenchmarkPassingArrayWithValue(b *testing.B) {
var arr [NumOfElems]Content
b.ResetTimer()
for i := 0; i < b.N; i++ {
withValue(arr)
}
b.StopTimer()
}
func BenchmarkPassingArrayWithRef(b *testing.B) {
var arr [NumOfElems]Content
b.ResetTimer()
for i := 0; i < b.N; i++ {
withReference(&arr)
}
b.StopTimer()
}
设置GODEBUG环境变量为gctrace=1
PS D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch24\gc_friendly\passing_ref> go test -bench=BenchmarkPassingArrayWithValue
gc 1 @0.068s 0%: 0+0.52+0 ms clock, 0+0/0.52/0.52+0 ms cpu, 4->4->0 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 2 @0.069s 0%: 0+1.5+0 ms clock, 0+0.52/1.0/0+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 3 @0.072s 0%: 0.52+1.0+0 ms clock, 8.3+0/0/0+0 ms cpu, 3->5->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 4 @0.074s 0%: 0+1.0+0 ms clock, 0+0/0/0.51+0 ms cpu, 3->5->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 5 @0.076s 0%: 0+1.0+0 ms clock, 0+0/0/0+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 6 @0.079s 0%: 0+0.51+0 ms clock, 0+0/1.5/0+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 7 @0.081s 1%: 0+2.1+0 ms clock, 0+1.0/2.1/0+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 8 @0.084s 1%: 0+1.0+0 ms clock, 0+0.51/2.0/0+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 9 @0.086s 1%: 0.51+1.0+0 ms clock, 8.2+0/0/0+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 10 @0.088s 2%: 0+1.0+0 ms clock, 0+1.0/3.6/1.0+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 11 @0.091s 2%: 0+1.0+0 ms clock, 0+0.054/0/0+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 12 @0.093s 2%: 0+1.5+0 ms clock, 0+1.5/2.1/0+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 13 @0.096s 2%: 0+1.0+0 ms clock, 0+0/2.0/0.51+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 14 @0.100s 2%: 0+1.1+0 ms clock, 0+0/2.6/1.6+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 15 @0.103s 2%: 0+1.0+0 ms clock, 0+0/0/0+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 16 @0.105s 2%: 0+0.51+0 ms clock, 0+0/0/0+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 17 @0.107s 2%: 0+1.0+0 ms clock, 0+1.0/2.0/0+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 18 @0.109s 2%: 0+1.0+0 ms clock, 0+0/0/0+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 19 @0.111s 2%: 0+1.0+0 ms clock, 0+0/0.53/0+0 ms cpu, 3->5->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 20 @0.114s 2%: 0+0.51+0 ms clock, 0+0/1.5/0+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 21 @0.116s 2%: 0+1.0+0 ms clock, 0+0/0/0+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 22 @0.118s 2%: 0+1.0+0 ms clock, 0+0/0/0+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 23 @0.120s 2%: 0+1.1+0 ms clock, 0+0/2.3/0+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 24 @0.123s 2%: 0+0.50+0 ms clock, 0+1.0/2.0/1.5+0 ms cpu, 3->5->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 25 @0.124s 2%: 0+1.0+0 ms clock, 0+0/0/0+0 ms cpu, 3->5->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 26 @0.126s 2%: 0+1.3+0 ms clock, 0+0/0/0+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 27 @0.130s 2%: 0+1.0+0 ms clock, 0+1.0/4.0/6.0+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 28 @0.133s 2%: 0+0+0 ms clock, 0+0/0/0+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 29 @0.134s 3%: 1.5+0.56+0 ms clock, 24+0.056/0.056/0+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 30 @0.138s 3%: 0+0.51+0 ms clock, 0+0.51/2.0/4.1+0 ms cpu, 3->3->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 31 @0.142s 3%: 0+1.0+0 ms clock, 0+0.51/3.1/0+0 ms cpu, 3->3->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 32 @0.147s 3%: 0+0.51+0 ms clock, 0+1.0/2.0/2.5+0 ms cpu, 3->3->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 33 @0.152s 3%: 0+0.51+0 ms clock, 0+0/2.0/2.5+0 ms cpu, 3->3->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 34 @0.160s 3%: 0+1.0+0 ms clock, 0+0.55/2.1/1.0+0 ms cpu, 3->3->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 35 @0.170s 3%: 0+2.0+0 ms clock, 0+0/4.0/0+0 ms cpu, 3->3->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 36 @0.181s 3%: 0+1.0+0 ms clock, 0+0/1.0/1.0+0 ms cpu, 3->3->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 37 @0.204s 3%: 0+0.70+0 ms clock, 0+0.093/0.67/1.1+0 ms cpu, 3->3->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 38 @0.215s 2%: 0+1.0+0 ms clock, 0+0/2.1/1.5+0 ms cpu, 3->3->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 39 @0.222s 3%: 0.53+0.54+0 ms clock, 8.5+0/1.0/2.1+0 ms cpu, 3->3->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 40 @0.231s 3%: 0+1.0+0 ms clock, 0+2.1/2.1/0+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 41 @0.233s 3%: 0+1.5+0 ms clock, 0+0/0/0+0 ms cpu, 3->5->2 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 42 @0.237s 3%: 0+1.0+0 ms clock, 0+0.50/2.0/0+0 ms cpu, 5->6->2 MB, 6 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 43 @0.240s 3%: 0+0.99+0 ms clock, 0+0/0/0+0 ms cpu, 4->5->2 MB, 5 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 44 @0.242s 3%: 0+0.99+0 ms clock, 0+0/2.9/0+0 ms cpu, 4->5->2 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 45 @0.244s 3%: 0+0.99+0 ms clock, 0+0/0.99/0+0 ms cpu, 4->5->2 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 46 @0.246s 3%: 0+1.1+0 ms clock, 0+0/0/0+0 ms cpu, 4->5->2 MB, 5 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 47 @0.248s 3%: 0+1.2+0 ms clock, 0+0/0/0+0 ms cpu, 4->5->2 MB, 5 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 48 @0.251s 3%: 0+1.2+1.0 ms clock, 0+0/5.1/3.8+16 ms cpu, 4->5->2 MB, 5 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 49 @0.254s 3%: 0+0.99+0.50 ms clock, 0+0/3.9/2.9+8.1 ms cpu, 4->5->2 MB, 5 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 50 @0.257s 3%: 0+1.0+0 ms clock, 0+0/4.0/1.0+0 ms cpu, 4->5->2 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 51 @0.259s 3%: 0+1.0+0 ms clock, 0+0/0/0+0 ms cpu, 4->5->2 MB, 5 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 52 @0.262s 3%: 0+1.5+0 ms clock, 0+0/6.0/7.5+0 ms cpu, 4->5->2 MB, 5 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 53 @0.266s 3%: 0+1.0+0 ms clock, 0+0/4.0/6.0+0 ms cpu, 4->5->2 MB, 5 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 54 @0.271s 3%: 0+1.0+0 ms clock, 0+0/1.0/0+0 ms cpu, 4->5->2 MB, 5 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 55 @0.285s 3%: 0.061+0.50+0 ms clock, 0.97+0/1.0/1.5+0 ms cpu, 4->4->2 MB, 5 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 56 @0.304s 3%: 0.53+1.3+0 ms clock, 8.6+0.19/3.7/3.1+0 ms cpu, 4->5->2 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 57 @0.311s 3%: 0+1.6+0 ms clock, 0+0/4.8/5.3+0 ms cpu, 4->5->2 MB, 5 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 58 @0.317s 3%: 0+1.0+0 ms clock, 0+0/3.1/3.1+0 ms cpu, 4->5->2 MB, 5 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 59 @0.322s 3%: 0+1.0+0 ms clock, 0+0/0/0.53+0 ms cpu, 4->6->2 MB, 5 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 60 @0.325s 3%: 0+1.1+0 ms clock, 0+0/2.5/1.4+0 ms cpu, 5->6->3 MB, 6 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 61 @0.327s 3%: 0+1.0+0 ms clock, 0+1.0/0/0+0 ms cpu, 6->6->2 MB, 7 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 62 @0.331s 3%: 0+0.65+0 ms clock, 0+0/2.6/2.7+0 ms cpu, 5->5->2 MB, 5 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 63 @0.333s 3%: 0.52+1.0+0 ms clock, 8.4+0/0/0+0 ms cpu, 5->6->3 MB, 6 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 64 @0.335s 3%: 0+1.0+0 ms clock, 0+2.2/4.3/1.1+0 ms cpu, 6->6->3 MB, 7 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 65 @0.339s 3%: 0+0.52+0 ms clock, 0+0/1.0/1.5+0 ms cpu, 5->6->2 MB, 6 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 66 @0.348s 3%: 0+1.0+0 ms clock, 0+0/2.6/2.5+0 ms cpu, 4->5->2 MB, 5 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 67 @0.363s 3%: 0+2.0+0 ms clock, 0+2.0/5.1/2.0+0 ms cpu, 4->6->2 MB, 5 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 68 @0.501s 2%: 0+1.2+0 ms clock, 0+0/2.8/3.3+0 ms cpu, 5->5->3 MB, 6 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 69 @0.507s 2%: 0+1.8+0 ms clock, 0+1.0/5.7/2.3+0 ms cpu, 6->6->2 MB, 7 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 70 @0.530s 2%: 0+1.1+0 ms clock, 0+0/4.5/2.8+0 ms cpu, 5->5->2 MB, 6 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 71 @0.557s 2%: 0+1.0+0 ms clock, 0+0.51/2.0/2.5+0 ms cpu, 5->5->2 MB, 6 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 72 @0.563s 2%: 0+0.69+0 ms clock, 0+0/2.7/4.1+0 ms cpu, 5->5->2 MB, 6 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 73 @0.571s 2%: 0+1.3+0 ms clock, 0+0/5.3/2.6+0 ms cpu, 5->5->2 MB, 6 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 74 @0.580s 2%: 0+1.1+0 ms clock, 0+0/4.3/9.2+0 ms cpu, 5->5->2 MB, 6 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 75 @0.589s 2%: 0+1.0+0 ms clock, 0+0/4.1/6.1+0 ms cpu, 5->6->3 MB, 6 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 76 @0.595s 2%: 0+0+1.5 ms clock, 0+0/0/0+24 ms cpu, 5->6->2 MB, 6 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 77 @0.601s 2%: 0+1.0+0 ms clock, 0+1.0/4.0/6.0+0 ms cpu, 5->5->2 MB, 6 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 78 @0.608s 2%: 0+1.0+0 ms clock, 0+0/4.0/10+0 ms cpu, 5->5->2 MB, 6 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 79 @0.617s 2%: 0+1.0+0 ms clock, 0+0/2.0/0+0 ms cpu, 5->7->3 MB, 6 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 80 @0.626s 2%: 0+1.0+0 ms clock, 0+0/4.3/9.8+0 ms cpu, 6->6->3 MB, 7 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 81 @0.699s 2%: 0+2.4+0 ms clock, 0+0/6.0/0.50+0 ms cpu, 5->6->3 MB, 7 MB goal, 0 MB stacks, 0 MB globals, 16 P
# goprojects/golang-beginners-guide/src/ch24/gc_friendly/passing_ref.test
gc 1 @0.008s 1%: 0+1.0+0 ms clock, 0+0.51/2.0/1.0+0 ms cpu, 5->5->4 MB, 5 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 2 @0.013s 1%: 0+0.51+0 ms clock, 0+0/1.0/0+0 ms cpu, 20->20->20 MB, 20 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 3 @0.039s 0%: 0+1.5+0 ms clock, 0+0.52/1.0/0+0 ms cpu, 41->41->39 MB, 41 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 1 @0.008s 0%: 0+0.52+0 ms clock, 0+0/0/0+0 ms cpu, 77->77->76 MB, 77 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 2 @0.009s 0%: 0+0.52+0 ms clock, 0+0/1.5/0+0 ms cpu, 152->152->152 MB, 153 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 3 @0.055s 1%: 1.0+0+0 ms clock, 16+0/0/0+0 ms cpu, 153->153->0 MB, 306 MB goal, 0 MB stacks, 0 MB globals, 16 P (forced)
gc 4 @0.058s 1%: 0+0+0 ms clock, 0+0/0/0+0 ms cpu, 0->0->0 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P (forced)
gc 5 @0.067s 1%: 0+0.98+0 ms clock, 0+0/0/0+0 ms cpu, 76->76->76 MB, 76 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 6 @0.079s 2%: 0+0+1.0 ms clock, 0+0/0/0+17 ms cpu, 152->152->152 MB, 153 MB goal, 0 MB stacks, 0 MB globals, 16 P
goos: windows
goarch: amd64
pkg: goprojects/golang-beginners-guide/src/ch24/gc_friendly/passing_ref
cpu: AMD Ryzen 7 4800U with Radeon Graphics
BenchmarkPassingArrayWithValue-16 gc 7 @0.095s 2%: 0+0.50+0 ms clock, 0+0/0.50/0+0 ms cpu, 152->152->0 MB, 306 MB goal, 0 MB stacks, 0 MB globals,
16 P (forced)
gc 8 @0.106s 2%: 0+1.0+0 ms clock, 0+1.0/0/0+0 ms cpu, 76->76->76 MB, 76 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 9 @0.115s 1%: 0+0+0 ms clock, 0+0/0/0+0 ms cpu, 152->152->152 MB, 153 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 10 @0.161s 1%: 0+0.98+0 ms clock, 0+0/0/0+0 ms cpu, 305->305->152 MB, 306 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 11 @0.225s 1%: 0+1.5+0 ms clock, 0+0/0/0+0 ms cpu, 305->305->152 MB, 306 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 12 @0.275s 0%: 0+1.5+0 ms clock, 0+0/1.5/0+0 ms cpu, 305->305->152 MB, 306 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 13 @0.323s 0%: 0+1.0+0 ms clock, 0+0/0/0+0 ms cpu, 305->305->152 MB, 306 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 14 @0.374s 0%: 0+0+0 ms clock, 0+0/0/0+0 ms cpu, 305->305->152 MB, 306 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 15 @0.421s 0%: 0+0+0 ms clock, 0+0/0/0+0 ms cpu, 305->305->152 MB, 306 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 16 @0.467s 0%: 0+1.2+0 ms clock, 0+0/1.2/0+0 ms cpu, 305->305->152 MB, 306 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 17 @0.515s 0%: 0+1.5+0 ms clock, 0+0/1.5/0+0 ms cpu, 305->305->152 MB, 306 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 18 @0.565s 0%: 0+1.0+0 ms clock, 0+0/3.1/0+0 ms cpu, 305->305->152 MB, 306 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 19 @0.613s 0%: 0+0.96+0 ms clock, 0+0/0/0+0 ms cpu, 305->305->152 MB, 306 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 20 @0.673s 0%: 0+0.99+0 ms clock, 0+0/0/0+0 ms cpu, 305->305->152 MB, 306 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 21 @0.724s 0%: 0+1.0+0 ms clock, 0+0/2.0/0+0 ms cpu, 305->305->152 MB, 306 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 22 @0.778s 0%: 0+4.5+0 ms clock, 0+0/0/0+0 ms cpu, 305->305->152 MB, 306 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 23 @0.828s 0%: 0+1.0+0 ms clock, 0+0/0/0+0 ms cpu, 305->305->152 MB, 306 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 24 @0.874s 0%: 0+0+1.0 ms clock, 0+0/0/0+16 ms cpu, 305->305->152 MB, 306 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 25 @0.926s 0%: 0+0+0 ms clock, 0+0/0/0+0 ms cpu, 305->305->152 MB, 306 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 26 @0.971s 0%: 0+0+0 ms clock, 0+0/0/0+0 ms cpu, 305->305->152 MB, 306 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 27 @1.020s 0%: 0+1.0+0 ms clock, 0+0/4.0/0+0 ms cpu, 305->305->152 MB, 306 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 28 @1.065s 0%: 1.0+12+0 ms clock, 16+0/0/0+0 ms cpu, 305->305->152 MB, 306 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 29 @1.111s 0%: 0+1.0+0 ms clock, 0+0/0/0+0 ms cpu, 305->305->152 MB, 306 MB goal, 0 MB stacks, 0 MB globals, 16 P
gc 30 @1.160s 0%: 0+13+0 ms clock, 0+0/0/0+0 ms cpu, 305->305->152 MB, 306 MB goal, 0 MB stacks, 0 MB globals, 16 P
44 24867116 ns/op
PASS
ok goprojects/golang-beginners-guide/src/ch24/gc_friendly/passing_ref 2.682s
生成trace文件
PS D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch24\gc_friendly\passing_ref> go test -bench=BenchmarkPassingArrayWithRef -trace=trace
_ref
goos: windows
goarch: amd64
pkg: goprojects/golang-beginners-guide/src/ch24/gc_friendly/passing_ref
cpu: AMD Ryzen 7 4800U with Radeon Graphics
BenchmarkPassingArrayWithRef-16 1000000000 0.2363 ns/op
PASS
ok goprojects/golang-beginners-guide/src/ch24/gc_friendly/passing_ref 0.413s
PS D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch24\gc_friendly\passing_ref> go test -bench=BenchmarkPassingArrayWithRef -trace=trace
_ref.out
no required module provides package .out; to add it:
go get .out
PS D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch24\gc_friendly\passing_ref> go test -bench=BenchmarkPassingArrayWithRef -trace=trace
_ref
goos: windows
goarch: amd64
pkg: goprojects/golang-beginners-guide/src/ch24/gc_friendly/passing_ref
cpu: AMD Ryzen 7 4800U with Radeon Graphics
BenchmarkPassingArrayWithRef-16 1000000000 0.2473 ns/op
PASS
ok goprojects/golang-beginners-guide/src/ch24/gc_friendly/passing_ref 0.451s
PS D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch24\gc_friendly\passing_ref> go test -bench=BenchmarkPassingArrayWithValue -trace=tra
ce_value
goos: windows
goarch: amd64
pkg: goprojects/golang-beginners-guide/src/ch24/gc_friendly/passing_ref
cpu: AMD Ryzen 7 4800U with Radeon Graphics
BenchmarkPassingArrayWithValue-16 49 25593561 ns/op
PASS
ok goprojects/golang-beginners-guide/src/ch24/gc_friendly/passing_ref 1.406s
PS D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch24\gc_friendly\passing_ref> dir
目录: D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch24\gc_friendly\passing_ref
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 2024/7/27 18:53 799 pass_array_test.go
-a---- 2024/7/31 16:16 18433 trace_ref
-a---- 2024/7/31 16:16 32907 trace_value
go tool trace
普通程序输出trace信息
func main(){
f,err:=os.Create("trace.out")
if err != nil{
panic(err)
}
defer f.Close()
err = trace.Start(f)
if err != nil{
panic(err)
}
defer trace.Stop()
//你的程序
}
测试程序输出trace信息
go test -trace trace.out
可视化trace信息:
go tool trace trace.out
PS D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch24\gc_friendly\passing_ref> go tool trace trace_ref
2024/07/31 16:18:48 Preparing trace for viewer...
2024/07/31 16:18:48 Splitting trace for viewer...
2024/07/31 16:18:48 Opening browser. Trace viewer is listening on http://127.0.0.1:13904
避免内存分配和复制
- 初始化至合适的大小(自动扩容是有代价的)
- 复用内存
32.高效的字符串连接
const numbers = 100
func BenchmarkSprintf(b *testing.B) {
b.ResetTimer()
for idx := 0; idx < b.N; idx++ {
var s string
for i := 0; i < numbers; i++ {
s = fmt.Sprintf("%v%v", s, i)
}
}
b.StopTimer()
}
func BenchmarkStringBuilder(b *testing.B) {
b.ResetTimer()
for idx := 0; idx < b.N; idx++ {
var builder strings.Builder
for i := 0; i < numbers; i++ {
builder.WriteString(strconv.Itoa(i))
}
_ = builder.String()
}
b.StopTimer()
}
func BenchmarkBytesBuf(b *testing.B) {
b.ResetTimer()
for idx := 0; idx < b.N; idx++ {
var buf bytes.Buffer
for i := 0; i < numbers; i++ {
buf.WriteString(strconv.Itoa(i))
}
_ = buf.String()
}
b.StopTimer()
}
func BenchmarkStringAdd(b *testing.B) {
b.ResetTimer()
for idx := 0; idx < b.N; idx++ {
var s string
for i := 0; i < numbers; i++ {
s += strconv.Itoa(i)
}
}
b.StopTimer()
}
测试结果:
PS D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch24> go test -bench .
goos: windows
goarch: amd64
pkg: goprojects/golang-beginners-guide/src/ch24
cpu: AMD Ryzen 7 4800U with Radeon Graphics
BenchmarkSprintf-16 61113 18167 ns/op
BenchmarkStringBuilder-16 1478074 802.7 ns/op
BenchmarkBytesBuf-16 1325361 915.6 ns/op
BenchmarkStringAdd-16 180688 6844 ns/op
第二章:算法编程常用构造函数
1.构造链表
type ListNode struct {
Val int
Next *ListNode
}
func ConstructMyList(nums []int) *ListNode {
dummyNode := &ListNode{Val: math.MinInt}
cur := dummyNode
for _, num := range nums {
cur.Next = &ListNode{
Val: num,
Next: nil,
}
cur = cur.Next
}
return dummyNode.Next
}
2.树
2.1 数的构造
type TreeNode struct {
Val int
Left *TreeNode
Right *TreeNode
}
func constructTree(nums []int) *TreeNode {
treeNodes := make([]*TreeNode, len(nums))
for i, num := range nums {
if num != -1 {
treeNode := &TreeNode{
Val: num,
Left: nil,
Right: nil,
}
treeNodes[i] = treeNode
}
}
for i := 0; 2*i+2 < len(nums); i++ {
if treeNodes[i] != nil {
treeNodes[i].Left = treeNodes[2*i+1]
treeNodes[i].Right = treeNodes[2*i+2]
}
}
return treeNodes[0]
}
2.2 树的递归
递归算法三要素
- 确定递归函数的参数和返回值
- 确定终止条件
- 确定单层递归的逻辑
实现二叉树的前序遍历(递归法)
二叉树的前序遍历
由于需要打印前序遍历数据,创建切片存放节Val,此外无其他要处理的数据,且不需要返回值。递归函数定义如下:
record := make([]int, 0)
var traversal func(node *TreeNode)
在递归过程中当前节点为空时,本层递归结束:
if node == nil {
return
}
前序遍历是中左右的顺序,所以单层递归的逻辑是先取中节点的Val:
record = append(record, node.Val)
traversal(node.Left)
traversal(node.Right)
完整代码如下:
func preorderTraversal(root *TreeNode) []int {
record := make([]int, 0)
var traversal func(node *TreeNode)
traversal = func(node *TreeNode) {
if node == nil {
return
}
record = append(record, node.Val)
traversal(node.Left)
traversal(node.Right)
}
traversal(root)
return record
}
2.3 树的迭代
递归的实现就是每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,这就是递归为什么可以返回上一层位置的原因。
思路
前序遍历每次先处理中间节点,因此现将根节点入栈,然后将右节点入栈,再将左节点入栈。
实现二叉树的前序遍历(迭代法)
完整代码如下:
func preorderTraversal(root *TreeNode) []int {
record := []int{}
if root == nil {
return record
}
stack := list.New()
stack.PushBack(root)
for stack.Len() > 0 {
node := stack.Remove(stack.Back()).(*TreeNode)
record = append(record, node.Val)
if node.Right != nil {
stack.PushBack(node.Right)
}
if node.Left != nil {
stack.PushBack(node.Left)
}
}
return record
}
二叉树的统一迭代法
前序遍历的迭代过程有两个操作:
- 处理:将元素放入record数组中
- 访问:遍历节点
因为前序遍历的顺序是中左右,先访问的元素是中间节点,要处理的元素也是中间节点,要访问的元素和要处理的元素顺序是一致的,所以实现逻辑相对简介。
中序遍历是左中右,先访问二叉树顶部的节点,然后一层一层向下访问,直到到达树左边的最底部,再开始处理节点(把节点Val放入record数组中),这导致处理顺序和访问顺序不一致。
使用迭代法实现中序遍历,需要借用指针的遍历来帮忙访问节点,栈则用来处理节点上的元素。
代码实现如下:
前序遍历统一迭代
func inorderTraversal(root *TreeNode) []int {
ans := []int{}
if root == nil {
return ans
}
st := list.New()
cur := root
for cur != nil || st.Len() > 0 {
if cur != nil {
st.PushBack(cur)
cur = cur.Left
} else {
cur = st.Remove(st.Back()).(*TreeNode)
ans = append(ans, cur.Val)
cur = cur.Right
}
}
return ans
}
统一迭代法
将访问的节点放入栈中,把要处理的节点也放入栈中但要做标记。(要处理的节点入栈后,紧接着入栈一个空指针作为标记)
/**
type Element struct {
// 元素保管的值
Value interface{}
// 内含隐藏或非导出字段
}
func (l *List) Back() *Element
前序遍历:中左右
压栈顺序:右左中
**/
func preorderTraversal(root *TreeNode) []int {
if root == nil {
return nil
}
var stack = list.New()//栈
res:=[]int{}//结果集
stack.PushBack(root)
var node *TreeNode
for stack.Len()>0{
e := stack.Back()
stack.Remove(e)//弹出元素
if e.Value==nil{// 如果为空,则表明是需要处理中间节点
e=stack.Back()//弹出元素(即中间节点)
stack.Remove(e)//删除中间节点
node=e.Value.(*TreeNode)
res=append(res,node.Val)//将中间节点加入到结果集中
continue//继续弹出栈中下一个节点
}
node = e.Value.(*TreeNode)
//压栈顺序:右左中
if node.Right!=nil{
stack.PushBack(node.Right)
}
if node.Left!=nil{
stack.PushBack(node.Left)
}
stack.PushBack(node)//中间节点压栈后再压入nil作为中间节点的标志符
stack.PushBack(nil)
}
return res
}
中序遍历统一迭代
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/
//中序遍历:左中右
//压栈顺序:右中左
func inorderTraversal(root *TreeNode) []int {
if root==nil{
return nil
}
stack:=list.New()//栈
res:=[]int{}//结果集
stack.PushBack(root)
var node *TreeNode
for stack.Len()>0{
e := stack.Back()
stack.Remove(e)
if e.Value==nil{// 如果为空,则表明是需要处理中间节点
e=stack.Back()//弹出元素(即中间节点)
stack.Remove(e)//删除中间节点
node=e.Value.(*TreeNode)
res=append(res,node.Val)//将中间节点加入到结果集中
continue//继续弹出栈中下一个节点
}
node = e.Value.(*TreeNode)
//压栈顺序:右中左
if node.Right!=nil{
stack.PushBack(node.Right)
}
stack.PushBack(node)//中间节点压栈后再压入nil作为中间节点的标志符
stack.PushBack(nil)
if node.Left!=nil{
stack.PushBack(node.Left)
}
}
return res
}
后序遍历统一迭代
//后续遍历:左右中
//压栈顺序:中右左
func postorderTraversal(root *TreeNode) []int {
if root == nil {
return nil
}
var stack = list.New()//栈
res:=[]int{}//结果集
stack.PushBack(root)
var node *TreeNode
for stack.Len()>0{
e := stack.Back()
stack.Remove(e)
if e.Value==nil{// 如果为空,则表明是需要处理中间节点
e=stack.Back()//弹出元素(即中间节点)
stack.Remove(e)//删除中间节点
node=e.Value.(*TreeNode)
res=append(res,node.Val)//将中间节点加入到结果集中
continue//继续弹出栈中下一个节点
}
node = e.Value.(*TreeNode)
//压栈顺序:中右左
stack.PushBack(node)//中间节点压栈后再压入nil作为中间节点的标志符
stack.PushBack(nil)
if node.Right!=nil{
stack.PushBack(node.Right)
}
if node.Left!=nil{
stack.PushBack(node.Left)
}
}
return res
}
2.4 树的层序遍历
层序遍历一个二叉树。就是从左到右一层一层的去遍历二叉树。需要借用一个辅助数据结构即队列来实现,队列先进先出,符合一层一层遍历的逻辑。
func levelOrder(root *TreeNode) [][]int {
if root == nil {
return [][]int{}
}
res := make([][]int, 0)
queue := list.New()
queue.PushBack(root)
for queue.Len() > 0 {
queueLen := queue.Len()
tempArr := make([]int, queueLen)
for i := 0; i < queueLen; i++ {
node := queue.Remove(queue.Front()).(*TreeNode)
tempArr[i] = node.Val
if node.Left != nil {
queue.PushBack(node.Left)
}
if node.Right != nil {
queue.PushBack(node.Right)
}
}
res = append(res, tempArr)
}
return res
}
2.5 二叉树的深度和高度
二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数或者节点数(取决于深度从0开始还是从1开始)
二叉树节点的高度:指从该节点到叶子结点的最长简单路径边的条数或者节点数(取决于高度从0开始还是从1开始)
前序(中左右)求的是深度
后序(左右中)求的是高度
根节点的高度就是二叉树对的最大深度
- 确定递归函数参数和返回值:参数是传入树的根节点,返回这棵树的深度。
- 确定终止条件:如果为空节点,返回0,表示高度为0。
- 确定单层递归的逻辑:先求它的左子树的深度,再求右子树的深度,最后取左右深度最大的值再加1(加1是因为算上当前中间节点)就是目前节点为根节点对的树的深度。
代码实现:
func max(a, b int) int {
if a < b {
return b
}
return a
}
func maxDepth(root *TreeNode) int {
var travel func(node *TreeNode) int
travel = func(node *TreeNode) int {
if node == nil {
return 0
}
leftDepth := travel(node.Left)
rightDepth := travel(node.Right)
return max(leftDepth, rightDepth) + 1
}
if root == nil {
return 0
}
return travel(root)
}
2.6 完全二叉树节点个数
完全二叉树的定义:在完全二叉树中,除了最底层节点可能没填满外其余每层节点都达到了最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第h层(h从1开始),则该层包含1~2^(h-1)个节点。
堆就是一棵完全二叉树,同时保持父子节点的顺序关系。
完全二叉树只有两种情况:
- 满二叉树(满二叉树的定义:深度为k,有2^k-1个节点的二叉树。)
- 最后一层叶子节点没有满
对于情况1可以直接用2^树深度-1计算(注意这里根节点深度为1)。
对于情况2分别递归左孩子和右孩子,递归到某一深度一定会有左孩子或者右孩子为满二叉树,然后依然可以按照情况1计算。
如何判断一个左子树或者右子树是不是满二叉树?
在完全二叉树中,如果递归向左遍历的深度等于递归向右遍历的深度,说明这是一个满二叉树。
在完全二叉树中 如果递归向左遍历的深度不等于递归向右遍历的深度,说明这不是一个满二叉树。
func countNodes(root *TreeNode) int {
//终止条件
if root == nil {
return 0
}
leftDepth, rightDepth := 0, 0
leftNode, rightNode := root.Left, root.Right
//求左子树深度
for leftNode != nil {
leftNode = leftNode.Left
leftDepth++
}
//求右子树深度
for rightNode != nil {
rightNode = rightNode.Right
rightDepth++
}
//计算满二叉树节点数
if leftDepth == rightDepth {
return 2<<(leftDepth) - 1
}
//单层递归逻辑,后序遍历
leftTreeNodes := countNodes(root.Left)
rightTreeNodes := countNodes(root.Right)
return leftTreeNodes + rightTreeNodes + 1
}
2.7 平衡二叉树
高度平衡二叉树的定义:一个二叉树每个节点的左右子树的高度差的绝对值不超过1。
func isBalanced(root *TreeNode) bool {
var getHeight func(node *TreeNode) int
getHeight = func(node *TreeNode) int {
if node == nil {
return 0
}
l, r := getHeight(node.Left), getHeight(node.Right)
if l == -1 || r == -1 {
return -1
}
if l-r > 1 || r-l > 1 {
return -1
}
return max(l, r) + 1
}
h := getHeight(root)
if h == -1 {
return false
}
return true
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
原创来自程序员Carl