Go语言入门学习笔记


第一章:基本程序结构

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条件
与其他主要编程语言的差异

  1. 条件表达式不限制为常量或者整数;
  2. 单个case中,可以出现多个结果选项,使用逗号分隔;
  3. 与C语言等规则相反,Go语言不需要用break来明确退出一个case;
  4. 可以不设定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. 切片

  1. 容量是否可伸缩:数组容量不可伸缩
  2. 是否可以进行比较:相同维数相同长度的数组可比较,切片不可比较
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.字符串

与其他主要编程语言的差异

  1. string是数据类型,不是引用或指针类型
  2. string是只读的byte slice,len函数表示它所包含的byte数
  3. 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

  1. Unicode是一种字符集
  2. 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
}

编码与存储

字符“中”
Unicode0x4E2D
UTF-80xE4B8AD
string/[]byte[0xE4,0XB8,0XAD]
常用字符串函数
  1. strings包
  2. 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语言的函数

函数是一等公民
与其他主要编程语言的差异

  1. 可以有多个返回值
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)
*/
  1. 所有参数都是值传递:slice,map,channel会有传引用的错觉
  2. 函数可以作为变量的值
  3. 函数可以作为参数和返回值

可变长参数和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语言的相关接口

接口与依赖
与其他主要编程语言的差异

  1. 接口为非入侵性,实现不依赖于接口定义
  2. 所以接口的定义可以包含在接口使用者包内
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{}

自定义类型

  1. type IntConv func(n int)int
  2. 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!")
}

空接口与断言

  1. 空接口可以表示任何类型
  2. 通过断言来将空接口转换为制定类型
    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接口最佳实践

  1. 倾向于使用小的接口定义,很多接口只包含一个方法
type Reader interface {
	Read(p []byte) (n int, err error)
}
type Writer interface {
	Write(p []byte) (n int, err error)
}
  1. 较大的接口定义,可以由多个小接口定义组合而成
type ReadWriter interface {
	Reader
	Writer
}
  1. 只依赖于必要功能的最小接口
func StoreData(reader Reader) error {
	...
}

13.编写好的错误处理

Go的错误机制
与其他主要编程语言的差异

  1. 没有异常机制
  2. error类型实现了error接口
type error interface {
	Error() string
}
  1. 可以通过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

  1. 基本复用模块单元(以首字母大写来表明可被包外代码访问)
  2. 代码的package可以和所在的目录不一致
  3. 同一目录里的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

  1. 创建时默认的stack的大小
  • JDK5以后Java Thread stack默认为1M
  • Groutine的Stack初始化大小为2K
  1. 和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 Model

  • 和Actor的直接通讯不同,CSP模式则是通过Channel进行通讯的,更松耦合一些。
  • Go中channel是有容量限制并且独立于处理Groutine,而如Erlang,Actor模式中的messagebox容量是无限。
  • Golang协程会主动从channel中去处理channel传过来的消息,Actor模式进程总是被动地处理消息。
    channel机制
    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对象的获取
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 文件不会编译。

  1. 每个测试函数需要以 Test 为前缀,例如:Test_Division, TestDivision
  2. 每个性能测试函数需要以 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架构
pipe-filter架构

  • 非常适合于数据处理及数据分析系统
  • Filter封装数据处理的功能
  • Pipe用于连接Filter传递数据或者在异步处理过程中缓冲数据(进程内同步调用时,pipe演变为数据在方法调用间传递)
  • 松耦合:Filter只跟数据(格式)耦合
    Filter和组合模式
    filter和组合模式
    微内核架构(Micro-Kernel)

特点

  • 易于扩展
  • 错误隔离
  • 保持架构一致性
    要点
  • 内核包含公共流程或通用逻辑
  • 将可变或可扩展部分规划为扩展点
  • 抽象扩展点行为,定义接口
  • 利用插件进行可扩展
    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

图形化查看各函数CPU耗时

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 树的递归

递归算法三要素

  1. 确定递归函数的参数和返回值
  2. 确定终止条件
  3. 确定单层递归的逻辑

实现二叉树的前序遍历(递归法)
二叉树的前序遍历
由于需要打印前序遍历数据,创建切片存放节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
}

二叉树的统一迭代法
前序遍历的迭代过程有两个操作:

  1. 处理:将元素放入record数组中
  2. 访问:遍历节点
    因为前序遍历的顺序是中左右,先访问的元素是中间节点,要处理的元素也是中间节点,要访问的元素和要处理的元素顺序是一致的,所以实现逻辑相对简介。
    中序遍历是左中右,先访问二叉树顶部的节点,然后一层一层向下访问,直到到达树左边的最底部,再开始处理节点(把节点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开始)
前序(中左右)求的是深度
后序(左右中)求的是高度
根节点的高度就是二叉树对的最大深度

  1. 确定递归函数参数和返回值:参数是传入树的根节点,返回这棵树的深度。
  2. 确定终止条件:如果为空节点,返回0,表示高度为0。
  3. 确定单层递归的逻辑:先求它的左子树的深度,再求右子树的深度,最后取左右深度最大的值再加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)个节点。
堆就是一棵完全二叉树,同时保持父子节点的顺序关系。
完全二叉树只有两种情况

  1. 满二叉树(满二叉树的定义:深度为k,有2^k-1个节点的二叉树。)
  2. 最后一层叶子节点没有满
    对于情况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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值