恢复正常更新,可接受对具体章节的催更

Google资深工程师深度讲解Go语言

学习笔记 Alan 4个月前 (04-04) 681次浏览 2个评论

Go语言的安装与开发环境
基础语法
内建容器
面向“对象”
面向接口
函数式编程
错误处理和资源管理
测试与性能调优
Goroutine
Channel
http及其他标准库
迷宫的广度优先搜索
开始实战项目
单任务版爬虫
并发版爬虫
数据存储和展示
分布式爬虫
课程总结

Go语言的安装与开发环境

下载:

国内:http://studygolang.com/dl

https://golang.org/dl/

开发环境:vi, emacs, idea, eclipse, vs, sublime … + go 插件

IDE:Goland, liteIDE

本课程使用 idea + go 插件

本文GitHub仓库:https://github.com/alanhou/learning-go

基础语法

变量

变量定义

  • var a, b, c bool
  • var s1, s2 string = “hello”, “world”
  • 可放在函数内,或直接放在包内
  • 使用 var()集中定义变量
  • 让编译器自动决定类型
    • var a, b, i, s1, s2 = true, false, 3, “hello”, “world”
  • 使用 := 定义变量
    • a, b, i, s1, s2 := true, false, 3, “hello”, “world”
    • 只能在函数内使用

内建变量类型

  • bool, string
  • (u)int, (u)int8, (u)int16, (u)int32, (u)int64, uintptr(指针)未指定长度时根据操作系统是多少位来决定
  • byte, rune(长度32位,相当于 char,解决多国语言问题)
  • float32, float64, complex64, complex128

复数回顾Google资深工程师深度讲解Go语言

  • i = \(\sqrt{-1}\)
  • 复数:3 + 4i (实部+虚部)
  • \(|3 + 4i| = \sqrt{3^2 + 4^2} =5\)
  • \(i^2 = -1, i^3 = -i, i^4 = 1,…\)
  • \(e^{i\phi} = cos\phi + isin\phi\)
  • \(|e^{i\phi}| = \sqrt{cos^2\phi + sin^2\phi} = 1\)
  • \(e^0 = 1, e^{i{\pi\over2}} = i\)
  • \(e^{i\pi} = -1, e^{i{3\over2}\pi} = -i, e^{i2\pi} = 1\)

最美公式 – 欧拉公式Google资深工程师深度讲解Go语言

$$e^{i\pi} + 1 = 0$$

强制类型转换

  • 类型转换是强制的
  • var a, b int = 3, 4
  • var c int = math.Sqrt(a*a + b*b)   
  • var c int = int(math.Sqrt(float64(a*a + b*b)))  ?(尚有浮点数所带来的偏差问题需解决)

常量与枚举

常量的定义

  • const filename = “abc.txt”
  • const 数值可作为各种类型使用
  • const a,b = 3,4
  • var c = int(math.Sqrt(a*a + b*b)) // a,b 未指定类型无需转换为 float

使用常量定义枚举类型

  • 普通枚举类型
  • 自增值枚举类型

变量定义要点回顾

  • 变量类型写在变量名之后
  • 编译器可推测变量类型
  • 没有 char,只有 rune
  • 原生支持复数类型

条件语句

if

  • if 的条件里不需要括号

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

switch

  • switch 会自动 break,除非使用fallthrough
  • switch 后可以没有表达式

循环

for

  • for 的条件里不需要括号
  • for 的条件里可以省略初始条件,结束条件,递增表达式

  • 省略初始条件,相当于 while

  • 省略所有条件,无限循环/死循环

基本语法要点回顾

  • for,if后面的条件没有括号
  • if 条件里也可定义变量
  • 没有 while
  • switch 不需要 break,也可直接 switch 多个条件

函数

  • func eval(a, b int, op string) int
  • 函数可返回多个值
  • 函数返回多个值时可以起名字,但仅用于非常简单的函数,对于调用者而言没有区别
  • 函数可以作为参数,函数式编程
  • 可变参数列表

函数语法要点回顾

  • 返回值类型写在最后面
  • 可返回多个值
  • 函数可作为参数
  • 没有默认参数、可选参数

指针

  • 指针不能运算

参数传递

值传递?引用传递?

  • Go 语言只有值传递一种方式

内建容器

数组、切片和容器

数组

  • 数量写在类型前
  • 可通过 _ 来省略变量,不仅仅是 range,任何地方都可通过 _ 来省略变量
  • 如果只要下标 i,可写成for i := range numbers

为人么要用 range?

  • 意义明确、美观
  • c++:没有类似能力
  • Java/Python:只能 for each value,不能同时获取 i, v

数组是值类型

  • [10]int 和[20]int 是不同类型
  • 调用 func f(arr [10]int)会 拷贝 数组
  • 在 go 语言中一般不直接使用数组(指针),使用切片

切片(Slice)

  • s 就是一个切片,值为[2 3 4 5]
  • Slice本身没有数据,是对底层 array 的一个 view
  • arr 的值变为 [0 1 10 3 4 5 6 7]

Reslice

Slice 的扩展

  • s1的值为?
  • s2的值为?

Google资深工程师深度讲解Go语言

  • s1的值下为[2 3 4 5],s2的值为[5 6]
  • slice 可以向后扩展,不可以向前扩展
  • s[i]不可以超越 len(s),向后扩展不可以超越底层数组 cap(s)

Slice 的实现

Google资深工程师深度讲解Go语言

向 Slice 添加元素

  • s3, s4, s5的值为?arr 的值为?
  • 添加元素时如果超越 cap,系统会重新分配更大的底层数组
  • 由于值传递的关系,必须接收 append 的返回值
  • s = append(s, val)

Map

  • map[K]V, map[K1]map[K2]V(复合 map)

map 的操作

  • 创建:make(map[string] int)
  • 获取元素:m[key]
  • key 不存在时,获得Value 类型的初始值(Zero value)
  • 用 value, ok := m[key]来判断是否存在 key
  • 用 delete 删除一个 key

map 的遍历

  • 使用 range 遍历 key,或者遍历 key, value 对
  • 不保证遍历顺序,如需顺序,需手动对 key 排序
  • 使用 len 获取元素个数

map 的 key

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

map例题

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

解题思路:

对于一个字母 x

  • lastOccurred[x]不存在,或者<start → 无需操作
  • lastOccurred[x] >= start → 更新 start
  • 更新 lastOccurred[x],更新 maxLength

解决中文等国际化字符的问题:

rune相当于 go 语言的 char

  • 使用 range 遍历 pos, rune 对
  • 使用 utf8.RuneCountInString 获得字符数量
  • 使用 len 获得字节长度
  • 使用[]byte 获得字节

其它字符串操作

  • Fields, Split, Join
  • Contains, Index
  • ToLower, ToUpper
  • Trim, TrimRight, TrimLeft

strings 包下有更多可进行查看

面向“对象”

结构体和方法

面向对象

  • go 语言仅支持封装,不支持继承和多态
  • go语言没有 class,只有 struct

结构的创建

  • 不论地址还是结构本身,一律使用 . 来访问成员

  • 使用自定义工厂函数
  • 注意返回了局部变量的地址!

结构创建在堆上还是栈上?

  • 不需要知道

为结构定义方法

  • 显示定义和命名方法接收者

使用指针作为方法接收者

  • 只有使用指针才可以改变结构内容
  • nil 指针也可以调用方法!

值接收者 vs 指针接收者

  • 要改变内容必须使用指针接收者
  • 结构过大也考虑使用指针接收者(性能考虑)
  • 一致性:如有指针接收者,最好都是指针接收者
  • 值接收者是 go语言特有
  • 值/指针接收者均可接收值/指针

包和封装

封装

  • 名字一般使用 CamelCase
  • 首字母大写:public
  • 首字母小写:private

  • 每个目录一个包
  • main 包包含可执行入口
  • 为结构定义的方法必须放在同一包内
  • 可以是不同文件

go 语言中如何扩充系统类型或者别人的类型

  • 定义别名
  • 使用组合

GOPATH以及目录结构

GOPATH 环境变量

  • 默认在~/go(Unix, Linux), %USERPROFILE%\go(Windows)
  • 官方推荐:所有项目和第三方库都放在同一个 GOPATH 下
  • 也可以将每个项目放在不同的 GOPATH

以 Mac 为例(~/.bash_profile)

go get 获取第三方库

  • go get 命令演示
  • 使用 gopm 来获取无法下载的包(如官网 golang.org 下的包)

    通过Preferences 的如下配置可以在保存时自动整理所导入的包,如删除未使用或错误的包导入(IDEA 原来通过 On Save 来实现,已淘汰并即将删除)

Google资深工程师深度讲解Go语言

  • go build 来编译
  • go install 产生 pkg 文件和可执行文件
  • go run 直接编译运行

GOPATH下目录结构

  • src
    • git repository 1
    • git repository 2
  • pkg
    • git repository 1
    • git repository 2
  • bin
    • 执行文件1, 2, 3…

面向接口

duck typing的概念

Google资深工程师深度讲解Go语言

大黄鸭是鸭子吗?

  • 传统类型系统:脊索动物门,脊椎动物亚门,鸟纲雁形目…  不是
  • duck typing:是鸭子
  • 概念:“像鸭子走路,像鸭子叫(长得像鸭子),那么就是鸭子”
  • 描述事物的外部行为而非内部结构
  • 严格说 go 属于结构化类型系统,类似 duck typing

Python 中的 duck typing

  • 运行时才知道传入的 retriever 有没有 get
  • 需要注释来说明接口

C++中的 duck typing

  • 编译时才知道传入的 retriever 有没有 get
  • 需要注释来说明接口

Java 中的类似代码

  • 传入的参数必须实现 Retriever 接口
  • 不是 duck typing
  • 同时需要 Readable, Appendable 怎么办?(apache polygene)

Go 语言的 duck typing

  • 同时具有 Python, C++的 duck typing 的灵活性
  • 又具有 Java 的类型检查

接口的定义和实现

接口的定义

使用者(download)→实现者(retriever)

  • 接口由使用者定义

接口的实现

  • 接口的实现是隐式的
  • 只要实现接口里的方法

接口的值类型

接口变量里面有什么

接口变量

  • 实现者的类型
  • 实现者的值或实现者的指针

接口变量里面有什么

  • 接口变量自带指针
  • 接口变量同样采用值传递,几乎不需要使用接口的指针
  • 指针接收者实现只能以指针方式使用;值接收者都可

查看接口变量

  • 表示任何类型:interface{}
  • Type Assertion
  • Type Switch

接口的组合

常用系统接口

  • Stringer
  • Reader/Writer

函数式编程

函数与闭包

函数式编程 vs. 函数指针

  • 函数是一等公民:参数、变量、返回值都可以是函数
  • 高阶函数
  • 函数 → 闭包

“正统”函数式编程

  • 不可变性:不能有状态,只有常量和函数
  • 函数只能有一个参数
  • 本课程不作上述规定

闭包

Google资深工程师深度讲解Go语言

Python 中闭包

  • Python 原生支持闭包
  • 使用__closure__来查看闭包的内容

C++中的闭包

  • 过去:stl 或者 boost 带有类似库
  • C++11及以后:支持闭包

Java 中的闭包

  • 1.8以后:使用 Function 接口和 Lambda表达式来创建函数对象
  • 匿名类或 Lambda 表达式均支持闭包

Go语言闭包的应用

  • 例一:斐波那契数列
  • 例二:为函数实现接口
  • 例三:使用函数来遍历二叉树
    Google资深工程师深度讲解Go语言

总结

  • 更为自然,不需要修饰如何访问自由变量
  • 没有 Lambda 表达式,但是有匿名函数

错误处理和资源管理

defer调用

  • 确保调用在函数结束时发生
  • 参数在 defer 语句时计算
  • defer 列表为后进先出

何时使用 defer 调用

  • Open/Close
  • Lock/Unlock
  • PrintHeader/PrintFooter

错误处理概念

错误处理

服务器统一出错处理

  • 如何实现统一错误处理逻辑

panic和recover

panic

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

recover

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

服务器统一出错处理2

error vs panic

  • 意料之中的:使用 error。如:文件打不开
  • 意料之外的:使用 panic。如:数组越界

错误处理综合示例

  • defer + panic + recover
  • Type Assertion
  • 函数式编程的应用(errWrapper)

测试与性能调优

测试

Debugging Sucks! Testing Rocks!

传统测试 vs 表格驱动测试

传统测试

  • 测试数据和测试逻辑混在一起
  • 出错信息不明确
  • 一旦一个数据出错测试全部结束

表格驱动测试

  • 分离的测试数据和测试逻辑
  • 明确的出错信息
  • 可以部分失败
  • go 语言的语法使得我们更易实践表格驱动测试

代码覆盖率和性能测试

代码覆盖率

性能测试(Benchmark)

使用pprof进行性能调优


Google资深工程师深度讲解Go语言

http 测试

  • 通过使用假的 Request/Response(TestErrWrapper)
  • 通过起服务器(TestErrWrapperInServer)

生成文档和示例代码

文档

  • 用注释写文档
  • 在测试中加入 Example
  • 使用 go doc/godoc 来查看/生成文档

Goroutine

协程 Coroutine

  • 轻量级“线程”
  • 非抢占式多任务处理,由协程主动交出控制权
  • 编译器/解释器/虚拟机层面的多任务
  • 多个协程可能在一个或多个线程上运行

Subroutines are special cases of more general program components, called coroutines.

子进程是协程的一个特例

普通函数:线程  main ➝ doWork

协程:线程(可能) main ⟺ doWork

其它语言中的协程

  • C++:Boost.Corouting
  • Java:不支持
  • Python:使用 yield 关键字实现协程,Python 3.5加入了 async def 对协程原生支持

goroutine 的定义

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

go routine 可能的切换点

  • I/0, select
  • channel
  • 等待锁
  • 函数调用(有时)
  • runtime.Gosched()
  • 只是参考,不能保证切换,不能保证在其他地方不切换

Channel

Google资深工程师深度讲解Go语言

  • channel
  • buffered channel
  • range
  • 理论基础:Communication Sequential Process (CSP)
  • Don’t communicate by sharing memory; share memory by communicating.
  • 不要通过共享内存来通信;通过通信来共享内存

例一:使用 Channel 来等待 goroutine 结束

  • 以及 WaitGroup的使用

例二:使用 Channel 来实现树的遍历

例三:使用 Select 来进行调度

  • Select 的使用
  • 定时器的使用
  • 在 Select 中使用 Nil Channel

传统同步机制

  • WaitGroup
  • Mutex
  • Cond

http及其他标准库

http

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

http 服务器的性能分析

  • import _ “net/http/pprof”
  • 访问/debug/pprof
  • 使用 go tool pprof 分析性能

其它标准库

  • bufio
  • log
  • encoding/json
  • regexp
  • time
  • strings/math/rand

文档

迷宫的广度优先搜索

广度优先算法

  • 为爬虫实战项目做发准备
  • 应用广泛,综合性强
  • 面试常见

Google资深工程师深度讲解Go语言

例:广度优先搜索走迷宫

  • 用循环创建二维 slice
  • 使用 slice 来实现队列
  • 用 Fscanf 读取文件
  • 对 Point 的抽象

开始实战项目

爬虫项目介绍

为什么做爬虫项目

  • 有一定的复杂性
  • 可以灵活调整项目的复杂性
  • 平衡语言/爬虫之间的比重

网络爬虫分类

  • 通用爬虫,如 baidu, google
  • 聚焦爬虫,从互联网获取结构化数据

go语言的爬虫库/框架

  • henrylee2cn/pholcus
  • gocrawl
  • colly
  • hu17889/go_spider

本课程爬虫项目

  • 将不使用现成爬虫库/框架
  • 使用 ElasticSearch 作为数据存储
  • 使用 Go 语言标准模板库实现 http 数据展示部分

爬虫的主题

爬取内容

  • 内容:如新闻,博客,社区…

爬取人

  • QQ 空间,人人网,微博,微信,facebook?
  • 相亲网站,求职网站
  • 出于隐私和趣味性考虑,本课程将爬取相亲网站

Google资深工程师深度讲解Go语言

总体算法

Google资深工程师深度讲解Go语言

单任务版爬虫

 

并发版爬虫

数据存储和展示

分布式爬虫

课程总结

更新中…

喜欢 (1)
[]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
(2)个小伙伴在吐槽
  1. 一如既往地优秀,坐等更新
    向往2019-04-08 10:41 回复