定义变量 空值
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]) fmt.Println("arr[:6] =" , arr[:6]) fmt.Println("arr[2:] =" , arr[2:]) fmt.Println("arr[:] =" , arr[:]) s1 := arr[2:] s2 := arr[:] fmt.Println("after updateSlice(s1" ) updateSlice(s1) fmt.Println(s1) fmt.Println(arr) fmt.Println("after updateSlice(s2" ) updateSlice(s2) fmt.Println(s2) fmt.Println(arr) fmt.Println("printArray" ) printArray(arr[:]) fmt.Println("reslice" ) fmt.Println(s2) s2 = s2[:5] fmt.Println(s2) s2 = s2[2:] fmt.Println(s2) }
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) }
向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] s2 := s1[3:5] s3 := append(s2, 10) s4 := append(s3, 11) s5 := append(s4, 12) fmt.Println("s3=" , s3) fmt.Println("s4=" , s4) fmt.Println("s5=" , s5) fmt.Println("arr=" , arr) }
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 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) 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 []intfunc (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 }
依赖管理
go mod的使用
引入需要的库
1 2 3 import ( "go.uber.org/zap" )
直接运行
或者
1 2 3 4 5 go get -u 库名 go mod init modtest go build ./...
增加依赖
更新依赖
1 2 3 go get [@version] go mod tidy //更新版本之后,使用该命令可以去除多余的文件
目录管理 每个单独目录只有一个mian函数
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) }
常用的系统接口 只需要看系统函数的定义就可以了。
函数式编程 参数、变量、返回值都可以是函数 高阶函数(函数的参数依旧是个函数) 函数闭包
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() intfunc (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” 查看。
代码覆盖率和性能测试
测试http服务器
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占用率
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进行书的遍历
使用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
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 2 3 4 5 6 bufio log encoding/json regexp time strings/math/rand
获取文档
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) }