golang学习笔记

基础语法

变量定义

使用var关键字

var a,b,c bool
var s1,s2 string = “hello”,”world”
可以放在函数内,或直接放在包内
使用var()集中定义变量

使用:=定义变量

a,b,i,s1,s2 := true,false,3,”hello”,”world”,让编译器自动决定编译类型
只能在函数内使用

变量定义注意事项

定义过得变量一定要使用,否则编译器会报错
字符串类型的变量初始值是空字符串(不像java为null)
变量定义的时候赋值可以不用定义数据类型,编译器会进行类型推断,甚至在同一行代码定义不同类型的变量都是可以的
在函数外面定义变量不能使用:=来定义变量,只能使用var关键字进行变量定义
函数外面定义变量作用域在包内部,没有全局变量的说法

内建变量类型

数据类型

bool:布尔

string字符串

int整形

(u)int 根据操作系统位数决定长度(32位系统长度(u)int32/64位系统(u)int64)
(u)int8
(u)int16
(u)int32
(u)int64

uinttpr指针

byte字节

rune字符(32bit)

float(浮点)

float32
float64

complex(复数)

complex64 实部和虚部各32位浮点数
complex128 实部和虚部各64位浮点数

复数回顾

什么是复数:复数是形如 a + b i的数.式中a,b 为 实数,i是一个满足i^2 =-1的数,因为任何实数的平方不等于-1,所以i不是实数,而是实数以外的新的数.

|3+4i| = 5 ?到底是怎么算的?有知道的可以告诉我一下,我不会算😢开始还以为这个公式有问题,结果程序跑出来的结果就是这么多😫

复数

i^0 = 1

i^1 = i

i^2 = -1

i^3 = -i

i^4 = 1

i^5 = i

i^6 = -1

我们发现每增加一个次方就逆时针旋转90度

欧拉公式的由来

最美公式-欧拉公式

常量与枚举

常量定义使用const关键字定义

常量可以规定数据类型,也可以让编译器自主推断类型

常量也可以定义在const()里面表示定义一组常量

go语言的常量名称定义一般不大写,大写F代表public

枚举类型使用const()定义

go语言中没有专门的枚举类型,所以我们以常量组作为枚举类型

1
2
3
4
5
const (
zero = 0
one = 1
two = 2
)

go语言提供了iota关键字是枚举值的自增表达式,使用_下划线跳过

1
2
3
4
5
6
7
const (
cpp = iota
_
python
golang
javascript
)

以上枚举常量的值为cpp:0/_:1/python:2/golang:3/javascript:4

还能以iota关键之定义磁盘容量表达式如下

1
2
3
4
5
6
7
8
const (
b 1 << (10 * iota)
kb
mb
gb
tb
pb
)

条件语句

if条件语句

示例:读取文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import (
"fmt"
"io/ioutil"
)

func main() {
const filename = "abc.txt"
//go语言的函数可以返回多个值
file, err := ioutil.ReadFile(filename)
//错误不为空打印错误
if err != nil {
fmt.Println(err)
} else {
//没有错误打印文本文件内容
fmt.Printf("%s\n\n", file)
}
}

注意:go语言的if语句可以像其它语言的for语句的写法,先初始化赋值(定义变量),再进行条件判断,使用分号分隔,作用域在if以内,写法如下

1
2
3
4
5
6
7
8
9
10
11
12
13
package main
import (
"fmt"
"io/ioutil"
)
func main() {
const filename = "abc.txt"
if file, err := ioutil.ReadFile(filename); err != nil {
fmt.Println(err)
} else {
fmt.Printf("%s\n\n", file)
}
}

switch条件语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func eval(a int, b int, op string) int {
var result int
switch op {
case "+":
result = a+b
case "-":
result = a-b
case "*":
result = a*b
case "/":
result = a/b
default:
panic("unsupported operator" + op)
}
return result
}

看看这段

switch代码块中的case会自动break,如果不适用break使用fallthrough,也就是其它预压的case穿透。

panic是go语言中的报错,报错会让程序停下来。

switch里面如果没有表达式也可以将条件放到case语句里面,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func grade(score int) string {
g := ""
switch {
case score < 60:
g = "F"
case score < 80:
g = "C"
case score < 90:
g = "B"
case score <= 100:
g = "A"
default:
panic(fmt.Sprintf("Wrong score:%d",score))
}
return g
}

循环

for

一个简单的for循环

1
2
3
4
5
6
func loopdemo() {
sum := 1
for i := 0; i < 100; i++ {
sum+=1
}
}

for语句的条件里不需要括号

for的条件里面可以省略初始条件,结束条件,递增表达式

没有初始化条件的写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//将数字转换为2进制字符
func convertToBin(number int) string {
result := ""
for ; number > 0; number /= 2 {
lsb := number % 2
result = strconv.Itoa(lsb) + result
}
return result
}
//go语言对换行有所讲究,需要加上逗号
func main() {
fmt.Println(
convertToBin(2),
convertToBin(3),
convertToBin(4),
)
//打印结果为10 11 100
}

初始化条件及递增表达式同时省略(相当于while,但是go语言中没有while)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func printFile(filename string) {
file, err := os.Open(filename)
if err != nil {
panic(err)
}
scanner := bufio.NewScanner(file)
for scanner.Scan() {
fmt.Println(scanner.Text())
}

}

func main() {
printFile("abc.txt")
}

死循环写法(并发编程相互通信会用到死循环)

1
2
3
4
5
6
7
8
9
func forever() {
for true {
fmt.Println("死循环")
}
}

func main() {
forever()
}

函数

函数可以返回多个值

函数返回多个值时可以起名字(仅限于非常简单的函数,但对于调用者可以随便起名)

go语言函数是函数式编程语言,函数是一等公民,函数里面的参数、返回值、函数体都可以有函数

go语言没有花哨的lambda表达式,是需要将匿名函数写出即可

go语言没有默认参数,也没有方法重载

go语言函数有可变参数列表

使用函数式编程实现3的4次方

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//重新定义pow函数接收int类型
func pow(a, b int) int {
return int(math.Pow(float64(a), float64(b)))
}
//op定义了调用方传递的函数
func apply(op func(int, int) int, a int, b int) (result int) {
//反射获取到传递过来的函数的指针
pointer := reflect.ValueOf(op).Pointer()
//通过指针获取函数名称
name := runtime.FuncForPC(pointer).Name()
//打印传递的函数名称及参数
fmt.Printf("calling function is %s with args (%d,%d) \n", name, a, b)
//返回值由传入的函数处理后的结果决定
return op(a, b)
}
func main() {
//计算3的4次方等于81,传的的函数式操作为pow,a的b次方
fmt.Println(apply(pow, 3, 4))
//也可以使用匿名函数的方式,计算2的3次方
fmt.Println(apply(
func(a int, b int) int {
return int(math.Pow(float64(a), float64(b)))
}, 2, 3))
}

执行结果

1
2
3
4
calling function is main.pow with args (3,4) 
81
calling function is main.main.func1 with args (2,3)
8

函数的可变参示例

1
2
3
4
5
6
7
func sum(numbers ...int) int {
sum := 0
for i := range numbers {
sum += numbers[i]
}
return sum
}

指针

先看一段代码

1
2
3
4
5
6
7
8
9
10
func pointer() {
//定义变量a赋值为2
var a int = 2
//定义一个指针pa,类型为*int(指针型int),指向变量a的地址值
var pa *int = &a
//将指针*pa指向的地址值赋值为3,也就是将变量a赋值为3
*pa = 3
fmt.Println(a)
//打印结果为3
}

go语言的指针不能进行运算

go语言只有值传递一种方式(值传递会copy一份数据进行操作/引用传递会操作传递的值)

go语言通过指针传递能打到引用传递的效果

自定义类型需要考虑该类型是需要作为指针使用还是作为值来使用

使用指针对变量进行操作

1
2
3
4
5
6
7
8
9
10
func swap(a *int, b *int) {
*a, *b = *b, *a
}
func main() {
a := 3
b := 4
//使用指针对变量进行操作
swap(&a, &b)
fmt.Println(a,b)
}

不使用指针将操作后的结果返回或许更好

1
2
3
4
5
6
7
8
9
10
11
func swap(a int, b int) (resa int, resb int) {
return b, a
}

func main() {
a := 3
b := 4
//使用指针对变量进行操作
a, b = swap(a, b)
fmt.Println(a,b)
}

内建容器

数组

数组是值类型,在函数中执行的时候进行拷贝一份再处理

go语言中认为[3]int类型与[5]int认为是不同的类型,在调用的时候数组长度不一致go会认为是不同类型

go语言中我们一般情况下不使用数组,也不使用数组的指针,我们使用切片Slice

数组的定义方法

1
2
3
4
5
6
7
8
//定义容量为5元素类型为int的数组(元素初始值为0)
var array1 [5]int
//定义容量为3元素类型为int的数组(指定元素初始值)
array2 := [3]int{1,2,3}
//容量为编译器来确定类型为int的数组
array3 := [...]int{2,4,6,8,10}
//定义4行5列的int类型二维数组(元素初始值为0)
var grid [4][5]int

普通for循环遍历数组

1
2
3
4
5
6
func foreachArr() {
arr := [...]int{2,4,6,8,10}
for i := 0; i < len(arr); i++ {
fmt.Println(arr[i])
}
}

for i := range遍历数组

1
2
3
4
5
6
7
func foreachArr() {
arr := [...]int{2,4,6,8,10}
//这里的i取到的是arr的索引
for i := range arr{
fmt.Println(arr[i])
}
}

for i := range遍历数组同时取索引和值

1
2
3
4
5
6
7
func foreachArr() {
arr := [...]int{2,4,6,8,10}
//这里的i取到的是arr的索引,v取到的是值
for i,v := range arr{
fmt.Println(i,v)
}
}

for i := range遍历数组只获取值

1
2
3
4
5
6
7
func foreachArr() {
arr := [...]int{2,4,6,8,10}
//这里的索引以下标_替换代表不取索引,v取到的是值(任何地方都可以使用下标_省略变量)
for _,v := range arr{
fmt.Println(v)
}
}

切片(Slice)

我们先看一段代码,看看什么是切片

1
2
3
arr := [...]int{0,1,2,3,4,5,6,7}
s := arr[2:6]
//这里的s就是切片,一个左闭右开的区(数学里面的闭区间是取等的)

Slice其实是数组的一个视图

1
2
3
4
arr[2:6] = [2 3 4 5]
arr[:6] = [0 1 2 3 4 5]
arr[2:] = [2 3 4 5 6 7]
arr[:] = [0 1 2 3 4 5 6 7]

Slice本身没有数据,是对底层array的一个view

Reslice

1
2
3
4
5
6
7
arr := [...]int{0,1,2,3,4,5,6,7}
s2 := arr[:]
//[0 1 2 3 4 5 6 7]
s2 = s2[:5]
//[0 1 2 3 4]
s2 = s2[2:]
//[2 3 4]

slice的扩展

1
2
3
4
5
6
7
8
arr := [...]int{0,1,2,3,4,5,6,7}
//上面是元素数据
s1 := arr[2:6]
//[2 3 4 5]
//fmt.Pringln(s1[4])
//上面这行注释的代码会报索引越界异常,用下面的slice(s2)可以结局这个问题,golang的slice有cap的概念
s2 := s1[3:5]
//[5 6]

上面这一块的代码图解如下

slice扩展

slice的实现

slice的实现

slice底层有一个array

ptr指向了slice开头的元素

len指向了slice的长度,使用slice[n]只能取len长度里面的值,超过长度值会报索引越界

cap代表了从ptr开始整个数组的长度,扩展的时候只要不超过cap的长度就可以

slice可以向后扩展,但是不可以往前扩展,向后扩展不可以超过cap的长度

len(slice)可以取到这个slice的长度,cap(slice)可以取到这个slice的cap长度

切片(slice)的操作

slice的append操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
arr := [...]int{0,1,2,3,4,5,6,7}
//上面是元素数据
s1 := arr[2:6]
//s1--->[2 3 4 5]
//fmt.Pringln(s1[4])
//上面这行注释的代码会报索引越界异常,用下面的slice(s2)可以结局这个问题,golang的slice有cap的概念
s2 := s1[3:5]
//s2--->[5 6]
s3 := appent(s2,10)
//s3--->[5 6 10]
s4 := appent(s3,11)
//s4--->[5 6 10 11]
s5 := appent(s4,12)
//s5--->[5 6 10 11 12]

//arr最终的值为[0 1 2 3 4 5 6 10],arr的长度并没有发生变化,而s4及s5是对被扩展的数组的一个view,我们拿不到扩展的数组,s3 appent过后但是他并没有超过arr长度,所以对底层arr相应索引位置的数据进行了修改

添加元素时如果超过cap的长度,系统会重新分配更大的底层数组作为新slice的view

如果原来的数组有用系统会保留,如果没用会被go的垃圾回收机制给回收掉

由于值传递的关系必须接受appent的返回值 s = append(s,val)

slice的其它操作

slice的创建

定义一个空的slice长度为0

1
var s []int

定义指定元素内容的slice

1
s1 := []int{2,4,6,8}

内建函数定义len为16的slice

1
s2 := make([]int,16)

内建函数定义len为10 cap为32 的slice

1
s3 := make([]int,10,32)

slice的copy及元素remove操作

1
2
3
4
5
6
s1 := []int{2,4,6,8}
//s1--->[2 4 6 8]
s2 := make([]int,16)
//s2--->[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
copy(s2,s1)
//s2--->[2 4 6 8 0 0 0 0 0 0 0 0 0 0 0 0]

如果我们想要把s2中的元素8删掉怎么操作呢

1
2
3
//s2--->[2 4 6 8 0 0 0 0 0 0 0 0 0 0 0 0]
s2 = append(s2[:3],s2[4:]...)//s2所在的参数列表是一个可变参,如果需要将s2索引4右面的所有元素都送个它需要加上...的语法
//s2--->[2 4 6 0 0 0 0 0 0 0 0 0 0 0 0]

去掉s2的首尾操作

1
2
3
4
5
6
7
8
9
//s2--->[2 4 6 0 0 0 0 0 0 0 0 0 0 0 0]
//获取首元素的值,可以用于打印
front := s2[0]
//获取尾元素,可以用于打印
tail := s2[len(s2)-1]
//剔除首位操作
s2 := s2[1:]
s2 := s2[:len(s2)-1]
//s2--->[4 6 0 0 0 0 0 0 0 0 0 0 0]

Map

Map的创建

map[Key]Value 创建单层Key/Value类型的Map

map[Key1]map[Key2]Value 创建复合多层级Map

1
2
3
4
5
6
7
mapDemo := map[string]string{
"name": "anakin",
"course": "java",
"site": "imooc",
"quality": "notbad",
}
fmt.Println(mapDemo)

空map的创建

1
2
3
4
5
6
7
8
//使用make
m1 := make(map[string]int)
//m1--->map[] (m1 == empty)
//或者使用
var m2 map[string]int
//m2--->map[] (m2 == nil)

//go语言中的nil及empty是可以参与运算的,跟其它语言有所区别,值是nil也可以很安全的使用

Map的迭代

使用range对map进行迭代

1
2
3
4
5
6
7
8
9
10
11
12
13
func mapIterate() {
mapDemo := map[string]string{
"name": "anakin",
"course": "java",
"site": "imooc",
"quality": "notbad",
}
fmt.Println(mapDemo)
for k,v := range mapDemo{
fmt.Println(k,v)
}
}
//map打印出来的值我们发现map中的元素是无序的

map获取指定元素

通过key获取map中value的值

1
2
3
4
5
6
7
8
9
10
func mapDemo() {
mapDemo := map[string]string{
"name": "anakin",
"course": "java",
"site": "imooc",
"quality": "notbad",
}
courseName := mapDemo["course"]
fmt.Println(courseName)
}

如果map中没有这个key的话获取map的值不会报错,他是一个空串,Println他会打印一个空行

判断map中的某个元素是否存在

判断元素在map中是否存在,存在则打印,不存在不打印

1
2
3
4
5
6
7
8
9
10
11
12
13
func mapDemo() {
mapDemo := map[string]string{
"name": "anakin",
"course": "java",
"site": "imooc",
"quality": "notbad",
}
if courseName, ok := mapDemo["course"]; ok {
fmt.Println(courseName)
} else {
fmt.Println("key does not exist")
}
}

map删除指定元素

删除map中的指定key

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func mapDemo() {
mapDemo := map[string]string{
"name": "anakin",
"course": "java",
"site": "imooc",
"quality": "notbad",
}
delete(mapDemo,"name")
//删除key为name的元素,后面的key does not exist提示将会被打印
if courseName, ok := mapDemo["name"]; ok {
fmt.Println(courseName)
} else {
fmt.Println("key does not exist")
}
}

什么样的数据类型才能作为map的key呢

map使用哈希表,必须可以比较相等

除了slice,map,function的内建类型都可以作为key

Struct(自建)类型不包含上述字段也可以作为key

go在编译器会对上述条件进行检查

map实现leetCode算法3.无重复字符的最长子串

英文版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func lengthOfLongestSubstring(s string) int {
//记录每个字母最后出现的位置
lastOccurred := make(map[byte]int)
start := 0
maxLenght := 0
for i,ch := range []byte(s) {
//有可能不存在这个map获取出来的值是0,而0又是一个合法的下标,所以要判断这个key是否存在
if lastI,ok := lastOccurred[ch] ; ok && lastI >= start {
start = lastOccurred[ch] +1
}
if i - start +1 > maxLenght {
maxLenght = i - start + 1
}
lastOccurred[ch] = i
}
return maxLenght
}

国际版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func lengthOfLongestSubstring(s string) int {
//记录每个字母最后出现的位置
   lastOccurred := make(map[rune]int)
start := 0
maxLenght := 0
   for i,ch := range []rune(s) {
//有可能不存在这个map获取出来的值是0,而0又是一个合法的下标,所以要判断这个key是否存在
if lastI,ok := lastOccurred[ch] ; ok && lastI >= start {
start = lastOccurred[ch] +1
}
if i - start +1 > maxLenght {
maxLenght = i - start + 1
}
lastOccurred[ch] = i
}
return maxLenght
}

面向对象

go语言仅支持封装,不支持继承和多态

go语言没有class,只有struct

结构体

结构体定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//定义结构体
type TreeNode struct {
value int
left,right *TreeNode
}

//对结构体数据进行处理
func main() {
node1 := TreeNode{value: 3}
left := TreeNode{value: 1}
right := TreeNode{value: 2}
node1.left = &left
node1.right = &right
fmt.Println(node1)
}
//打印结果
{3 0xc000004480 0xc0000044a0}

go语言没有构造函数的说法,但是可以使用func实现自定义工厂构造函数

1
2
3
4
5
func createNode(value int) *TreeNode {
//c**函数返回局部变量不可用,不过在go语言中是可以的
//注意返回的是局部变量的地址
return &TreeNode{value: value}
}

为结构体定义方法

结构体的方法有一个特点,就是它在函数名前面多了一个接收者

1
2
3
4
5
6
7
8
9
//定义结构体方法
func (node TreeNode) print() {
fmt.Print(node.value)
}
//使用结构体方法
func main() {
node := TreeNode{value: 3}
node.print()
}

go语言中所有方法参数的传递都是值传递,参数会被copy执行,执行过后传递的参数并没有被修改,如需修改需要使用指针

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
type TreeNode struct {
value int
left,right *TreeNode
}
func (node TreeNode) print() {
fmt.Println(node.value)
}
func (node TreeNode) setVal(value int) {
node.value = value
}
//解决值传递问题需要加*号以指针的方式即可修改传递过来的值
func (node *TreeNode) setValue(value int) {
node.value = value
}
func main() {
node := TreeNode{}
node.print()
//打印值为0
node.setVal(1)
node.print()
//打印值为0
node.setValue(2)
node.print()
//打印值为2
}

方法的值接收者与指针接收者原则

要改变内容必须使用指针接收者

结构过大也要考虑使用指针接收者

一致性:如果有指针接收者,最好都是用指针接收者

值接收者是go语言特有

值/指针接收者既可以接收值也可以接收指针

遍历树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
type TreeNode struct {
value int
left,right *TreeNode
}
func (node TreeNode) print() {
fmt.Print(node.value)
}

func (node *TreeNode) traverse() {
if node == nil {
return
}
node.left.traverse()
node.print()
node.right.traverse()
}

func main() {
root := TreeNode{value:3}
root.left = &TreeNode{}
root.right = &TreeNode{value: 5}
root.left.right = &TreeNode{value: 2}
root.right.left = &TreeNode{value: 4}
root.traverse()
}

执行结果 0 2 3 4 5

遍历树图解

遍历树

traverse()函数先遍历左边再遍历自己再遍历右边往复递归

封装和包

封装

定义完方法我们一般要进行封装

go语言名字一般使用CamelCase

方法结构常量等定义首字母大写代表:public(针对包),

方法结构常量等定义首字母小写代表:private(针对包),

每个目录只能有一个包,包名不一定和目录名一样

main包包含和执行入口

一个目录下面main函数只能有一个main包,否则我们需要给目录起一个其它的名字

为结构定义方法必须放在同一个包内,但是可以是不同的文件

扩展已有类型

扩展已有类型可以定义别名或者使用组合

包装TreeNode为MyTreeNode实现新的遍历方式这里使用的方式是组合

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
package main

import (
"fmt"
"world.ismyfree/go-learning/tree"
)

type MyTreeNode struct {
node *tree.Node
}

func (myNode *MyTreeNode) postOrder() {
if myNode == nil || myNode.node == nil {
return
}
left := MyTreeNode{myNode.node.Left}
left.postOrder()
right := MyTreeNode{myNode.node.Right}
right.postOrder()
myNode.node.Print()
}

func main() {
root := tree.Node{Value: 3}
root.Left = &tree.Node{}
root.Right = &tree.Node{Value: 5}
root.Left.Right = &tree.Node{Value: 2}
root.Right.Left = &tree.Node{Value: 4}
root.Traverse()
fmt.Println()
node := MyTreeNode{&root}
node.postOrder()
}

树还是之前的树,就不图解了,遍历结果为 2 0 4 5 3

定义slice的别名实现一个先进先出的队列

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
Queue.go
------------------------------------------------------
package queue

//定义一个队列,xian,它是一个int类型的slice
type Queue []int

//推进队列
func (q *Queue) Push(v int) {
*q = append(*q,v)
}

//弹出队列
func (q *Queue) Pop() int {
head := (*q)[0]
*q = (*q)[1:]
return head
}

//队列元素是否为空
func (q *Queue) IsEmpty() bool {
return len(*q) == 0
}
------------------------------------------------------


main.go
------------------------------------------------------
package main

import (
"fmt"
"world.ismyfree/go-learning/queue"
)

func main() {
q := queue.Queue{1}
q.Push(2)
q.Push(3)
fmt.Println(q.Pop())
fmt.Println(q.Pop())
fmt.Println(q.IsEmpty())
fmt.Println(q.Pop())
fmt.Println(q.IsEmpty())
}
------------------------------------------------------

打印结果

1
2
3
4
5
1
2
false
3
true

安装第三方包goimports

移动到一下目录%GOPATH%/src/golang.org/x从github上download以下包

1
git clone git@github.com:golang/tools.git
1
git clone git@github.com:golang/mod.git
1
git clone git@github.com:golang/xerrors.git

执行命令安装goimports

1
2
//请将%GOPATH%替换为绝对路径后执行命令
go install %GOPATH%/src/golang.org/x/tools/cmd/goimports

尝试使用第三方库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
"fmt"
"golang.org/x/tools/container/intsets"
)

func testSparse() {
sparse := intsets.Sparse{}
sparse.Insert(1)
sparse.Insert(2)
sparse.Insert(3)
sparse.Insert(4)
fmt.Println(sparse.Has(5))
fmt.Println(sparse.Has(4))
}

func main() {
testSparse()
}

GOPATH目录结构

go build进行编译

go install产生pkg文件和可执行文件

go run直接编译运行

src

git repository1
git repository2

pkg

git repository1
git repository2

bin

可执行文件1,2,3

面向接口

go语言的接口是使用者定义的

接口实现是隐式的,无需注明实现了哪个接口,只需拥有接口同样的方法

接口变量自带指针

接口变量同样采用值传递,几乎不需要使用接口的指针

指针接收者实现只能以指针的方式使用;值接收者两者都可以

接口变量强制转换用变量接收后后面加’,ok’,可以使用ok判断是否该类型,学名叫Type assertion

除了使用Type assertion方式判断类型,也可以使用switch case语句的方式来判断类型并执行相关逻辑

interface{} 可以表示任何类型

接口定义

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
mockretriever.go
------------------------------------------------------
package mock

type Retriever struct {
Contents string
}
//定义接口实现,无需指明实现了哪个接口,方法只需要实现接口中的方法
func (r Retriever) Get(url string) string {
return r.Contents
}
------------------------------------------------------


realretriever.go
------------------------------------------------------
package rel

import (
"net/http"
"net/http/httputil"
"time"
)

type Retriever struct {
UserAgent string
TimeOut time.Duration
}
//定义接口实现,无需指明实现了哪个接口,方法只需要实现接口中的方法
func (r Retriever) Get(url string) string {
resp, err := http.Get(url)
if err != nil {
panic(err)
}
result, err := httputil.DumpResponse(resp, true)
resp.Body.Close()
if err != nil {
panic(err)
}
return string(result)
}
------------------------------------------------------


main.go
------------------------------------------------------
package main

import (
"fmt"
"world.ismyfree/go-learning/retriever/mock"
"world.ismyfree/go-learning/retriever/rel"
)
//定义接口
type Retriever interface {
Get(url string) string
}

func download(r Retriever) string {
return r.Get("http://www.baidu.com")
}

func main() {
//接口变量(里面有是闲着类型/实现者的值或实现者的指针(它指向了一个实现者))
//不要使用接口变量的地址,因为接口变量本身可以包含指针,内部指向实现者即可
var r Retriever
//假的retriever
r = mock.Retriever{Contents: "this is a fack imooc.com"}
fmt.Println(download(r))
//真实的retriever
r = rel.Retriever{}
fmt.Println(download(r))
}

接口组合

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
mockretriever.go
------------------------------------------------------
package mock

type (
Retriever struct {
Contents string
}
)

func (r *Retriever) Post(url string, form map[string]string) string {
r.Contents = form["contents"]
return "ok"
}

func (r *Retriever) Get(url string) string {
return r.Contents
}
------------------------------------------------------

main.go
------------------------------------------------------
package main

import (
"fmt"
"world.ismyfree/go-learning/retriever/mock"
)

type Retriever interface {
Get(url string) string
}

type Poster interface {
Post(url string,form map[string]string) string
}

func download(r Retriever) string {
return r.Get("http://www.baidu.com")
}

func post(poster Poster) {
poster.Post("http:www.baidu.com", map[string]string{"name":"zhangsan","course":"java"})
}

type RetrieverPoster interface {
Retriever
Poster
}

const url = "http://www.baidu.com"
func session(s RetrieverPoster) string{
s.Post(url, map[string]string{"contents":"anather faked imooc.com"})
return s.Get(url)

}

func main() {
var r RetrieverPoster
r = &mock.Retriever{Contents: "this is a fack imooc.com"}
//type assertion
if mockRetriever,ok := r.(*mock.Retriever); ok{
fmt.Println(mockRetriever.Contents)
} else {
fmt.Println("not a mock retriever")
}
fmt.Println(session(r))
}

常用标准接口

该接口相当于其它语言中的toString

io.go —> io.Reader#Read(p []byte) (n int, err error)

文件、网络、byte、slice、string等等都会用Reader#Read

io.go —> io.Writer#Write(p []byte) (n int, err error)

文件、网络、byte、slice、string等等都会用Reader#Write

使用io.go —> io.Reader#Read(p []byte)代替file示例

这里不仅可以使用文件还可以使用其它字符串等数据类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func printFile(filename string) {
file, err := os.Open(filename)
if err != nil {
panic(err)
}
printFileContents(file)
}

func printFileContents(reader io.Reader) {
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
}

func main() {
printFile("basic/loop/abc.txt")
s := `abc"d"
kkkk
123
p`
printFileContents(strings.NewReader(s))

}

函数式编程

函数是一等公民:参数、变量、返回值等都可以是函数,可以给函数实现接口

高阶函数:函数的参数还是一个函数

函数—>闭包

正统函数式编程

不可变性:不能有状态,只有常量和函数
正统函数只能有一个参数
go语言式通用语言,不会再正统函数式做文章

使用函数式编程实现一个累加器(闭包)

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import "fmt"

//定义函数,返回值是一个函数
func adder() func(int) int {
sum := 0
//这里的v既是一个参数又是函数体的一个局部变量
return func(v int) int {
sum += v
//这里的sum是这个函数体外面定义的,他是一个自有变量
return sum
}
}

func main() {
//这里返回的是一个函数实例,每调用一次函数实例将对实例的返回值进行累加
funcAdder := adder()
for i := 0; i < 10; i++ {
fmt.Printf("0+...+%d = %d\n",i,funcAdder(i))
}
}

执行结果

1
2
3
4
5
6
7
8
9
10
0+...+0 = 0
0+...+1 = 1
0+...+2 = 3
0+...+3 = 6
0+...+4 = 10
0+...+5 = 15
0+...+6 = 21
0+...+7 = 28
0+...+8 = 36
0+...+9 = 45

闭包

自由变量不断地找与局部变量的联系然后整体返回一个闭包

以正统函数方式实现累加器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import "fmt"

type innerAdder func(int) (int, innerAdder)

func adder(base int) innerAdder {
return func(v int) (int, innerAdder) {
return base + v, adder(base + v)
}
}

func main() {
innerAdder := adder(0)
for i := 0; i < 10; i++ {
var s int
s, innerAdder = innerAdder(i)
fmt.Printf("0+...+%d = %d\n",i,s)
}
}

java中的闭包实现累加器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Test {
private static Function<Integer, Integer> adder() {
final Holder<Integer> sum = new Holder<>(0);
return (Integer value) -> {
sum.value += value;
return sum.value;
};
}
public static void main(String[] args) {
Function<Integer, Integer> adder = adder();
for (int i = 0; i < 10; i++) {
System.out.println(adder.apply(i));
}
}
}

java8后:使用Function接口和lambda表达式来创建函数对象

匿名类或者lambda表达式均支持闭包

斐波那契生成器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import "fmt"

//斐波那契生成器
func fibonacci() func() int {
a,b := 0,1
return func() int {
a,b = b,a+b
return a
}
}

func main() {
generate := fibonacci()
fmt.Println(generate())
fmt.Println(generate())
fmt.Println(generate())
fmt.Println(generate())
fmt.Println(generate())
}

实现io.Reader遍历斐波那契生成器中的数据

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
package main

import (
"bufio"
"fmt"
"io"
"strings"
)
//定义函数类型
type intGen func() int
//为函数类型实现接口
func (g intGen) Read(p []byte) (n int, err error) {
next := g()
//斐波那契生成器永远读不完做一个限制
if next > 10000 {
return 0,io.EOF
}
s := fmt.Sprintf("%d\n", next)
//TODO: 如果p太小则不正确
return strings.NewReader(s).Read(p)
}
//斐波那契生成器(替换定义类型,它实现了io.Reader接口)
func fibonacci() intGen {
a,b := 0,1
return func() int {
a,b = b,a+b
return a
}
}
//传递reader实现
func printFileContents(reader io.Reader) {
scanner := bufio.NewScanner(reader)
//遍历生成器中的数据
for scanner.Scan() {
fmt.Println(scanner.Text())
}
}
//主函数
func main() {
//实现reader的斐波那契生成器
generate := fibonacci()
//读取斐波那契生成器的数据
printFileContents(generate)
}

遍历二叉树传递遍历过程中的操作

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
node.go
------------------------------------------------------
package tree

import "fmt"

func (node Node) setVal(value int) {
node.Value = value
}
func (node *Node) setValue(value int) {
node.Value = value
}
//定义类型
type Node struct {
Value int
Left,Right *Node
}
//定义print
func (node Node) Print() {
fmt.Print(node.Value," ")
}
//遍历树
func (node *Node) Traverse() {
node.TraverseFunc(func(n *Node) {
if node == nil {
return
}
n.Print()
})
fmt.Println()
}
//遍历树同时做什么操作由传递的函数决定
func (node *Node) TraverseFunc(f func(node *Node)) {
if node == nil {
return
}
node.Left.TraverseFunc(f)
f(node)
node.Right.TraverseFunc(f)
}
------------------------------------------------------

main.go
------------------------------------------------------
package main

import (
"fmt"
"world.ismyfree/go-learning/tree"
)

func main() {
root := tree.Node{Value: 3}
root.Left = &tree.Node{}
root.Right = &tree.Node{Value: 5}
root.Left.Right = &tree.Node{Value: 2}
root.Right.Left = &tree.Node{Value: 4}
root.Traverse()
count := 0
root.TraverseFunc(func(node *tree.Node) {
count++
})
fmt.Println("Noce count",count)
}

错误处理与资源管理

defer调用

defer确保在函数结束时发生调用

参数在defer语句时计算(for loop 会栈执行)

defer调用里面时栈存储,先进后出

defer调用使用斐波那契生成器写入文件关闭资源操作

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
fib.go
------------------------------------------------------
package fib

import (
"fmt"
"io"
"strings"
)

type fibIntGen func() int

//为函数类型实现接口
func (g fibIntGen) Read(p []byte) (n int, err error) {
next := g()
//斐波那契生成器永远读不完做一个限制
if next > 10000 {
return 0, io.EOF
}
s := fmt.Sprintf("%d\n", next)
return strings.NewReader(s).Read(p)
}

func Fibonacci() fibIntGen {
a, b := 0, 1
return func() int {
a, b = b, a+b
return a
}
}
------------------------------------------------------


main.go
------------------------------------------------------
package main

import (
"bufio"
"fmt"
"os"
"world.ismyfree/go-learning/functional/fibonacci/fib"
)

func writeFile(filename string) {
file, err := os.Create(filename)
if err != nil {
panic(err)
}
//关闭文件资源
defer file.Close()
writer := bufio.NewWriter(file)
//将数据写入文件
defer writer.Flush()
fibonacci := fib.Fibonacci()
for i := 0; i < 20; i++ {
fmt.Fprintln(writer,fibonacci())
}
}

func main() {
writeFile("fib.txt")
}

由于defer调用是栈类型,writer.Flush()会比file.Close()先执行

defer在写代码的时候就想到创建file就要关闭,创建writer就要想到flush

何时使用defer调用

Open/Close

Lock/Unlock

PrintHeader/PrintFooter

错误处理

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
package main

import (
"bufio"
"fmt"
"os"
"world.ismyfree/go-learning/functional/fibonacci/fib"
)

func writeFile(filename string) {
// If there is an error, it will be of type *PathError.
file, err := os.OpenFile(filename, os.O_EXCL|os.O_CREATE, 0666)
if err != nil {
//如果是*os.PathError,打印指定信息
if pathError,ok := err.(*os.PathError); ok {
fmt.Println(pathError.Op,pathError.Path,pathError.Err)
} else {
//否则打印正常错误信息
panic(err)
}
}
defer file.Close()

writer := bufio.NewWriter(file)
defer writer.Flush()

fibonacci := fib.Fibonacci()
for i := 0; i < 20; i++ {
fmt.Fprintln(writer,fibonacci())
}
}

func main() {
writeFile("fib.txt")
}

统一错误处理

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
handler.go
------------------------------------------------------
package filelisting

import (
"io/ioutil"
"net/http"
"os"
)

//包装为http.HandleFunc(pattern string, handler func(ResponseWriter, *Request))的第二个参数
func HandleFileList(writer http.ResponseWriter, request *http.Request) error {
path := request.URL.Path[len("/list/"):]
file, err := os.Open(path)
//错误文件路径会控制台会报错,我们需要对错误进行统一处理
//panic serving [::1]:55751: open fib.txt1: The system cannot find the file specified.
/*if err != nil {
panic(err)
}*/
//内部错误展示给用户不太好,需要进行包装
/*if err != nil {
http.Error(writer,
err.Error(),
http.StatusInternalServerError)
return
}*/
//错误直接返回使用通用处理方法
if err != nil {
return err
}
defer file.Close()
content, err := ioutil.ReadAll(file)
if err != nil {
return err
}
writer.Write(content)
return nil
}
------------------------------------------------------


web.go
------------------------------------------------------
package main

import (
"net/http"
"os"
"world.ismyfree/go-learning/errhanding/filelistingserver/filelisting"
)

//定义函数类型对函数进行包装
type appHandler func(writer http.ResponseWriter, request *http.Request) error

/*
统一错误处理方法
1.http.HandleFun函数的第二个参数是一个func(ResponseWriter, *Request)
2.使用errWrapper方法对其进行包装返回一个func(ResponseWriter, *Request)
3.handler(writer,request)执行过程中如果报错就会返回err,然后对err进行处理
*/
func errWrapper(handler appHandler) func(http.ResponseWriter, *http.Request) {
//包装一个函数,返回一个函数,在返回一个无返回值的函数中进行处理
return func(writer http.ResponseWriter, request *http.Request) {
//这个handler实质就是filelisting.HandleFileList
err := handler(writer, request)
if err != nil {
//控制台打印错误信息
log.Warn("Error handling request: %s",err.Error())
//赋值默认值
code := http.StatusOK
switch {
//文件不存在code
case os.IsNotExist(err):
code = http.StatusNotFound
//权限不够禁止访问
case os.IsPermission(err):
code = http.StatusForbidden
//默认系统内部异常code
default:
code = http.StatusInternalServerError
}
//将错误消息响应给客户端
http.Error(writer, http.StatusText(code), code)
}
}
}

func main() {
http.HandleFunc("/list/", errWrapper(filelisting.HandleFileList))

err := http.ListenAndServe(":8888", nil)
if err != nil {
panic(err)
}
}

panic与recover

panic(恐慌)

看到panic想到这幅图(重)

panic是一个很重的词,尽量少用

停止当前函数执行(跟其它语言的throw Exception有点像)

一致向上返回,执行每一层的defer调用

如果没有遇见recover,程序退出

recover(恢复)

仅在defer调用中使用

在defer调用recover里面可以获取panic的值进行处理

如果在defer调用中无法处理panic的值,可以重新panic

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
package main

import (
"fmt"
)

func main() {
tryRecover()
fmt.Println("test")
}

func tryRecover() {
//匿名函数
defer func() {
//defer调用遇见recover
r := recover()
//如果是一个错误对错误进行处理
if err, ok := r.(error); ok {
fmt.Println("Error occurred:", err)
} else {
//否则重新panic
//panic(r)
panic(fmt.Sprintf("I don't know what to do: %v",err))
}
}() //大括号是匿名函数的函数体,我们需要匿名函数被调用在函数体后面需要加上小括号
//recover这个错误我们在defer中调用
//panic(errors.New("this is an error"))
//b := 0
//a := 5 / b
//fmt.Println(a)
panic(123)

}

对统一错误处理进行改进 使用dever调用recover

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
//http.HandleFunc调用到server.go文件中的func (c *conn) serve(ctx context.Context)方法走了defer调用recover
defer func() {
if err := recover(); err != nil && err != ErrAbortHandler {
const size = 64 << 10
buf := make([]byte, size)
buf = buf[:runtime.Stack(buf, false)]
c.server.logf("http: panic serving %v: %v\n%s", c.remoteAddr, err, buf)
}
if !c.hijacked() {
c.close()
c.setState(c.rwc, StateClosed)
}
}()
------------------------------------------------------


web.go
------------------------------------------------------
//我们手动处理更友好,处理未知错误,友好相应给客户端
package main

import (
"log"
"net/http"
"os"
"world.ismyfree/go-learning/errhanding/filelistingserver/filelisting"
)

//定义函数类型对函数进行包装
type appHandler func(writer http.ResponseWriter, request *http.Request) error

/*
统一错误处理方法
1.http.HandleFun函数的第二个参数是一个func(ResponseWriter, *Request)
2.使用errWrapper方法对其进行包装返回一个func(ResponseWriter, *Request)
3.handler(writer,request)执行过程中如果报错就会返回err,然后对err进行处理
*/
func errWrapper(handler appHandler) func(http.ResponseWriter, *http.Request) {
//包装一个函数,返回一个函数,在返回一个无返回值的函数中进行处理
return func(writer http.ResponseWriter, request *http.Request) {
//defer对未知错误进行处理!!!!!!!!!!!!!!
defer func() {
if r := recover(); r != nil {
log.Printf("Panic: %v", r)
http.Error(writer, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
}()

//这个handler实质就是filelisting.HandleFileList
err := handler(writer, request)
if err != nil {
//控制台打印错误信息
log.Printf("Error occurred handling request: %s", err.Error())
//赋值默认值
code := http.StatusOK
switch {
//文件不存在code
case os.IsNotExist(err):
code = http.StatusNotFound
//权限不够禁止访问
case os.IsPermission(err):
code = http.StatusForbidden
//默认系统内部异常code
default:
code = http.StatusInternalServerError
}
//将错误消息响应给客户端
http.Error(writer, http.StatusText(code), code)
}
}
}

func main() {
http.HandleFunc("/", errWrapper(filelisting.HandleFileList))

err := http.ListenAndServe(":8888", nil)
if err != nil {
panic(err)
}
}

自定义用户错误处理未知错误给前端友好提示(终极版)

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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
handler.go
------------------------------------------------------
package filelisting

import (
"io/ioutil"
"net/http"
"os"
"strings"
)

const prefix = "/list/"

type userError string
//error接口有一个Error方法相当于实现了error接口
func (e userError) Error() string {
return e.Message()
}
//userError包装了一个Message方法,这两个方法相当于实现了userError接口及error接口
func (e userError) Message() string {
return string(e)
}

//包装为http.HandleFunc(pattern string, handler func(ResponseWriter, *Request))的第二个参数
func HandleFileList(writer http.ResponseWriter, request *http.Request) error {
if strings.Index(request.URL.Path, prefix) != 0 {
//return errors.New("path mast start with :" + prefix)
return userError("path must start with: "+prefix)
}
path := request.URL.Path[len(prefix):]
file, err := os.Open(path)
//错误文件路径会控制台会报错,我们需要对错误进行统一处理
//panic serving [::1]:55751: open fib.txt1: The system cannot find the file specified.
/*if err != nil {
panic(err)
}*/
//内部错误展示给用户不太好,需要进行包装
/*if err != nil {
http.Error(writer,
err.Error(),
http.StatusInternalServerError)
return
}*/
//错误直接返回使用通用处理方法
if err != nil {
return err
}
defer file.Close()
content, err := ioutil.ReadAll(file)
if err != nil {
return err
}
writer.Write(content)
return nil
}
------------------------------------------------------


web.go
------------------------------------------------------
package main

import (
"log"
"net/http"
"os"
"world.ismyfree/go-learning/errhanding/filelistingserver/filelisting"
)

//定义函数类型对函数进行包装
type appHandler func(writer http.ResponseWriter, request *http.Request) error

/*
统一错误处理方法
1.http.HandleFun函数的第二个参数是一个func(ResponseWriter, *Request)
2.使用errWrapper方法对其进行包装返回一个func(ResponseWriter, *Request)
3.handler(writer,request)执行过程中如果报错就会返回err,然后对err进行处理
*/
func errWrapper(handler appHandler) func(http.ResponseWriter, *http.Request) {
//包装一个函数,返回一个函数,在返回一个无返回值的函数中进行处理
return func(writer http.ResponseWriter, request *http.Request) {
//defer对未知错误进行处理!!!!!!!!!!!!!!
defer func() {
if r := recover(); r != nil {
log.Printf("Panic: %v", r)
http.Error(writer, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
}()

//这个handler实质就是filelisting.HandleFileList
err := handler(writer, request)
if err != nil {
//控制台打印错误信息
log.Printf("Error occurred handling request: %s", err.Error())
//如果错误是希望展示给用户的错误
if userError, ok := err.(userError); ok {
http.Error(writer,userError.Message(),http.StatusBadRequest)
return
}

//赋值默认值
code := http.StatusOK
switch {
//文件不存在code
case os.IsNotExist(err):
code = http.StatusNotFound
//权限不够禁止访问
case os.IsPermission(err):
code = http.StatusForbidden
//默认系统内部异常code
default:
code = http.StatusInternalServerError
}
//将错误消息响应给客户端
http.Error(writer, http.StatusText(code), code)
}
}
}

//定义能给用户看的错误
type userError interface {
error
Message() string
}

func main() {
http.HandleFunc("/", errWrapper(filelisting.HandleFileList))

err := http.ListenAndServe(":8888", nil)
if err != nil {
panic(err)
}
}

测试与性能调优

表格驱动测试

方法名称以func TestXxx(t *testing.T)格式

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
package main

import (
"math"
"testing"
)

//测试方法参数需要添加*testing.T作为方法参数
func TestTriangle(t *testing.T) {
tests := []struct{ a, b, c int }{
{3, 4, 6},
{5, 12, 13},
{8, 15, 17},
{30000, 40000, 50000},
}
for _, element := range tests {
if actual := calcTriangle(element.a, element.b); actual != element.c {
t.Errorf("calcTriangle(%d,%d) got %d ; expected %d",element.a,element.b,actual,element.c)
}
}
}

//计算勾股定理
func calcTriangle(a int, b int) int {
var c int
c = int(math.Sqrt(float64(a*a + b*b)))
return c
}

打印结果
------------------------------------------------------
API server listening at: [::]:50325
=== RUN TestTriangle
add_test.go:17: calcTriangle(3,4) got 5 ; expected 6
--- FAIL: TestTriangle (0.00s)
FAIL

Debugger finished with exit code 0
------------------------------------------------------

查看代码覆盖率

先生成代码覆盖率文件

1
go test -coverprofile=c.out

使用可视化工具查看文件

1
go tool cover -html=c.out

代码覆盖率可视化查看

红色是没有覆盖的代码

绿色是覆盖的代码

代码性能测试

方法名称以func Benchmark(b *testing.B)格式

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
package main

import (
"math"
"testing"
)
//性能测试代码
func BenchmarkSubStr(b *testing.B) {
s := "黑化肥挥发发灰会花飞灰化肥挥发发黑会飞花"
ans := 8
//b.N让系统决定代码性能执行的次数
for i := 0; i < b.N; i++ {
actual := lengthOfLongestSubstring(s)
if actual != ans {
b.Errorf("got %d for input %s; expected %d", actual,s,ans)
}
}
}
//需要进行性能测试的方法快
func lengthOfLongestSubstring(s string) int {
//记录每个字母最后出现的位置
lastOccurred := make(map[rune]int)
start := 0
maxLenght := 0
for i,ch := range []rune(s) {
//有可能不存在这个map获取出来的值是0,而0又是一个合法的下标,所以要判断这个key是否存在
if lastI,ok := lastOccurred[ch] ; ok && lastI >= start {
start = lastOccurred[ch] +1
}
if i - start +1 > maxLenght {
maxLenght = i - start + 1
}
lastOccurred[ch] = i
}
return maxLenght
}

打印结果
------------------------------------------------------
API server listening at: [::]:62673
goos: windows
goarch: amd64
pkg: world.ismyfree/go-learning/basic/loop
BenchmarkSubStr
BenchmarkSubStr-8 523216 2315 ns/op
PASS

Debugger finished with exit code 0
------------------------------------------------------

也可以使用命令行对代码进行测试

1
go test -bench .

生成性能测试生成cpu报告

1
go test -bench . -cpuprofile cpu.out

使用pprof工具查看cpu.out报告文件

输入命令进入工具命令行

1
go tool pprof cpu.out

进入命令行工具后输入web命令即可以图形化网页形式展示性能问题,针对问题进行调优

输入help可查看帮助命令

各种报错没能成功,有时间再研究

http测试

仅对函数进行测试(更像单元测试)

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
errorwrapper_test.go
------------------------------------------------------
package main

import (
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
)

type testingUserError string

//error接口有一个Error方法相当于实现了error接口
func (e testingUserError) Error() string {
return e.Message()
}

//testingUserError包装了一个Message方法,这两个方法相当于实现了testingUserError接口及error接口
func (e testingUserError) Message() string {
return string(e)
}

func errPanic(writer http.ResponseWriter, request *http.Request) error {
panic(123)
}

func errUserError(writer http.ResponseWriter, request *http.Request) error {
return testingUserError("user error")
}

func errNotFound(writer http.ResponseWriter, request *http.Request) error {
return os.ErrNotExist
}

func errNoPermission(writer http.ResponseWriter, request *http.Request) error {
return os.ErrPermission
}

func errUnknown(writer http.ResponseWriter, request *http.Request) error {
return errors.New("unknown error")
}

func noError(writer http.ResponseWriter, request *http.Request) error {
fmt.Fprintln(writer, "no error")
return nil
}

func TestErrWrapper(t *testing.T) {
tests := []struct {
h appHandler
code int
message string
}{
{errPanic, 500, "Internal Server Error"},
{errUserError, 400, "user error"},
{errNotFound, 404, "Not Found"},
{errNoPermission, 403, "Forbidden"},
{errUnknown, 500, "Internal Server Error"},
{noError,200,"no error"},
}
for _, tt := range tests {
f := errWrapper(tt.h)
response := httptest.NewRecorder()
request := httptest.NewRequest(http.MethodGet, "http://localhost:8888/list/fib.txt", nil)
f(response, request)
b, _ := ioutil.ReadAll(response.Body)
body := strings.Trim(string(b), "\n")
if response.Code != tt.code || body != tt.message {
t.Errorf("expect (%d,%s); got (%d, %s)", tt.code, tt.message, response.Code, body)
}

}
}
------------------------------------------------------
响应结果
------------------------------------------------------
API server listening at: [::]:50904
=== RUN TestErrWrapper
2020/12/28 17:26:00 Panic: 123
2020/12/28 17:26:00 Error occurred handling request: user error
2020/12/28 17:26:00 Error occurred handling request: file does not exist
2020/12/28 17:26:00 Error occurred handling request: permission denied
2020/12/28 17:26:00 Error occurred handling request: unknown error
--- PASS: TestErrWrapper (0.01s)
PASS

Debugger finished with exit code 0
------------------------------------------------------

开一个服务器对http进行测试(集成度更高)

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
errorwrapper_test.go
------------------------------------------------------
package main

import (
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
)

type testingUserError string

//error接口有一个Error方法相当于实现了error接口
func (e testingUserError) Error() string {
return e.Message()
}

//testingUserError包装了一个Message方法,这两个方法相当于实现了testingUserError接口及error接口
func (e testingUserError) Message() string {
return string(e)
}

func errPanic(writer http.ResponseWriter, request *http.Request) error {
panic(123)
}

func errUserError(writer http.ResponseWriter, request *http.Request) error {
return testingUserError("user error")
}

func errNotFound(writer http.ResponseWriter, request *http.Request) error {
return os.ErrNotExist
}

func errNoPermission(writer http.ResponseWriter, request *http.Request) error {
return os.ErrPermission
}

func errUnknown(writer http.ResponseWriter, request *http.Request) error {
return errors.New("unknown error")
}

func noError(writer http.ResponseWriter, request *http.Request) error {
fmt.Fprintln(writer, "no error")
return nil
}

func TestErrWrapperInServer(t *testing.T) {
tests := []struct {
h appHandler
code int
message string
}{
{errPanic, 500, "Internal Server Error"},
{errUserError, 400, "user error"},
{errNotFound, 404, "Not Found"},
{errNoPermission, 403, "Forbidden"},
{errUnknown, 500, "Internal Server Error"},
{noError, 200, "no error"},
}
for _, tt := range tests {
f := errWrapper(tt.h)
server := httptest.NewServer(http.HandlerFunc(f))
resp, _ := http.Get(server.URL)
verifyResponse(resp,tt.code,tt.message,t)
}
}

func verifyResponse(resp *http.Response,expectCode int,expectMessage string,t *testing.T) {
b, _ := ioutil.ReadAll(resp.Body)
body := strings.Trim(string(b), "\n")
if resp.StatusCode != expectCode || body != expectMessage {
t.Errorf("expect (%d,%s); got (%d, %s)", expectCode, expectMessage, resp.StatusCode, body)
}
}

查看接口文档

需要安装godoc工具

1
go install golang.org/x/tools/cmd/godoc

执行命令查看接口文档

1
godoc -http=:6060

打开本地端口网页6060查看(所有接口文档,包含本地及官方)

web页面接口文档

并发编程

协程Coroutine

轻量级”线程”

非抢占式多任务处理,由协程主动交出控制权

编译器/解释器/虚拟机层的多任务

多个协程可以在一个或者多个线程上运行

子程序是协程的一个特例(协程是比子程序更宽泛的一个概念)

协程与普通函数的对比

协程简单演示go func

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
package main

import (
"fmt"
"runtime"
"time"
)

func main() {
var a [10]int
for i := 0; i < 10; i++ {
//协程
go func(i int) {
for true {
//id操作协程之间会切换
//fmt.Printf("hello from goroutine %d",i)
a[i]++
//手动交出协程控制权
runtime.Gosched()
}
}(i)
}
time.Sleep(time.Millisecond)
fmt.Println(a)
}

此段代码有数据访问冲突,可以使用以下命令进行检测

1
go run -race xxx.go

命令执行结果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
==================
WARNING: DATA RACE
//读
Read at 0x00c0001300f0 by main goroutine:
main.main()
     D:/goProject/src/world.ismyfree/go-learning/goroutine/goroutine.go:24 +0x104
//写
Previous write at 0x00c0001300f0 by goroutine 7:
main.main.func1()
D:/goProject/src/world.ismyfree/go-learning/goroutine/goroutine.go:17 +0x6f

Goroutine 7 (running) created at:
main.main()
D:/goProject/src/world.ismyfree/go-learning/goroutine/goroutine.go:13 +0xca
==================
[4400 3695 3707 3321 3159 2643 2964 2641 2969 2490]
Found 1 data race(s)
exit status 66

goroutine

goroutine抽象图

任何函数只要加上go就能送给调度器运行

不需要在定义是区分是否是异步函数

调度器在合适的点进行切换

使用-race可以检测数据访问冲突

调度器会将各个协程映射到物理机的各个线程

goroutine可能切换的参考点

I/O,select (如print)
d
channel

等待锁

函数调用(有时)

runtime.Gosched()(手动交出协程控制权)

channel

channel

goroutine与goroutine之间双向的通道就是channel

channel是goroutine与goroutine之间的交互,一个goroutine发送数据就要有一个goroutine接收数据,没有goroutine接收数据会发生dadelock

channel

channel
buffered channel
range

理论基础:Communication Sequential Process (CSP模型)

不要通过共享内存来通信;通过通信来共享内存

channel的示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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
package main

import (
"fmt"
"time"
)

//worker是一个goroutine
func worker(id int, c chan int) {
/*for true {
//从channel中获取数据并打印(一般打印的数据都是乱序,调度器随意选择调度的goroutine)
if n, ok := <-c; !ok {
break
} else {
fmt.Printf("worker %d received %c\n", id, n)
}
}*/
for n := range c {
fmt.Printf("worker %d received %c\n", id, n)
}
}

//创建worker
func createWorker(id int) chan<- int {
c := make(chan int)
//这部分操作需要交给一个goroutine
go worker(id, c)
//返回一个channel
return c
}

//channel示例代码
func chanDemo() {
var channels [10]chan<- int
for i := 0; i < 10; i++ {
//创建一个channel(channel是一等公民,能作为参数也能作为返回值)
//channels[i] = make(chan int)
//创建一个goroutine接收channel中的数据
channels[i] = createWorker(i)
}
//往channel里面发送数据
for i := 0; i < 10; i++ {
channels[i] <- 'a' + i
}
//往channel里面发送数据
for i := 0; i < 10; i++ {
channels[i] <- 'A' + i
}
time.Sleep(time.Millisecond)
}

//bufferedChannel
func bufferedChannel() {
c := make(chan int, 3)
go worker(0, c)
c <- 'A'
c <- 'B'
c <- 'C'
c <- 'C'
c <- 'C'
time.Sleep(time.Millisecond)
}

//bufferedChannel
func channelClose() {
c := make(chan int, 3)
go worker(0, c)
c <- 'A'
c <- 'B'
c <- 'C'
c <- 'C'
c <- 'C'
close(c)
time.Sleep(time.Millisecond)
}

//main
func main() {
fmt.Println("Channel as first-class citizen")
chanDemo()
fmt.Println("Buffered channel")
bufferedChannel()
fmt.Println("Channel close and range")
channelClose()
}

channel示例优化(通过通信来共享内存)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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
package main

import (
"fmt"
)

//worker是一个goroutine
func doWorker(id int, c chan int, done chan bool) {
/*for true {
//从channel中获取数据并打印(一般打印的数据都是乱序,调度器随意选择调度的goroutine)
if n, ok := <-c; !ok {
break
} else {
fmt.Printf("worker %d received %c\n", id, n)
}
}*/
for n := range c {
fmt.Printf("worker %d received %c\n", id, n)
//通过通信来共享内存通知外面打印完成
//done <- true
//channel发送数据需要在另外的goroutine中做,否则会发生阻塞
//go func() {done<-true}()
done <- true
}
}

//创建worker
func createWorker(id int) worker {
w := worker{
in: make(chan int),
done: make(chan bool),
}
//c := make(chan int)
//这部分操作需要交给一个goroutine
go doWorker(id, w.in, w.done)
//返回一个channel
return w
}

type worker struct {
in chan int
done chan bool
}

//channel示例代码
func chanDemo() {
var workers [10]worker
for i := 0; i < 10; i++ {
//创建一个channel(channel是一等公民,能作为参数也能作为返回值)
//channels[i] = make(chan int)
//创建一个goroutine接收channel中的数据
workers[i] = createWorker(i)
}
//往channel里面发送数据
for i := 0; i < 10; i++ {
workers[i].in <- 'a' + i
//现在没有sleep也能打印完成,但是是顺序打印的
//<-workers[i].done
}
for _, worker := range workers {
<-worker.done
}
//往channel里面发送数据
for i := 0; i < 10; i++ {
workers[i].in <- 'A' + i
//现在没有sleep也能打印完成,但是是顺序打印的
//<-workers[i].done
}
for _, worker := range workers {
<-worker.done
}

/*for _,worker := range workers{
<-worker.done
<-worker.done
}*/
//sleep的方式很不好,我们需要通知到外面已经打印完成
//time.Sleep(time.Millisecond)
}

//main
func main() {
chanDemo()
}

使用go提供的waitGroup改进示例

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
package main

import (
"fmt"
"sync"
)

//worker是一个goroutine
func doWorker(id int, c chan int, wg *sync.WaitGroup) {
/*for true {
//从channel中获取数据并打印(一般打印的数据都是乱序,调度器随意选择调度的goroutine)
if n, ok := <-c; !ok {
break
} else {
fmt.Printf("worker %d received %c\n", id, n)
}
}*/
for n := range c {
fmt.Printf("worker %d received %c\n", id, n)
//通过通信来共享内存通知外面打印完成
//done <- true
//channel发送数据需要在另外的goroutine中做,否则会发生阻塞
//go func() {done<-true}()
//done <- true
wg.Done()
}
}

//创建worker
func createWorker(id int,wg *sync.WaitGroup) worker {
w := worker{
in: make(chan int),
wg: wg,
}
//c := make(chan int)
//这部分操作需要交给一个goroutine
go doWorker(id, w.in, wg)
//返回一个channel
return w
}

type worker struct {
in chan int
wg *sync.WaitGroup
}

//channel示例代码
func chanDemo() {
//go提供的等待多任务完成的方法
var wg sync.WaitGroup
var workers [10]worker
for i := 0; i < 10; i++ {
//创建一个channel(channel是一等公民,能作为参数也能作为返回值)
//channels[i] = make(chan int)
//创建一个goroutine接收channel中的数据
workers[i] = createWorker(i,&wg)
}
//往channel里面发送数据
for i := 0; i < 10; i++ {
workers[i].in <- 'a' + i
//现在没有sleep也能打印完成,但是是顺序打印的
//<-workers[i].done
wg.Add(1)
}
//往channel里面发送数据
for i := 0; i < 10; i++ {
workers[i].in <- 'A' + i
//现在没有sleep也能打印完成,但是是顺序打印的
//<-workers[i].done
wg.Add(1)
}
wg.Wait()
}

//main
func main() {
chanDemo()
}

使用函数式编程对示例进行抽象重构

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
package main

import (
"fmt"
"sync"
)

//worker是一个goroutine
func doWorker(id int, w worker) {
/*for true {
//从channel中获取数据并打印(一般打印的数据都是乱序,调度器随意选择调度的goroutine)
if n, ok := <-c; !ok {
break
} else {
fmt.Printf("worker %d received %c\n", id, n)
}
}*/
for n := range w.in {
fmt.Printf("worker %d received %c\n", id, n)
//通过通信来共享内存通知外面打印完成
//done <- true
//channel发送数据需要在另外的goroutine中做,否则会发生阻塞
//go func() {done<-true}()
//done <- true
w.done()
}
}

//创建worker
func createWorker(id int, wg *sync.WaitGroup) worker {
w := worker{
in: make(chan int),
done: func() {
wg.Done()
},
}
//c := make(chan int)
//这部分操作需要交给一个goroutine
go doWorker(id, w)
//返回一个channel
return w
}

type worker struct {
in chan int
done func()
}

//channel示例代码
func chanDemo() {
//go提供的等待多任务完成的方法
var wg sync.WaitGroup
var workers [10]worker
for i := 0; i < 10; i++ {
//创建一个channel(channel是一等公民,能作为参数也能作为返回值)
//channels[i] = make(chan int)
//创建一个goroutine接收channel中的数据
workers[i] = createWorker(i, &wg)
}
//往channel里面发送数据
for i := 0; i < 10; i++ {
workers[i].in <- 'a' + i
//现在没有sleep也能打印完成,但是是顺序打印的
//<-workers[i].done
wg.Add(1)
}
//往channel里面发送数据
for i := 0; i < 10; i++ {
workers[i].in <- 'A' + i
//现在没有sleep也能打印完成,但是是顺序打印的
//<-workers[i].done
wg.Add(1)
}
wg.Wait()
}

//main
func main() {
chanDemo()
}

使用channel实现树的遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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
Node.go
------------------------------------------------------
package tree

import "fmt"

func (node Node) setVal(value int) {
node.Value = value
}
func (node *Node) setValue(value int) {
node.Value = value
}

//定义类型
type Node struct {
Value int
Left, Right *Node
}

//定义print
func (node Node) Print() {
fmt.Print(node.Value, " ")
}

//遍历树
func (node *Node) Traverse() {
node.TraverseFunc(func(n *Node) {
if node == nil {
return
}
n.Print()
})
fmt.Println()
}

//遍历树同时做什么操作由传递的函数决定
func (node *Node) TraverseFunc(f func(node *Node)) {
if node == nil {
return
}
node.Left.TraverseFunc(f)
f(node)
node.Right.TraverseFunc(f)
}

//使用channel对树进行遍历
func (node *Node) TraverseWithChannel() chan *Node {
out := make(chan *Node)
go func() {
node.TraverseFunc(
func(node *Node) {
out <- node
},
)
close(out)
}()
return out
}
------------------------------------------------------

main.go
------------------------------------------------------
package main

import (
"fmt"
"world.ismyfree/go-learning/tree"
)

func main() {
root := tree.Node{Value: 3}
root.Left = &tree.Node{}
root.Right = &tree.Node{Value: 5}
root.Left.Right = &tree.Node{Value: 2}
root.Right.Left = &tree.Node{Value: 4}
root.Traverse()
count := 0
root.TraverseFunc(func(node *tree.Node) {
count++
})
fmt.Println("Node count: ", count)

//使用channel遍历树获取最大值
c := root.TraverseWithChannel()
maxNode := 0
for node := range c {
if node.Value > maxNode {
maxNode = node.Value
}
}
fmt.Println("Max node value:", maxNode)
}

使用select方式对chnnel进行调度

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
package main

import (
"fmt"
"math/rand"
"time"
)

//worker是一个goroutine
func worker(id int, c chan int) {
/*for true {
//从channel中获取数据并打印(一般打印的数据都是乱序,调度器随意选择调度的goroutine)
if n, ok := <-c; !ok {
break
} else {
fmt.Printf("worker %d received %c\n", id, n)
}
}*/
for n := range c {
time.Sleep(time.Second)
fmt.Printf("worker %d received %d\n", id, n)
}
}

//创建worker
func createWorker(id int) chan<- int {
c := make(chan int)
//这部分操作需要交给一个goroutine
go worker(id, c)
//返回一个channel
return c
}

func generator() chan int {
out := make(chan int)
go func() {
i := 0
for true {
time.Sleep(time.Duration(rand.Intn(1500)) * time.Millisecond)
out <- i
i++
}
}()
return out
}

func main() {
var c1, c2 = generator(), generator()
var worker = createWorker(0)
var values []int
//10s后往这个channel发送时间
afterTenSecond := time.After(10 * time.Second)
//每秒往这个
tick := time.Tick(time.Second)
for true {
var activeWorker chan<- int
var activeValue int
if len(values) > 0 {
activeWorker = worker
activeValue = values[0]
}
select {
case n := <-c1:
values = append(values, n)
case n := <-c2:
values = append(values, n)
case activeWorker <- activeValue:
values = values[1:]
case <-time.After(500 * time.Millisecond):
//每次进寻魂生成数据超过500毫秒打印time out
fmt.Println("time out")
case <-tick:
fmt.Println("queue len = ", len(values))
case <-afterTenSecond:
//10s后冲这个channel获取到数据结束程序
fmt.Println("Bye")
return
}
}
}

传统同步机制

WaitGroup

Mutex(互斥量)

cond(条件变量)

Mutex示例(真实操作请使用系统库提供的原子操作,这里仅作为示例)

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
package main

import (
"fmt"
"sync"
"time"
)

type atomicInt struct {
value int
lock sync.Mutex
}

func (a *atomicInt) increment() {
fmt.Println("safe increment")
func(){
a.lock.Lock()
defer a.lock.Unlock()
a.value++
}()
}

func (a *atomicInt) get() int {
a.lock.Lock()
defer a.lock.Unlock()
return a.value
}

func main() {
var a atomicInt
a.increment()
go func() {
a.increment()
}()
time.Sleep(time.Millisecond)
fmt.Println(a.get())
}

http及其它标准库

httpClient示例

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
package main

import (
"fmt"
"net/http"
"net/http/httputil"
)

func main() {
request, err := http.NewRequest(http.MethodGet, "https://www.imooc.com", nil)
//控制请求的头部信息
request.Header.Add("User-Agent","Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1")
//自己构建客户端打印重定向时候的操作
client := http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
fmt.Println("Redirect", req)
return nil
},
}
//我们使用自己构建的client
resp, err := client.Do(request)
//resp, err := http.DefaultClient.Do(request)
if err != nil {
panic(err)
}
defer resp.Body.Close()
//使用httputil简化工作
s, err := httputil.DumpResponse(resp, true)
if err != nil {
panic(err)
}
fmt.Printf("%s\n",s)
}

httpServer性能分析

在程序中import _ “net/http/pprof”

在浏览器中打开接口路径为/debug/pprof/的请求

http pprof工具

使用命令行pprof工具获得程序30秒的性能访问权限(30秒内随意访问服务的接口)

1
go tool pprof http://localhost:8888/debug/pprof/profile

以上命令操作完后输入web以图形化界面进行分析程序(需要安装Graphviz)

也可以使用如下命令查看内存使用情况

1
go tool pprof http://localhost:8888/debug/pprof/profile

其它标准库

常用标准库

bufio

log

encoding/json

regexp

time

strings/math/rand

起一个服务查看标准库文档

1
godoc -http=:6060

中文标准库网址

点此进行跳转go中文api

迷宫的广度优先搜索

迷宫示意图

0代表可以走的地方

1代表墙

人为规定入口和出口为左上角和右下角

思路

想想我们在一个未知的世界

第0步

顺序探索并放入未探索队列当中(上左下右)

第1步

每探索一个移除未探索队列并将新探索到未探索的加入队列中

第2步上

第2步左

第2步下

第2步右

代码实现

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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
maze.in
------------------------------------------------------
6 5
0 1 0 0 0
0 0 0 1 0
0 1 0 1 0
1 1 1 0 0
0 1 0 0 1
0 1 0 0 0
------------------------------------------------------
maze.go
------------------------------------------------------
package main

import (
"fmt"
"os"
)

//读取迷宫文件
func readMaze(filename string) [][]int {
file, err := os.Open(filename)
if err != nil {
panic(err)
}
//定义行列临时变量
var row, col int
//扫描第一行获取文件的行列数(\n读换行)
fmt.Fscanf(file, "%d %d\n", &row, &col)
//创建第一维slice
maze := make([][]int, row)
for i := range maze {
//创建第二维slice
maze[i] = make([]int, col)
//将数据放到二维slice中的每一个元素中
for j := range maze[i] {
a := &maze[i][j]
if j == len(maze[i])-1 {
//(\n读换行)
fmt.Fscanf(file, "%d\n", a)
} else {
fmt.Fscanf(file, "%d", a)
}
}
}
return maze
}

type point struct {
i, j int
}

//移动(上左下右)
var dirs = [4]point{
{-1, 0},
{0, -1},
{1, 0},
{0, 1},
}

//移动add
func (p point) add(r point) point {
return point{p.i + r.i, p.j + r.j}
}

//at校验当前位置
func (p point) at(grid [][]int) (int, bool) {
//往上走或者往下走越界的时候
if p.i < 0 || p.i >= len(grid) {
return 0, false
}
//往左走或者往右走越界的时候
if p.j < 0 || p.j >= len(grid[p.i]) {
return 0, false
}
return grid[p.i][p.j],true
}

//走迷宫
func walk(maze [][]int, start, end point) [][]int{
//要走的每一步
steps := make([][]int, len(maze))
for i := range steps {
steps[i] = make([]int, len(maze[i]))
}
//将起点放入队列当中
Q := []point{start}

for len(Q) > 0 {
//取出队列中当前的位置作为要探索的点
cur := Q[0]
Q = Q[1:]
//判断当前探索点是否是终点
if cur == end {
break
}
for _, dir := range dirs {
next := cur.add(dir)
//如果越界或者撞墙,继续探索
val, ok := next.at(maze)
if !ok || val == 1 {
continue
}
//如果当前有值说明已经有值了,继续探索
val, ok = next.at(steps)
if !ok || val != 0 {
continue
}
//如果当前点在起点继续探索
if next == start {
continue
}
//探索(需要做的事:1.探索到的下一步填上是第几步2.将探索到的下一步加入到队列)
curSteps, _ := cur.at(steps)
steps[next.i][next.j] = curSteps + 1
Q = append(Q, next)
}
}
return steps
}

func main() {
maze := readMaze("maze/maze.in")
fmt.Println("print maze")
fmt.Println("-----------------------")
//打印读取出来的数据
for _, row := range maze {
for _, val := range row {
fmt.Printf("%d ", val)
}
fmt.Println()
}
fmt.Println("-----------------------")
fmt.Println("print steps")
fmt.Println("-----------------------")
//走迷宫
steps := walk(maze, point{0, 0}, point{len(maze) - 1, len(maze[0]) - 1})
for _,row := range steps {
for _, val := range row {
fmt.Printf("%3d ",val)
}
fmt.Println()
}
fmt.Println("-----------------------")
}

打印结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

print maze
-----------------------
0 1 0 0 0
0 0 0 1 0
0 1 0 1 0
1 1 1 0 0
0 1 0 0 1
0 1 0 0 0
-----------------------
print steps
-----------------------
0 0 4 5 6
1 2 3 0 7
2 0 4 0 8
0 0 0 10 9
0 0 12 11 0
0 0 13 12 13
-----------------------

如果本文对你有所帮助,请赏我1个铜板买喵粮自己吃,您的支持是我最大的动力!!!