0%

Golang练习笔记

定义变量

空值

1
2
3
4
5
func variableZeroValue() {
var a int
var s string
fmt.Printf("%d %q\n", a, s)
}

初始化

1
2
3
4
5
func variableInitialValue() {
var a, b int = 3, 4
var s string = "abc"
fmt.Println(a, b, s)
}

类型推断

1
2
3
4
5
func variableTypeDeduction() {
var a, b, c, d = 3, 4, true, "def"
var s string = "abc"
fmt.Println(a, b, c, d, s)
}

利用冒号定义的简单写法

1
2
3
4
5
6
func variableShorter() {
a, b, c, d := 3, 4, true, "def"
b = 6
var s string = "abc"
fmt.Println(a, b, c, d, s)
}

函数外的变量定义(不能使用冒号定义,且它的作用域只在该包内部)

1
2
3
var aa = 55
var ss = "llll"
var bb = true

或者

1
2
3
4
5
var (
bbb = 666
sss = "jjj"
ttt = false
)

内建变量类型

bool, string,

(u)int, (u)int8, (u)int16, (u)int32, (u)int64, uintptr,

byte, rune,

float32, float64, complex64, complex128,

常量与枚举

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
 func consts() {
const filename = "abc.txt"
const a, b = 3, 4
var c int
c = int(math.Sqrt(a*a + b + b))
fmt.Println(filename, c)
}

func enums() {
const (
cpp = iota
java
golang
swift
_
C
)

const (
b = 1 << (10 * iota)
bb
bbb
bbbb
)

fmt.Println(cpp, java, golang, C)
fmt.Println(b, bb, bbb, bbbb)
}
1
2
3
abc.txt 4
0 1 2 5
1 1024 1048576 1073741824

条件判断

if的条件里可以赋值
if的条件里赋值的变量作用域就在这个if语句里

1
2
3
4
5
6
7
8
func main() {
const filename = "abc.txt"
if contents, err := ioutil.ReadFile(filename); err != nil {
fmt.Println(err)
} else {
fmt.Println("%s\n", contents)
}
}

switch后面可以没有表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func grade(score int) string {
g := ""
switch {
case score < 0 || score > 100:
panic(fmt.Sprintf("wrong score: %d", score))
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

1
2
3
4
5
6
7
8
func convertToBin(n int) string {
res := ""
for ; n > 0; n /= 2 {
lsb := n % 2
res = strconv.Itoa(lsb) + res
}
return res
}
1
2
3
4
5
6
7
8
9
10
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)
}
}

死循环

1
2
3
4
5
func forever() {
for {
fmt.Println("abc")
}
}

函数

  • 返回值类型写在后面
  • 可返回多个值
  • 函数作为参数
  • 没有默认参数,有可选参数
1
2
3
4
5
6
func div(a, b int) (q, r int) {
// return a / b, a % b
q = a / b
r = a % b
return q, r
}
1
2
3
4
5
6
7
8
9
10
func pow(a, b int) int {
return int(math.Pow(float64(a), float64(b)))
}

func main() {
q, r := div(8, 6)
fmt.Println(q, r)

fmt.Println(apply(pow, 1, 5))
}

或者以匿名函数的形式进行

1
2
3
4
5
6
7
8
func main() {

fmt.Println(apply(
func(a int, b int) int {
return int(math.Pow(
float64(a), float64(b)))
}, 1, 5))
}

函数作为参数

1
2
3
4
5
6
7
8
func apply(op func(int, int) int, a, b int) int {
p := reflect.ValueOf(op).Pointer()
opName := runtime.FuncForPC(p).Name()
fmt.Printf("Calling function %s with args "+
"(%d, %d)\n", opName, a, b)

return op(a, b)
}

可变参数

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

指针

1
2
3
4
5
6
func exp() {
var a int = 2
var pa *int = &a
*pa = 3
fmt.Println(a)
}

参数传递

  • go语言只有值传递一种方式
    var a int 当参数传递给fun f(pa *int)它其实就是将a的指针&a拷贝一份传递给pa,pa也跟&a一样,同时指向a

  • 再如:var cache Cache 当参数传递给func f(cache Cache),参数Cache那么大不可能拷贝一份的,实际上Cache也只是一个指向data的指针,所以cache当参数的时候,它只是拷贝了一份指针,它同时也指向了data

交换两个变量的值

1
2
3
4
5
6
7
8
9
10
func swap(a, b int) {
b, a = a, b
}
func main() {
exp()

a, b := 3, 4
swap(a, b)
println(a, b)
}

输出

1
2
3
➜  hello go run point.go
3
3 4

我们发现值没有变,这是需要使用指针,将a,b所指向的值改变

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func exp() {
var a int = 2
var pa *int = &a
*pa = 3
fmt.Println(a)
}

func swap(a, b *int) {
*b, *a = *a, *b
}
func main() {
exp()

a, b := 3, 4
swap(&a, &b)
println(a, b)
}

数组

数组的定义

1
2
3
4
5
6
7
8
9
10
func exp() {
var arr1 [5]int
arr2 := [3]int{1, 2, 3} //冒号等于的定义,需要给数组一个初值
arr3 := [...]int{1, 2, 3, 4} //或者不指定数组个数
fmt.Println(arr1, arr2, arr3)

//二维数组定义
var grid [4][5]int
fmt.Println(grid)
}

数组的遍历

1
2
3
for i := 0; i < len(arr3); i++ {
fmt.Println(arr3[i])
}

下标

1
2
3
for i := range arr3 {
fmt.Println(arr3[i])
}

1
2
3
for _, v := range arr3 {
fmt.Println(v)
}

下标和值

1
2
3
for i, v := range arr3 {
fmt.Println(i, v)
}

数组是值类型的,所以当参数用的时候需要指定长度cannot use arr1 (type [5]int) as type [4]int in argument to printArray,且在函数里面再次赋值后,它在外面是不会改变的

1
2
3
4
5
6
func printArray(arr [4]int) {
for i, v := range arr {
fmt.Println(i, v)
}
arr[0] = 100 //在外面调用时,依旧是1
}

所以,[10]int和[20]int 是不同类型;调用func f(arr [10]int)会拷贝数组。需要改变它就需要用到指针了

1
2
3
4
5
6
7
8
9
10
11
12
13
func exp() {
arr3 := [...]int{1, 2, 3, 4}

printArray(&arr3)
fmt.Println(arr3, arr3[0])
}

func printArray(arr *[4]int) {
for i, v := range arr {
fmt.Println(i, v)
}
arr[0] = 100 //在外面调用时,改变为100
}

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68

func updateSlice(s []int){
s[0] = 100
}



func main() {

arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
//s := arr[2:6]
fmt.Println("arr[2:6] =", arr[2:6])#arr[2:6] = [2 3 4 5]

fmt.Println("arr[:6] =", arr[:6])#arr[:6] = [0 1 2 3 4 5]

fmt.Println("arr[2:] =", arr[2:])#arr[2:] = [2 3 4 5 6 7]

fmt.Println("arr[:] =", arr[:])#arr[:] = [0 1 2 3 4 5 6 7]


s1 := arr[2:]
s2 := arr[:]

fmt.Println("after updateSlice(s1")
updateSlice(s1)
fmt.Println(s1)
fmt.Println(arr)

# after updateSlice(s1
# [100 3 4 5 6 7]
# [0 1 100 3 4 5 6 7]

fmt.Println("after updateSlice(s2")
updateSlice(s2)
fmt.Println(s2)
fmt.Println(arr)

# after updateSlice(s2
# [100 1 100 3 4 5 6 7]
# [100 1 100 3 4 5 6 7]

fmt.Println("printArray")
printArray(arr[:])


# printArray
# 0 100
# 1 50
# 2 100
# 3 3
# 4 4
# 5 5
# 6 6
# 7 7

fmt.Println("reslice")
fmt.Println(s2)
s2 = s2[:5]
fmt.Println(s2)
s2 = s2[2:]
fmt.Println(s2)

# reslice
# [100 50 100 3 4 5 6 7]
# [100 50 100 3 4]
# [100 3 4]

}

slice

slice可以向后扩展,不可以向前扩展
s[i]不可以超越len(s),向后扩展不可以超越底层数组 cap(s)

1
2
3
4
5
6
7
8
9
10
11
func sliceExtension() {
fmt.Println("sliceExtension")
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
s1 := arr[2:6]
s2 := s1[3:5]
fmt.Println("s1=", s1)
fmt.Println("s2=", s2)
}
# sliceExtension
# s1= [2 3 4 5]
# s2= [5 6]

向slice添加元素时,如果超越了cap,系统会重新分配更大的底层数组
新的slice里面, ptr len cap都会变掉,所以必须接收append的返回值

s = append(s, value)

1
2
3
4
5
6
7
8
9
10
11
12
13
func sliceAppend() {
fmt.Println("slice append")
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
s1 := arr[2:6] # 2 3 4 5
s2 := s1[3:5] # 5 6
s3 := append(s2, 10) # 5 6 10
s4 := append(s3, 11) # 5 6 10 11
s5 := append(s4, 12) # 5 6 10 11 12
fmt.Println("s3=", s3)
fmt.Println("s4=", s4)
fmt.Println("s5=", s5)
fmt.Println("arr=", arr) # 0, 1, 2, 3, 4, 5, 6 10 数组长度不变的
}

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
func sliceCreate() {
var s []int #定义一个空slice
for i := 0; i < 20; i++ {
printSlice(s)
s = append(s, 2*i+1)
}
println(s)

s1 := []int{2, 3, 5, 6, 8, 2, 5}
println(s1)

s2 := make([]int, 16)
s3 := make([]int, 10, 32) //32是开辟的空间cap
println("print Slice s2 s3")
printSlice(s2)
printSlice(s3)

println("Slice copy")
copy(s2, s1) # 将s1复制给s2
printSlice(s2)

//[2 3 5 6 8 2 5 0 0 0 0 0 0 0 0 0], len=16, cap= 16
//如何删除8
println("Slice delete")
s2 = append(s2[:4], s2[5:]...)
printSlice(s2)

//删除头尾
fmt.Println("pop from front")
front := s2[0]
s2 = s2[1:]

fmt.Println("pop from back")
back := s2[len(s2)-1]
s2 = s2[:len(s2)-1]
fmt.Println(front, back)
printSlice(s2)
}
1
2
3
4
5
6
7
8
9
10
11
print Slice  s2 s3
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0], len=16, cap= 16
[0 0 0 0 0 0 0 0 0 0], len=10, cap= 32
Slice copy
[2 3 5 6 8 2 5 0 0 0 0 0 0 0 0 0], len=16, cap= 16
Slice delete
[2 3 5 6 2 5 0 0 0 0 0 0 0 0 0], len=15, cap= 16
pop from front
pop from back
2 0
[3 5 6 2 5 0 0 0 0 0 0 0 0], len=13, cap= 15

map

定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func main() {
m := map[string]string{
"name": "sam",
"class": "go",
}

fmt.Println(m)

m2 := make(map[string]int)

var m3 map[string]int

fmt.Println(m2, m3)
}

遍历,key在map里是无序的~

1
2
3
for k, v := range m {
fmt.Println(k, v)
}

当key打错了,获取到的是空值

1
2
3
4
//获取值
fmt.Println("getting values")
name := m["name"]
println(name)
1
2
3
4
5
if name, ok := m["name"]; ok {
println(name)
} else {
println("key does not exist")
}

删除

1
2
3
4
//删除
fmt.Println("delete values")
name1, ok := m["name"]
println(name1, ok)

map的key的类型可以是
除了slice,map,function的内建类型都可以作为key
struct类型不包含上述字段,也可以作为key

字符和字符串的处理

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
func main() {
s := "yes中文中文"
fmt.Println(s)
for _, b := range []byte(s) {
fmt.Printf("%X ", b)
}

fmt.Println()

for i, ch := range s {
fmt.Printf("(%d %X)", i, ch)
}

fmt.Println()
fmt.Println("rune count:", utf8.RuneCountInString(s))

//单个字符转义
bytes := []byte(s)
for len(bytes) > 0 {
ch, size := utf8.DecodeRune(bytes)
bytes = bytes[size:]
fmt.Printf("%c ", ch)
}

fmt.Println()
}
1
2
3
4
5
yes中文中文
79 65 73 E4 B8 AD E6 96 87 E4 B8 AD E6 96 87
(0 79)(1 65)(2 73)(3 4E2D)(6 6587)(9 4E2D)(12 6587)
rune count: 7
y e s 中 文 中 文

rune相当于go的char
使用range遍历string,rune对
使用utf8.RuneCountInString(s)获得字符数量
使用len获得字节长度
使用[]byte获得所有的字节

寻找最长不含重复字符的字串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func lengthOfNonRepeatSubStr(s string) int {
lastOccurred := make(map[rune]int)
start := 0
maxLength := 0

for i, ch := range []rune(s) {

if lastI, ok := lastOccurred[ch]; ok && lastI >= start {
start = lastI + 1
}

if i-start+1 > maxLength {
maxLength = i - start + 1
}
lastOccurred[ch] = i
}
return maxLength
}

其它字符串操作
Fields Split Join
Contains Index
ToLower ToUpper
Trim TrimRight TrimLeft

结构体和方法

面向对象

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

结构体的创建:不论地址还是结构本身,一律使用.来访问成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//定义
type treeNode struct {
value int
left, right *treeNode
}

//工厂函数创建
func createNode(value int) *treeNode {
return &treeNode{value: value} //这里返回的是局部变量的地址给外部使用,在go依旧是可以的
}

func main() {
var root treeNode
fmt.Println(root)

//创建
root = treeNode{value: 3}
root.left = &treeNode{}
root.right = &treeNode{5, nil, nil}
root.right.left = new(treeNode)
fmt.Println(root)
nodes := []treeNode{
{value: 3},
{},
{6, nil, &root},
}
fmt.Println(nodes)

root.left.right = createNode(2)
fmt.Println(root)
}

给结构定义方法:
func后面括号的是方法接收者(其实就跟函数的返回值一样)
只有使用指针才可以改变结构的内容
nil指针也可以调用方法(可以将值传进来,但是nil的赋值会报错,需要做return处理)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//树的遍历:给结构定义方法
func (node *treeNode) setValue(value int) {
if node == nil {
fmt.Println("setting value to nil node")
return
}
node.value = value
}


//值不会变
// func (node treeNode) setValue(value int) {
// node.value = value
// }

func (node *treeNode) setValue(value int) {
node.value = value
}
1
2
3
4
5
6
fmt.Println("print\n")
root.print()

fmt.Println("\nsetValue\n")
root.right.left.setValue(9)
root.right.left.print()

中序遍历

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
```

总结:
要改变内容必须使用指针接收者
结构过大也考虑使用指针接收者



## 包和封装

封装
名字一般使用驼峰命名
首字母大写:public(针对于包)
首字母小写:private(针对于包)



每个目录一个包
main包包含可执行入口
为结构定义的方法必须放在同一个包内
一个包可以放不同的文件
建议结构体都不需要前缀包名(TreeNode改为Node即可)


## 拓展已有类型

定义别名
```bash
type myTreeNode struct {
node *tree.TreeNode
}

//后序遍历
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()
}

使用组合

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

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
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
"fmt"
"learngo/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
type myTreeNode struct {
*tree.Node
}

依赖管理

  • GOPATH
  • GOVENDOR
  • go mod

go mod的使用

引入需要的库

1
2
3
import (
"go.uber.org/zap"
)

直接运行

1
go run zaptest.go

或者

1
2
3
4
5
go get -u 库名 

go mod init modtest

go build ./...

增加依赖

1
go get

更新依赖

1
2
3
go get [@version]

go mod tidy //更新版本之后,使用该命令可以去除多余的文件

目录管理

每个单独目录只有一个mian函数

build当前目录的所有子文件

1
go build ./...

产生结果, 结果放在GOPATH的bin目录

1
2
go install ./...
go env GOPATH

接口的概念

infra

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

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

type Retriver struct{}

func (Retriver) Get(url string) string {
resp, err := http.Get(url)
if err != nil {
panic(err)
}

defer resp.Body.Close()

bytes, _ := ioutil.ReadAll(resp.Body)

return string(bytes)
}

testing

1
2
3
4
5
6
7
package testing

type Retriver struct{}

func (Retriver) Get(url string) string {
return "fake content"
}

download

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

import (
"fmt"
"learngo/downloader/infra"
)

func getRetriver() retriver {
// return testing.Retriver{}//测试
return infra.Retriver{}//真实数据
}

//是infra还是testing的Retriver呢?如何判断呢:通过接口

type retriver interface {
Get(string) string
}

func main() {
var r retriver = getRetriver()
fmt.Printf("%s\n", r.Get("http://www.imooc.com"))

}

duck typing

接口的定义和实现

接口的定义(接口由使用者定义)

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"
"learngo/retriever/mock"
"learngo/retriever/real"
)

type Retriver interface {
Get(url string) string
}

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

func main() {
var r Retriver
r = mock.Retriver{"this is a fake imooc.com"}
r = real.Retriver{}
fmt.Println(download(r))
}

接口的实现(不需要实现Retriver接口,只需要实现接口里的方法)

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

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

type Retriver struct {
UserAgent string
TimeOut time.Duration
}

func (r Retriver) 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)
}

接口的值类型

switch判断类型

1
2
3
4
5
6
7
8
9
10
func inspect(r Retriver) {
fmt.Printf("类型=%T 值=%v\n", r, r)
switch v := r.(type) {
case mock.Retriver:
fmt.Println("Contents:", v.Contents)
case *real.Retriver:
fmt.Println("UserAgent:", v.UserAgent)
}

}

type assertion

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//type assertion

//real
realRetriver := r.(*real.Retriver)
fmt.Println(realRetriver.TimeOut)

//出错写法
realRetriver := r.(real.Retriver)
fmt.Println(realRetriver.TimeOut)

//防止出错
if mockRetriver, ok := r.(mock.Retriver); ok {
fmt.Println(mockRetriver.Contents)
} else {
fmt.Println("not a mock retriver")
}

表示任何类型:interface{}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//改为支持任何类型
type Queue []interface{}

func (q *Queue) Push(v interface{}) {
*q = append(*q, v)
}

func (q *Queue) Pop() interface{} {
head := (*q)[0]
*q = (*q)[1:]
return head
}

func (q *Queue) IsEmpty() bool {
return len(*q) == 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
type Retriver interface {
Get(url string) string
}

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

const url = "http://www.imooc.com"

func download(r Retriver) string {
return r.Get(url)
}

func poster(poster Poster) {
poster.Post(url,
map[string]string{
"name": "sam",
"language": "golang",
})
}

type RetriverPoster interface {
Retriver
Poster
}

func session(s RetriverPoster) string {
s.Post(url, map[string]string{
"contents": "another facked imooc.com",
})
return s.Get(url)
}

常用的系统接口

只需要看系统函数的定义就可以了。

  • Stringer
  • Reader/Writer

函数式编程

参数、变量、返回值都可以是函数
高阶函数(函数的参数依旧是个函数)
函数闭包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//返回一个函数
func adder() func(int) int {
sum := 0
return func(v int) int {
sum += v
return sum
}
}
func main() {
a := adder()
for i := 0; i < 10; i++ {
fmt.Println((a(i)))
}
}

斐波那契数列

1
2
3
4
5
6
7
func fibonacci() func() int {
a, b := 0, 1
return func() int {
a, b = b, a+b
return a
}
}

为函数实现接口

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
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: incorrect if p is too small!
return strings.NewReader(s).Read(p)
}



func printFileContents(reader io.Reader) {
scanner := bufio.NewScanner(reader)

for scanner.Scan() {
fmt.Println(scanner.Text())
}
}

func main() {
var f intGen = fib.Fibonacci()
printFileContents(f)
}

利用函数遍历二叉树

defer

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

1
2
3
4
5
func tryDefer() {
defer fmt.Println(1)
defer fmt.Println(2)
defer fmt.Println(3)
}

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
func tryDefer() {
defer fmt.Println(1)
defer fmt.Println(2)
defer fmt.Println(3)

for i := 0; i < 100; i++ {
defer fmt.Println(i)

if i == 30 {
panic("printed too many")
}
}
}

func writeFle(filename string) {
file, err := os.Create(filename)
if err != nil {
panic(err)
}
defer file.Close()

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

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

func main() {
writeFle("fib.txt")

tryDefer()
}
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
➜  defer go run defer.go
30
29
28
27
26
25
24
23
22
21
20
19
18
17
16
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
3
2
1
panic: printed too many

goroutine 1 [running]:
main.tryDefer()
/Users/samtake/Desktop/learngo/hello/errHandling/defer/defer.go:19 +0x2fc
main.main()
/Users/samtake/Desktop/learngo/hello/errHandling/defer/defer.go:43 +0x3b
exit status 2

何时调用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
36
func tryDefer() {
for i := 0; i < 100; i++ {
defer fmt.Println(i)
if i == 30 {
// Uncomment panic to see
// how it works with defer
// panic("printed too many")
}
}
}

func writeFle(filename string) {
file, err := os.OpenFile(filename,
os.O_EXCL|os.O_CREATE|os.O_WRONLY, 0666)

if err != nil {
if pathError, ok := err.(*os.PathError); !ok {
panic(err)
} else {
fmt.Printf("%s, %s, %s\n",
pathError.Op,
pathError.Path,
pathError.Err)
}
return
}
defer file.Close()

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

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

服务器统一出错处理

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
type appHandler func(writer http.ResponseWriter,
request *http.Request) error

func errWrapper(
handler appHandler) func(
http.ResponseWriter, *http.Request) {
return func(writer http.ResponseWriter,
request *http.Request) {
// panic
defer func() {
if r := recover(); r != nil {
log.Printf("Panic: %v", r)
http.Error(writer,
http.StatusText(http.StatusInternalServerError),
http.StatusInternalServerError)
}
}()

err := handler(writer, request)

if err != nil {
log.Printf("Error occurred "+
"handling request: %s",
err.Error())

// user error
if userErr, ok := err.(userError); ok {
http.Error(writer,
userErr.Message(),
http.StatusBadRequest)
return
}

// system error
code := http.StatusOK
switch {
case os.IsNotExist(err):
code = http.StatusNotFound
case os.IsPermission(err):
code = http.StatusForbidden
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)
}
}

panic和recover

panic
停止当前函数执行
一直向上返回,执行每一层的defer
如果没有遇见recover,程序就退出

recover
仅在defer调用中使用
获取panic的值
如果无法处理,可重新panic

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func tryRecover() {
defer func() {
r := recover()
if r == nil {
fmt.Println("Nothing to recover. " +
"Please try uncomment errors " +
"below.")
return
}
if err, ok := r.(error); ok {
fmt.Println("Error occurred:", err)
} else {
panic(fmt.Sprintf(
"I don't know what to do: %v", r))
}
}()

}

测试

go test 会自动执行路径包下面的“_test.go”文件(包括文件名包含 “_test.go” 的源文件),我们管称这些是测试文件,里面包含着你的测试函数,测试用例等。更多用法可以输入 “go help test” 查看。

1
go test

代码覆盖率和性能测试

1
2
3
4
```

## 使用pprof进行性能调优
```bash

测试http服务器

1
2
3
4
```

## 生成文档和实力代码
```bash

Goroutine

轻量级“线程”
非抢占式多任务处理,由协程主动交出控制权(Printf是个IO操作,内部做了控制权切换)
多个协程可能在一个或多个线程上运行

1
2
3
4
5
6
7
8
9
10
11
func main() {
for i := 0; i < 1000; i++ {
go func(i int) {
for {
fmt.Printf("hello form goroutine %d\n", i)
}
}(i)
}

time.Sleep(time.Millisecond)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

func tryGoroutine() {
var a [10]int
for i := 0; i < 10; i++ {
go func(ii int) {
for {
a[ii]++
runtime.Gosched()//主动交出控制权
}
}(i)
}

time.Sleep(time.Millisecond)
fmt.Println(a)
}

数据访问的冲突

1
go run -race goroutine.go

查看cpu占用率

1
top

go语言的调度器

任何函数只需加上go就能送给调度器运行
不需要在定义时区分是否是异步函数
调度器在合适的点进行切换
使用-race来检测数据访问冲突

goroutine 可能的切换点
I/O、select
channel
等待锁
函数调用(有时会)
runtime.Gosched()
以上所列只是参考~真实情况就不一定~

Channel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func chanDemo() {
// var c chan int //c == nil
c := make(chan int)

go func() {
for {
n := <-c
fmt.Println(n)
}
}()
c <- 1
c <- 2 //如果main退出了,2是没有输出的
time.Sleep(time.Millisecond)

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func worker(c chan int) {
func() {
for {
n := <-c
fmt.Println(n)
}
}()
}
func chanDemo() {
// var c chan int //c == nil
c := make(chan int)

go worker(c)
c <- 1
c <- 2 //如果main退出了,2是没有输出的
time.Sleep(time.Millisecond)

}

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
func worker(id int, c chan int) {
func() {
for {
fmt.Printf("Worker %d received %c\n", id, <-c)
}
}()
}
func chanDemo() {
var channels [10]chan int

for i := 0; i < 10; i++ {
channels[i] = make(chan int)
go worker(i, channels[i])
}

for i := 0; i < 10; i++ {
channels[i] <- 'a' + i
}

for i := 0; i < 10; i++ {
channels[i] <- 'A' + i
}

time.Sleep(time.Millisecond)

}

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
func creatWorker(id int) chan<- int {
c := make(chan int)
go func() {
for {
fmt.Printf("Worker %d received %c\n", id, <-c)
}
}()

return c
}
func chanDemo() {
var channels [10]chan<- int

for i := 0; i < 10; i++ {
channels[i] = creatWorker(i)
}

for i := 0; i < 10; i++ {
channels[i] <- 'a' + i
}

for i := 0; i < 10; i++ {
channels[i] <- 'A' + i
}

time.Sleep(time.Millisecond)

}

缓存channel

1
2
3
4
5
6
7
8
9
10
11
func work(id int, c chan int) {
for {
fmt.Printf("Worker %d received %c\n", id, <-c)
}
}
func bufferedChannel() {
c := make(chan int, 3) //给个缓存3
go work(0, c)
c <- 1
time.Sleep(time.Millisecond)
}

关闭通道

1
2
3
4
5
6
7
8
9
10
func channelClose() {
c := make(chan int)
go work(0, c)
c <- 'a'
c <- 'b'
c <- 'c'
c <- 'd'
close(c)
time.Sleep(time.Millisecond)
}

关闭之后,依旧能收到(空串,0)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Worker 0 received a
Worker 0 received b
Worker 0 received c
Worker 0 received d
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 r

解决关闭之后不再输出

1
2
3
4
5
6
7
8
9
10
func work(id int, c chan int) {
for {
n, ok := <-c
if !ok {
break
}

fmt.Printf("Worker %d received %c\n", id, n)
}
}
1
2
3
4
5
6
7
func work(id int, c chan int) {
for {
for n := range c {
fmt.Printf("Worker %d received %c\n", id, n)
}
}
}

使用channel等待goroutine结束(重点)

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

import (
"fmt"
"sync"
)

func doWork(id int,
w worker) {
for n := range w.in {
fmt.Printf("Worker %d received %c\n",
id, n)
w.done()
}
}

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

func createWorker(
id int, wg *sync.WaitGroup) worker {
w := worker{
in: make(chan int),
done: func() {
wg.Done()
},
}
go doWork(id, w)
return w
}

func chanDemo() {
var wg sync.WaitGroup

var workers [10]worker
for i := 0; i < 10; i++ {
workers[i] = createWorker(i, &wg)
}

wg.Add(20)
for i, worker := range workers {
worker.in <- 'a' + i
}
for i, worker := range workers {
worker.in <- 'A' + i
}

wg.Wait()
}

func main() {
chanDemo()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Worker 7 received h
Worker 5 received f
Worker 0 received a
Worker 1 received b
Worker 1 received B
Worker 4 received e
Worker 9 received j
Worker 8 received i
Worker 2 received c
Worker 0 received A
Worker 6 received g
Worker 3 received d
Worker 9 received J
Worker 6 received G
Worker 7 received H
Worker 2 received C
Worker 4 received E
Worker 8 received I
Worker 3 received D
Worker 5 received F

使用Channel进行书的遍历

1
2


使用Select进行调度(重点)

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

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

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

func worker(id int, c chan int) {
for n := range c {
time.Sleep(time.Second)
fmt.Printf("Worker %d received %d\n",
id, n)
}
}

func createWorker(id int) chan<- int {
c := make(chan int)
go worker(id, c)
return c
}

func main() {
var c1, c2 = generator(), generator()
var worker = createWorker(0)

var values []int
tm := time.After(10 * time.Second)
tick := time.Tick(time.Second)
for {
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(800 * time.Millisecond):
fmt.Println("timeout")
case <-tick:
fmt.Println(
"queue len =", len(values))
case <-tm:
fmt.Println("bye")
return
}
}
}

同步机制

WaitGroup
Mutex
Cond

1
2
3
4
```

## 迷宫算法
```bash

http标准库

使用http客户端发送请求
使用http.Client控制请求头部等
使用httputil简化工作

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

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

func main() {
request, err := http.NewRequest(
http.MethodGet,
"http://www.imooc.com", nil)
request.Header.Add("User-Agent",
"Mozilla/5.0 (iPhone; CPU iPhone OS 10_3 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) CriOS/56.0.2924.75 Mobile/14E5239e Safari/602.1")

client := http.Client{
CheckRedirect: func(
req *http.Request,
via []*http.Request) error {
fmt.Println("Redirect:", req)
return nil
},
}
resp, err := client.Do(request)
if err != nil {
panic(err)
}
defer resp.Body.Close()

s, err := httputil.DumpResponse(resp, true)
if err != nil {
panic(err)
}

fmt.Printf("%s\n", s)
}

pprof

1
_ "net/http/pprof"
1
...../debug/pprof/
1
go tool pprof [地址]

其它标准库

1
2
3
4
5
6
bufio
log
encoding/json
regexp
time
strings/math/rand

获取文档

1
godoc -http :8888
1
http://docscn.studygolang.com/

gin&增加middleware

拉取gin和日志库zap

1
2
go get -u github.com/gin-gonic/gin
go get -u go.uber.org/zap
1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import "github.com/gin-gonic/gin"

func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.Run()
}
1
http://localhost:8080/ping
1
2
3
4
5
6
listen tcp :8080: bind: address already in use 问题解决
命令行 lsof -i:8080 这里8080是我要释放的端口号

可以看到,该端口被id为51217的进程所占用,这个时候直接在命令行输入

kill 51217
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
import (
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)

func main() {
r := gin.Default()

logger, err := zap.NewProduction()
if err != nil {
panic(err)
}

r.Use(func(c *gin.Context) {
logger.Info("incoming request", zap.String("path", c.Request.URL.Path))
c.Next()
})

r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})

r.GET("/hello", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "hello",
})
})

r.Run()
}
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
import (
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"math/rand"
"time"
)

const keyRequestId = "requestId"

func main() {
r := gin.Default()
logger, err := zap.NewProduction()
if err != nil {
panic(err)
}

r.Use(func(c *gin.Context) {
s := time.Now()

c.Next()

logger.Info("incoming request",
zap.String("path", c.Request.URL.Path),
zap.Int("status", c.Writer.Status()),
zap.Duration("elapsed", time.Now().Sub(s)))
}, func(c *gin.Context) {
c.Set(keyRequestId, rand.Int())

c.Next()
})

r.GET("/ping", func(c *gin.Context) {
h := gin.H{
"message": "pong",
}
if rid, exists := c.Get(keyRequestId); exists {
h[keyRequestId] = rid
}
c.JSON(200, h)
})
r.GET("/hello", func(c *gin.Context) {
c.String(200, "hello")
})
r.Run()
}

使用正则表达式

1
2
3
4
5
6
7
func main() {
re := regexp.MustCompile(`[a-zA-Z0-9]+@[a-zA-Z0-9]+\.[a-zA-Z0-9]+`)
match := re.FindAllString(text, -1)
fmt.Println(match)
}

[sam@gmail.com 5555@qq.com 5555@163.com]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func main() {
re := regexp.MustCompile(`([a-zA-Z0-9]+)@([a-zA-Z0-9]+)\.([a-zA-Z0-9]+)`)
match := re.FindAllStringSubmatch(text, -1)
fmt.Println(match)

for _, m := range match {
fmt.Println(m)
}
}


[[sam@gmail.com sam gmail com] [5555@qq.com 5555 qq com] [5555@163.com 5555 163 com]]
[sam@gmail.com sam gmail com]
[5555@qq.com 5555 qq com]
[5555@163.com 5555 163 com]

包管理(包引入)

1
go mod init [crawler-single-task]
1
2
3
4
5
6
7
8
9
➜  crawler git:(master) ✗ cat go.mod                     
module crawler-single-task

go 1.13

require (
golang.org/x/net v0.0.0-20191207000613-e7e4b65ae663
golang.org/x/text v0.3.2
)

以项目当前目录为起点

1
2
3
4
import (
"crawler-single-task/engine"
"crawler-single-task/zhenai/parser"
)

单任务爬虫要点笔记

转换 UTF-8 与 GBK 编码的文本

1
go get golang.org/x/text
1
utf8Reader := transform.NewReader(resp.Body, simplifiedchinese.GBK.NewDecoder())

确定编码库

1
go get golang.org/x/net/html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//确定编码
func determinEncoding(r io.Reader) encoding.Encoding {
bytes, err := bufio.NewReader(r).Peek(1024)
if err != nil {
panic(err)
}
e, _, _ := charset.DetermineEncoding(bytes, "")

return e
}


//转码
e := determinEncoding(resp.Body)
utf8Reader := transform.NewReader(resp.Body, e.NewDecoder())

获取城市名和链接的方式

使用css选择器$('#cityList>dd>a')

使用xpath

使用正则表达式

1
2
3
4
5
func main() {
re := regexp.MustCompile(`[a-zA-Z0-9]+@[a-zA-Z0-9]+\.[a-zA-Z0-9]+`)
match := re.FindAllString(text, -1)
fmt.Println(match)
}