Golang 学习笔记

1. go语言的概述和环境配置

2. go语言的语法

  1. 第一个go语言程序

    1
    2
    3
    4
    5
    6
    7
    package main

    import "fmt"

    func main() {
    fmt.Println("Hello World")
    }
  1. go语言的变量定义

    go语言完整的定义的变量的方法为 var 变量名 类型=值var name string ="fuwei",可以简写为name:="fuwei"(这种只能在函数内使用,无法再包内使用),

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

    import "fmt"

    func main() {
    var a int=0
    var b=0 //编译器自动猜测
    c:=0
    var d int
    var e string
    f:=""
    arr:=[5]int{1,2,3,4,5}
    var arr1 [5]int

    var slice1 []int
    slice2:=[]int{1,2,3,4,5}

    fmt.Println(a,b,c,d,f,e,arr,arr1,slice1,slice2)

    }

    //简单的一个计算demo,勾股定理
    func main() {
    a, b := 3, 4
    c := int(math.Sqrt(float64(a*a + b*b)))
    fmt.Print(c)
    }

  2. 常量的定义,和其他语言一样,常量的值是不可变动的,在不指定常量的类型的时候,编译器会自动转换

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    func main() {
    a, b := 3, 4
    c := int(math.Sqrt(float64(a*a + b*b)))
    fmt.Print(c)

    const e, f = 3, 4
    g := math.Sqrt(float64(e*e + f*f))
    fmt.Print(g)
    }


  3. 枚举

    在go语言中枚举的定义使用const定义。可以直接定义

    1
    2
    3
    4
    5
    6
    const (
    Cpp = 0
    java = 1
    python = 2
    )

使用iota表达式

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

import "fmt"

const (
Cpp = iota //代表是自增
java
python
)

const (
b = 1 << (10 * iota) //代表使用1 << (10 * iota)来增加值
kb
mb
gb
tb
pb
)

func main() {
fmt.Println(Cpp, java, python)
fmt.Println(b, kb, mb, gb, tb, pb)
}

//结果:
0 1 2
1 1024 1048576 1073741824 1099511627776 1125899906842624

3. 条件和循环

3.1 条件

  1. 使用if关键字,不用括号,可以跟多个语句

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    func main() {
    const fileName = "/Users/fuwei/Documents/笔记/Go语言/01.goLang.md"
    contents, err := ioutil.ReadFile(fileName)
    if err!=nil {
    fmt.Print(err)
    } else {
    fmt.Println(contents)
    }
    fmt.Println(grade(10))

    }

    if可以简写为

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    func main() {
    const fileName = "/Users/fuwei/Documents/笔记/Go语言/01.goLang.md"
    //contents, err := ioutil.ReadFile(fileName)
    //这里的修改
    if contents, err := ioutil.ReadFile(fileName)
    err != nil {
    fmt.Print(err)
    } else {
    fmt.Println(contents)
    }
    fmt.Println(grade(10))

    }
  2. swtich case,不用写break编译器会自动添加

    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

    func grade(score int) string {
    //默认
    g := ""
    switch {
    case score < 60:
    {
    g = "F"

    }
    case score < 80:
    {
    g = "F"

    }
    case score < 100:
    {
    g = "A"

    }
    default:
    {
    panic("unknow")

    }

    }
    return g
    }

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

import (
"bufio"
"fmt"
"os"
"strconv"
)

//07.循环
func main() {
fmt.Println(convertToBin(10),
convertToBin(15))
readFile("/Users/fuwei/Documents/笔记/Go语言/01.goLang.md")
}

func convertToBin(n int) string {
result := ""
for ; n > 0; n /= 2 {
lsb := n % 2
//转数据
result = strconv.Itoa(lsb) + result

}
return result
}

func readFile(filePath string) {
open, err := os.Open(filePath)
if err != nil {
panic(err)
}
scanner := bufio.NewScanner(open)

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

一个小总结

  1. go语言的变量定义,变量名在前,类型在后
  2. 导入的包一定要使用
  3. 定义的变量一定要使用,如果不想使用可以用_来代替

4. 函数

  1. 返回值类型写在后面
  2. 可返回多个值
  3. 函数作为参数实现函数式编程
  4. 没有默认值,可变参数列表

4.1 函数的定义

  1. 标准的函数

    1
    2
    3
    4
    //多个返回值
    func print(a int){
    fmt.Println(a)
    }
  1. 多个返回值

    1
    2
    3
    4
    5
    //多个返回值
    func div(a, b int) (q, r int) {
    i, _ := eval(a, b, "/")
    return i, a % b
    }

4.2 函数式编程

1
2
3
4
5
6
7
func apply(op func(float64, float64) float64, a, b float64) float64 {
//通过反射来
p := reflect.ValueOf(op).Pointer()
opName := runtime.FuncForPC(p).Name()
fmt.Println("Calling function is %s with args (%d,%d)", opName, a, b)
return op(a, b)
}

4.3 可变参数列表

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

5. 指针

  1. 值传递和引用传递

    使用C++的代码来演示传值和传引用的区别

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    void pass_by_value(int a){
    a++;
    }

    void pass_by_ref(int& a){
    a++;
    }

    int main() {
    void pass_by_value(int);
    void pass_by_ref(int&);

    int a=5;
    pass_by_value(a);
    std::cout<<"a:"<<a;
    int b=5;
    pass_by_ref(b);
    std::cout<<"\n b:"<<b;
    return 0;
    }

打印结果是 a:5 b:6

go 语言只有值传递,没有引用传递,函数传递的时候都需要拷贝一份。

在GO语言中如何交换两个值?

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"

func main() {
a, b := 3, 4
swap(a, b)

fmt.Println(a, b)
swap1(&a, &b)
fmt.Println(a, b)
}

func swap(a, b int) {
a, b = b, a
}

//能够交换
func swap1(a, b *int) {
*a, *b = *b, *a
}


6. 数组

数组是一个定长的数据集合:

  1. 数组的定义

    1
    2
    3
    4
    5
    6
    var arr1 [5]int
    arr2 := [3]int{1, 3, 5}
    //让编译器判断
    arr3 := [...]int{2, 4, 6, 8, 10}


  2. 多维数组

    1
    2
    //多维数组
    var grid [4][5]int
  3. 数组作为函数传递

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    //这样是拷贝整个数组,导致内存浪费
    func printArr(arr [5]int) {
    fmt.Println("print arr")
    for i, v := range arr {
    fmt.Println(i, v)
    }
    }

    //这个是地址的拷贝
    func printArrp(arr *[5]int) {
    fmt.Println("print arr")
    for i, v := range arr {
    fmt.Println(i, v)
    }
    }

7.切片

切片可以理解成动态的数组。

切片生成的对应的数组是原数组的一个视图,如果修改则会影响原来的数据

数组创建的时候,数据都是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
package main

import "fmt"

//切片
func main() {
arr3 := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
fmt.Println("arr3[2:5]", arr3[2:5])
fmt.Println("arr3[:5]", arr3[:5])
fmt.Println("arr3[2:]", arr3[2:])
fmt.Println("arr3[:]", arr3[:])
//fmt.Println("arr3[2:5]",arr3[2:5])

s1 := arr3[2:6]
updateArr(s1)
fmt.Println("s1=", s1)
fmt.Println(arr3)

//slice再次建立slice
s2 := s1[3:7]
fmt.Println("s2=", s2)

//新的
s3:= append(s2, 10)
fmt.Println(s3)
fmt.Printf("arr=%v,len(arr)=%v cap(arr)=%v",arr,len(arr),cap(arr))

//超越cap
fmt.Println("\nover cap")
s4:= append(s3, 10,11,23,12,123)
fmt.Println(s4)
fmt.Printf("arr=%v,len(arr)=%v cap(arr)=%v",arr,len(arr),cap(arr))
}

func updateArr(s []int) {
s[0] = 100
}
  1. 正常的切片使用
1
2
3
4
5
6
7
8
9
10
11
12
package main
import "fmt"
//切片
func main() {
arr3 := [...]int{0,1,2,3,4,5,6,7,8,9}
fmt.Println("arr3[2:5]",arr3[2:5])
fmt.Println("arr3[:5]",arr3[:5])
fmt.Println("arr3[2:]",arr3[2:])
fmt.Println("arr3[:]",arr3[:])
//fmt.Println("arr3[2:5]",arr3[2:5])
}

  1. 切片生成的对应的数组是原数组的一个视图,如果修改则会影响原来的数据

    1
    2
    3
    4
    5
    s1 := arr3[2:6]
    updateArr(s1)
    fmt.Println("s1=", s1)
    fmt.Println(arr3)

  2. 如果切片的子切片越界,也就是slice的扩展,可以向后扩展

    1
    2
    3
    //slice再次建立slice,越界
    s2 := s1[3:7]
    fmt.Println("s2=", s2)
  3. 切片的长度和容量(len和cap)len是切片的长度,cap是向能向后扩展的长度

    1
    2
    3
    fmt.Printf("s1=%v,len(s1)=%v cap(s1)=%v",s1,len(s1),cap(s1))
    s1=[100 3 4 5],len(s1)=4 cap(s1)=8

  4. 向切片添加数据,添加数据如果大于原来的数组Cap,系统会重新分配更大的数组,原来的数组会被垃圾回收

    1
    2
    3
    4
    5
    6
    7
    8
    //新的
    s3:= append(s2, 10)
    fmt.Println(s3)
    fmt.Printf("arr=%v,len(arr)=%v cap(arr)=%v",arr,len(arr),cap(arr))
    fmt.Println("\nover cap")
    s4:= append(s3, 10,11,23,12,123)
    fmt.Println(s4)
    fmt.Printf("arr=%v,len(arr)=%v cap(arr)=%v",arr,len(arr),cap(arr))
  5. Slice cap是2^n的增长

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    package main

    import "fmt"

    func main() {
    var arr []int
    for i := 0; i < 100; i++ {
    arr = append(arr, 2*i+1)
    fmt.Printf("arr=%v,len(arr)=%v cap(arr)=%v\n", arr, len(arr), cap(arr))
    }
    }

  6. 申请切片,指定长度和容量

    长度代表需要使用的长度,容量代表真正的大小,如果大于这个容量会从新分配一个新的数组,这样保证了内存使用和内存的申请的一个平衡

    1
    2
    3
    4
    5
    //创建slice
    s2:=make([]int ,16)
    s3:=make([]int,10,32)//长度和容量
    fmt.Printf("s2=%v,len(s2)=%v cap(s2)=%v\n", s2, len(s2), cap(s2))
    fmt.Printf("s3=%v,len(s3)=%v cap(s3)=%v\n", s3, len(s3), cap(s3))
  7. 切片的拷贝,拷贝到dst的数组中。

    1
    2
    3
    copy(s2,arr)
    fmt.Printf("s2=%v,len(s2)=%v cap(s2)=%v\n", s2, len(s2), cap(s2))
    fmt.Printf("s3=%v,len(s3)=%v cap(s3)=%v\n", s3, len(s3), cap(s3))
  8. 切片的数据删除

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    //创建slice
    s2 := make([]int, 16)
    s3 := make([]int, 10, 32) //长度和容量
    fmt.Printf("s2=%v,len(s2)=%v cap(s2)=%v\n", s2, len(s2), cap(s2))
    fmt.Printf("s3=%v,len(s3)=%v cap(s3)=%v\n", s3, len(s3), cap(s3))

    copy(s2, arr)
    fmt.Printf("s2=%v,len(s2)=%v cap(s2)=%v\n", s2, len(s2), cap(s2))
    fmt.Printf("s3=%v,len(s3)=%v cap(s3)=%v\n", s3, len(s3), cap(s3))

    // 删除中间的元素
    s2 = append(s2[:3], s2[4:]...)
    fmt.Printf("s2=%v,len(s2)=%v cap(s2)=%v\n", s2, len(s2), cap(s2))

    front := s2[0]
    s2 = s2[1:]//删除头元素
    tail := s2[len(s2)-1]
    s2 = s2[:len(s2)-1]//删除尾元素
    fmt.Printf("front:%v,tail:%v\n", front, tail)
    fmt.Printf("s2=%v,len(s2)=%v cap(s2)=%v\n", s2, len(s2), cap(s2))

8. map结构

  1. 创建map,获取元素,判断可以是否存在,删除key,k-v遍历

    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 "fmt"

    func main(){
    m:=map[string]string{
    "key1":"val1",
    "key2":"val2",
    "key3":"val3",
    "key4":"val4",
    "key5":"val5",
    }
    m2:=make(map[string]int) //m2==empty
    var m3 map[string] int //nil 可以参与安全运算
    fmt.Println(m,m2,m3)
    //map是无序的,每次顺序都不同
    for k,v :=range m{
    fmt.Printf("\n Key:%s,Value:%s\n",k,v)
    }

    fmt.Println(m["key1"])
    fmt.Println(m["key10"])

    //判断是否存在
    if s,hasKey := m["key10"];hasKey{
    fmt.Println(s)
    }else {
    fmt.Println("key not exist")
    }

    delete(m,"key1")
    fmt.Println(m)

    }

    • map使用哈希表,必须可以比较相等
    • 除了slice,map,function的内建类型都可以作为key
    • Struct类型不包括上述字段,也可以为key

    [实战]

    寻找最长不含有除服的字符串的子串

eg. abcabcbb—-> abc , bbbbb—>b, pwwkew—->wke

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"

func main() {
str := "abcdacbdacbd"
fmt.Println(norepeateSubstr(str))
}

func norepeateSubstr(s string) int {
start := 0
m := make(map[string]int)
max := 0
for i, ch := range []byte(s) {
if v, hasKey := m[string(ch)]; hasKey && v >= start {
start = m[string(ch)] + 1
}
//当前最大值减去起始值,比较大小
if i-start+1 > max {
max = i - start + 1
}
m[string(ch)] = i
}
return max
}

Eg. goLang处理中文,rune

9. 面向对象

go语言没有构造函数,如果想使用构造的方法,可以手写一个工厂函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package main

import "fmt"

type treeNode struct {
value int
left, right *treeNode
}

func main() {
var root treeNode

root = treeNode{value: 3}
root.left = &treeNode{}
root.right = &treeNode{}
root.right.left = new(treeNode)
root.left.right = createNode(2)
nodes := []treeNode{
{value: 3}, {}, {6, nil, &root},
}
root.left.right.setValue(4)
root.left.right.print()
root.left.right.setValuePoint(5)
root.left.right.print()
fmt.Println(nodes)

}

//类似构造函数
//此处返回的是局部变量的地址
func createNode(value int) *treeNode {
return &treeNode{value: value}
}

//代表方法成员方法
func (node treeNode) print() {
fmt.Println(node.value)
}

//这个方法是传值的,无法修改对象的值
func (node treeNode) setValue(value int) {
node.value = value
}

//这个方法是传值的,无法修改对象的值
func (node *treeNode) setValuePoint(value int) {
node.value = value
}

//使用是相同,但是是传值的
func print(node treeNode) {
fmt.Println(node.value)
}


  1. 定义类

    1
    2
    3
    4
    type treeNode struct {
    value int
    left, right *treeNode
    }
  1. 创建对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    var root treeNode
    root = treeNode{value: 3}
    root.left = &treeNode{}
    root.right = &treeNode{}
    root.right.left = new(treeNode)
    root.left.right = createNode(2)
    nodes := []treeNode{
    {value: 3}, {}, {6, nil, &root},
    }
  1. 创建构造函数

    1
    2
    3
    4
    5
    //类似构造函数
    //此处返回的是局部变量的地址
    func createNode(value int) *treeNode {
    return &treeNode{value: value}
    }
  1. 创建成员方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    //代表方法成员方法
    func (node treeNode) print() {
    fmt.Println(node.value)
    }

    //这个方法是传值的,无法修改对象的值
    func (node treeNode) setValue(value int) {
    node.value = value
    }
    func (node *treeNode) setValuePoint(value int) {
    node.value = value
    }
  1. 树的遍历

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    func(node *treeNode) traverse(){
    if node==nil{
    return
    }
    //此处不用判断nil 因为nil也可以使用方法
    node.left.traverse()
    node.print()
    node.right.traverse()
    }

10. 封装

  1. 名字风格使用CamelCase
  2. 首字母大写:public,首字母小写:private
  3. 最小的单位是包

10.1 包

  1. 一个目录一个包
  2. main包中才有可执行入口
  3. 为结构定义的方法必须放在同一个包里,可以是不同的文件

TreeNode的修改

目录结构:

1
2
3
4
├── main.go
├── tree
   └── Node.go

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package tree

import "fmt"

type Node struct {
Value int
Left, Right *Node
}


//类似构造函数
//此处返回的是局部变量的地址
func CreateNode(value int) *Node {
return &Node{Value: value}
}

//代表方法成员方法
func (node Node) Print() {
fmt.Println(node.Value)
}

//这个方法是传值的,无法修改对象的值
func (node Node)SetValue(value int) {
node.Value = value
}
func (node *Node) SetValuePoint(value int) {
node.Value = value
}

//使用是相同,但是是传值的
func print(node Node) {
fmt.Println(node.Value)
}

func(node *Node) Traverse(){
if node==nil{
return
}
node.Left.Traverse()
node.Print()
node.Right.Traverse()
}


main方法:

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"
import "./tree"

func main() {
var root tree.Node
root = tree.Node{Value: 3}
root.Left = &tree.Node{}
root.Right = &tree.Node{}
root.Right.Left = new(tree.Node)
root.Left.Right = tree.CreateNode(2)
nodes := []tree.Node{
{Value: 3}, {}, {6, nil, &root},
}
root.Left.Right.SetValue(4)
root.Left.Right.Print()
//调用方式不发生改变,
//编译器会根据方法的参数来传递决定传地址还是值。
root.Left.Right.SetValuePoint(5)
root.Left.Right.Print()
fmt.Println(nodes)
root.Traverse()
}

10.2对象的扩展

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

import "../tree"

type NodeEx struct {
Node *tree.Node
}

func (nodeEx *NodeEx) Traverse() {
if nodeEx == nil || nodeEx.Node == nil {
return
}

nodeEx.Node.Right.Traverse()
nodeEx.Node.Print()
nodeEx.Node.Left.Traverse()
}

10.3 扩展一个队列

1
2
3
4
5
6
7
8
9
10
11
package queue
type Queue []int
func (q *Queue) Push(value int) {
*q = append(*q, value)
}

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

11. 接口

go语言的接口比较灵活,不用显示的实现

duck typing

  1. 接口的定义

    1
    2
    3
    type Retriever interface {
    Get(url string) string
    }
  1. 接口的实现 ,不需要显示的定义实现哪个接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    package retriever

    type MockRetriever struct {
    }

    func (mockRetiever MockRetriever) Get(url string) string {
    return "get baidu ok"
    }

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

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

    type HttpRetriever struct{}

    func (retriever *HttpRetriever) Get(url string) string {
    content, err := http.Get(url)
    if err != nil {
    panic(err)
    }
    defer content.Body.Close()
    response, err := httputil.DumpResponse(content,true)
    if err!=nil{
    panic(err)
    }
    return string(response)
    }

  1. 接口的组合

  2. 接口内部有什么,接口的内容

12. 资源的管理

  1. Defer 函数执行完成后释放,有panic和return 也不受影响

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

    package main

    import (
    "fmt"
    )

    func main() {
    tryDefer()
    }

    func tryDefer() {
    defer fmt.Println(1)
    defer fmt.Println(2)

    fmt.Println(3)
    panic("error")
    fmt.Println(4)
    return
    }
    //打印结果;
    3
    2
    1
  2. 错误处理

    1
       
  1. 统一的错误处理

    简单的文件服务器:

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

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

    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) {
    err:=handler(writer,request)
    if err!=nil{
    switch {
    case os.IsNotExist(err):
    http.Error(writer,http.StatusText(http.StatusNotFound),http.StatusNotFound)

    }
    }
    }
    }

    func main() {
    http.HandleFunc("/List/", errWrapper(fileList))
    err := http.ListenAndServe(":8888", nil)
    if err != nil {

    }
    }

    func fileList(writer http.ResponseWriter, request *http.Request) error {

    path := request.URL.Path[len("/List/"):]
    file, err := os.Open(path)
    if err != nil {
    //http.Error(writer, err.Error(), http.StatusInternalServerError)
    return err
    }
    defer file.Close()
    all, err := ioutil.ReadAll(file)
    if err != nil {
    panic(err)
    }
    writer.Write(all)
    return err
    }

12. 探讨指针的专栏

13. panic和recover

panic

相当于throw

  1. 停止当前函数执行
  2. 一直向上返回
  3. 没有遇见recover,程序退出

recover

  1. 仅在defer中调用
  2. 获取panic的值
  3. 如果无法处理,可以重新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
package main

import "fmt"

func main() {
tryRecover()

}

func tryRecover() {
defer func() {
r:=recover()
if err,ok:=r.(error);ok{
fmt.Println("进入自定义错误处理:")
fmt.Println(err)
}else {
panic(r)
}
}()
b:=0
a:=5/b
fmt.Println(a)
}

学习到底32讲,做一个整理

14. routine

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"
"time"
)

func main() {
var arr [10]int
for i := 0; i < 10; i++ {
go func(i int) {
for {
arr[i]++
//主动交出控制权
//runtime.Gosched()
}
}(i)
}
time.Sleep(time.Second)
fmt.Println(arr)
}

1
2
3
4
5
6
7
8
go func(i int) {
for {
arr[i]++
//主动交出控制权
//runtime.Gosched()
}
}(i)
此处使用闭包的传值的方法

14.1 Coroutine

14.2 Channel

  1. 基于CSP模型实现并发,

  2. 不要通过共享内存来通信,通过通信来共享内存

    1. 所有的channel的收发都是阻塞的

    2. 定义使用var c chan int,c:=make(chan int)

    3. 如果定义一个chan没有接收的对象,则会造成死锁

    4. 如果chan有缓存区,但是数量超过了缓存区,也会造成deadlock

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

import (
"fmt"
)

func chandemo1() {
c := make(chan int)
c <- 1 //这里会阻塞,如果没有发出,就会造成死锁
c <- 2
n := <-c
n = <-c
fmt.Println(n)

}
//会报错

因为chan的输入和输出的值的过程的阻塞的,如果没有发出,就会造成程序永久的阻塞,造成死锁问题.

为了解决这个问题,需要再开一个协程来处理数据chan的发出

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"
"time"
)

func chanDemo() {
c := make(chan int)
go func() {
for {
n := <-c
fmt.Println(n)
}
}()
c <- 1
c <- 2
time.Sleep(time.Millisecond)
}
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
package main

import (
"fmt"
"time"
)

func work(id int, c chan int) {
for {
fmt.Printf("work %d,do %c\n", id, <-c)
}
}
func chanDemo() {
var channels [10]chan int

for i := 0; i < 10; i++ {
channels[i] = make(chan int)
go work(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)
}
func main() {
chanDemo()
}

chan int 作为返回值

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 (
"fmt"
"time"
)

func createWork(id int) chan int {
c := make(chan int)
go func() {
for {
fmt.Printf("work %d,do %c\n", id, <-c)
}
}()
return c
}
func chanDemo() {
var channels [10]chan int

for i := 0; i < 10; i++ {
channels[i] =createWork(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)
}
func main() {
chanDemo()
}

定义chan只能发数据或者收数据

chan <-代表只能收数据和<-chant外面只能获得数据,如果使用错误会导致编译失败

chan限制

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 (
"fmt"
"time"
)

func createWork(id int) chan<- int {
c := make(chan int)
go func() {
for {
fmt.Printf("work %d,do %c\n", id, <-c)
}
}()
return c
}
func chanDemo() {
var channels [10]chan<- int

for i := 0; i < 10; i++ {
channels[i] =createWork(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)
}
func main() {
chanDemo()
}

chan增加缓冲区

协程虽然是轻量级的资源,但是发送完成后,立即进行资源的切换也会比较消耗资源,所以可以定义一个缓冲区,先收完数据再进行发送。

1
c := make(chan int, 3)//定义缓冲区长度为3
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
package main

import (
"fmt"
"time"
)
func work(id int,c chan int){
for {
fmt.Printf("work %d,do %c\n", id, <-c)
}
}
func bufferedChannel() {
c := make(chan int, 3)
go work(0,c)

c <- 'a'
c <- 'b'
c <- 'c'
c <- 'd'
time.Sleep(time.Millisecond)
}
func main() {
//chanDemo()
bufferedChannel()
}

如果知道数据已经发完了?

告知发送三个值后我已经发送完了,在接受方还要定义已经接受完的特殊值:

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
func finishSend() {
c := make(chan int, 3)
go work(0, c)

c <- 'a'
c <- 'b'
c <- 'c'
close(c)
time.Sleep(time.Millisecond)
}

func work(id int, c chan int) {
for {
i,ok:= <-c
if !ok{
//如果没有收到值,直接不接收数据了,因为此处是个死循环
break
}
fmt.Printf("work %d,do %c\n", id, i)
}
}
//或者使用rang函数
func work(id int, c chan int) {
for i := range c {
fmt.Printf("work %d,do %c\n", id, i)
}
}

43. 通过通信共享内存

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

import (
"fmt"
)

type worker struct {
in chan int
done chan bool
}

func work(id int, c chan int, done chan bool) {
for i := range c {
fmt.Printf("work %d,do %c\n", id, i)
done <- true
}
}
func createWork(id int) worker {
w := worker{
in: make(chan int),
done: make(chan bool),
}
go work(id, w.in, w.done)
return w
}
func chanDemo() {
var workers [10]worker
for i := 0; i < 10; i++ {
workers[i] = createWork(i)
}
for i, worker := range workers {
worker.in <- 'a' + i
}
for i, worker := range workers {
worker.in <- 'A' + i
}
for _, worker := range workers {
<-worker.done
<-worker.done
}
//time.Sleep(time.Millisecond)
}

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

当打印完小写的时候会产生报错,报错的原因是work的方法返回值没有接收。

sync.WaitGroup的使用,类似java中的CountDownLatch,可以动态追加

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

import (
"fmt"
"sync"
)

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

func work(id int, c chan int,wg *sync.WaitGroup) {
for i := range c {
fmt.Printf("work %d,do %c\n", id, i)
wg.Done()
}
}
func createWork(id int,wg *sync.WaitGroup) worker {
w := worker{
in: make(chan int),
}
go work(id, w.in,wg)
return w
}
func chanDemo() {
var wg sync.WaitGroup
var workers [10]worker
for i := 0; i < 10; i++ {
workers[i] = createWork(i,&wg)
}
for i, worker := range workers {
worker.in <- 'a' + i
wg.Add(1)
}

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

wg.Wait()
//time.Sleep(time.Millisecond)
}

func main() {
chanDemo()
//bufferedChannel()

}

45. select

如果两个channel c1,c2,谁先获得值,就采用的谁的数据

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

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

func main() {
var c1, c2 = generator(), generator()
var worker = createWork(0)
n := 0
var values []int
//产生一个channel 到时间会送入一个值
//此处使用10s
after := time.After(10 * time.Second)
tick := time.Tick(time.Second)
fmt.Println("开始时间:", time.Now())
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 <-after:
{
fmt.Println("到时间结束:", time.Now())
return
}
//非阻塞
default:
{

}
}

}
}
func work(id int, c chan int) {

for i := range c {
time.Sleep(time.Second)
fmt.Printf("work %d,do %d\n", id, i)
}
}
func createWork(id int) chan<- int {
c := make(chan int)
go work(id, c)
return c
}
func generator() chan int {
out := make(chan int)
go func() {
i := 0
for {
fmt.Println("生成数字", i)
time.Sleep(time.Duration(rand.Intn(1500)) * time.Millisecond)
out <- i
i++
}
}()
return out
}

46 传统的互斥锁

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

import (
"fmt"
"time"
)

type atomicInt int

func (a *atomicInt) increment() {
*a++
}

func (a *atomicInt) get() int {
return int(*a)
}

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

使用go run -race main.go查看执行的轨迹

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
==================
WARNING: DATA RACE
Read at 0x00c000134008 by main goroutine:
main.main()
/Users/fuwei/work/goPros/go/learnGo/bytedance/ch46.mutex/main.go:25 +0xc5

Previous write at 0x00c000134008 by goroutine 7:
main.(*atomicInt).increment()
/Users/fuwei/work/goPros/go/learnGo/bytedance/ch46.mutex/main.go:11 +0x51
main.main.func1()
/Users/fuwei/work/goPros/go/learnGo/bytedance/ch46.mutex/main.go:22 +0x32

Goroutine 7 (finished) created at:
main.main()
/Users/fuwei/work/goPros/go/learnGo/bytedance/ch46.mutex/main.go:21 +0xaa
==================
2
Found 1 data race(s)
exit status 66

增加sync.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
package main

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

type atomicInt struct {
value int
lock sync.Mutex
}

func (a *atomicInt) increment() {
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.Second)
fmt.Println(a.get())
}

47. http

文件服务器增加pprof

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

import (
"fmt"
"io/ioutil"
"net/http"
//避免没有的时候 报错
_ "net/http/pprof"
"os"
)

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) {
err := handler(writer, request)
if err != nil {
switch {
case os.IsNotExist(err):
http.Error(writer, http.StatusText(http.StatusNotFound), http.StatusNotFound)
}
}
}
}

func main() {
http.HandleFunc("/file/", errWrapper(fileList))
http.HandleFunc("/List/", errWrapper(getDirList))
err := http.ListenAndServe(":8888", nil)
if err != nil {

}
}
func getDirList(writer http.ResponseWriter, request *http.Request) error {
dir_list, e := ioutil.ReadDir("./")
if e != nil {
fmt.Println("read dir error")
return e
}

for _, v := range dir_list {
bytes := []byte(v.Name() + "\n")
writer.Write(bytes)
}
return e
}
func fileList(writer http.ResponseWriter, request *http.Request) error {

path := request.URL.Path[len("/List/"):]
file, err := os.Open(path)
if err != nil {
//http.Error(writer, err.Error(), http.StatusInternalServerError)
return err
}
defer file.Close()
all, err := ioutil.ReadAll(file)
if err != nil {
panic(err)
}
writer.Write(all)
return err
}

访问localhost:8888/debug/pprof,显示当前的程序运行的堆栈:

47-01

go tool pprof http://localhost:8888/debug/pprof/profile使用这个命令获得30s的cpu的运行情况

47-02

碰到的故障:failed to execute dot. Is Graphviz installed? Error: exec: "dot": executable file not found in $PATH 是电脑没有安装gvedit导致

48. 常用的第三方库

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

查看文档godoc -http :8888

49. 分布式爬虫

更新中…