10错误处理
错误处理方式
1 | package main |
自定义错误
Go语言的error类型实际上是一个接口,定义如下:
type error interface {
Error() string
}
error接口包含Error方法,用来返回一个字符串。换言之,所有符合 Error()string 格式的方法,都能实现错误接口。
1 | package main |
宕机与恢复
一般而言,只有当程序发生不可逆的错误时,才会使用panic方法来触发宕机。panic方法是Go语言的一个内置函数,使用panic方法后,程序的执行将直接中断。
panic方法的源代码如下,由于其参数为空接口类型,因此我们可以传入任意类型的值作为宕机内容:
func panic(v interface{})
1 | package main |
Go语言通过内置函数recover来捕获宕机,类似于其它编程语言中的try-catch机制。
在使用panic方法触发宕机后且在退出当前函数前会调用延迟执行语句defer,代码示例如下
1 | package main |
Go语言错误应用
panic()和recover() 虽然能模拟其他语言的异常机制,但是并不建议在Go语言编程中使用类似的方法。正如本章开篇所提到的,推荐使用多值返回来处理错误。
1 | package main |
11文件操作
目录基本操作
文件是以硬盘为载体的信息存储集合,文件可以是文本、图片、程序。
系统为了更好的管理文件,便使用了目录。目录是存放文件的地方,为树形层次结构。
想要读取一个目录的内容,可以使用”io/ioutil”库中的ReadDir方法,此方法返回一个有序列表。
对于创建目录可以使用“os“库的如下接口Mkdir。
1 | package main |
文件基本操作
对于文件的创建与打开使用的是标准库“os“中的OpenFile:
func OpenFile(name string, flag int, perm FileMode) (file *File, err error)
想要读取文件可使用“os”库中的Read接口:
func (f *File) Read(b []byte) (n int, err error)
比之前的文件读取,向文件写内容可用Write:
func (f *File) Write(b []byte) (n int, err error)
删除文件使用的“os”库中的Remove:
func Remove(name string) error
1 | func ReadFile(path string) { |
处理json文件
JSON(JavaScript Object Notation, JS 对象简谱) 是一种轻量级的数据交换格式。JSON最初是属于JavaScript的一部分,后来由于具有良好的可读和便于快速编写的特性,它现在已独立于语言
标准库提供了"encoding/json"库来处理JSON。编码JSON,即从其他的数据类型编码成JSON字符串,这个过程,我们会使用如下的接口:
func Marshal(v interface{}) ([]byte, error)
解码JSON会使用到Unmarshal接口,也就是Marshal的反操作。
func Unmarshal(data []byte, v interface{}) error
1 | func main(){ |
12接口类型
接口定义
在Go语言中,接口是一个自定义类型,接口类型是一种抽象的类型,它不会暴露出他所代表的内部值的结构,它只会展示出它自己的方法,因此,接口类型不能将其实例化。
Go语言的接口是非常灵活的,它通过一种方式来声明对象的行为,谁实现了这些行为,就相当于实现了这个接口里面声明各种方法的集合,但接口本身不去实现这些方法所要的一些操作
1 | type InterfaceName interface{ |
接口创建
1 | type Mysql struct { |
接口赋值存在2种情况:
1将对象实例赋值给接口
只能是对象指针赋值,而不是对象直接赋值,否者会发生错误
2将接口赋值给另一接口
a两接口拥有同样方法集,可相互赋值
b若A是B的子集,则B可赋值给A,A不可赋值给B
接口嵌入(接口组合):
类似结构体的内嵌,是继承特性的实现,体现了非侵入式的风格
接口不能嵌入自身,包括直接嵌入和间接嵌入
空接口
空接口(interface{})是Go语言中最特殊的接口,在Java语言中,所有的类都继承自一个基类Object,而Go中的interface{}接口就相当于Java语言里的Object。
在Go语言中,空接口不包含任何方法,也正因如此,所有的类型都实现了空接口,因此空接口可以存储任意类型的数值。
*空接口不能直接赋值给变量,需要类型断言。
空接口常见使用方式
1 | func Println(a ...interface{})(n int, err error)"..."表示可变长参数 |
类型断言
类型断言是使用在接口变量上的操作。简单来说,接口类型向普通类型的转换就是类型断言。
类型断言的语法是:
1 | t, ok := X.(T)//判断X的类型是否为T断言成功,则ok为true,t的值为接口变量X的动态值;断言失败则,则t的值为类型T的初始值,t的类型始终为T。例如:var X interface{}=1//空接口存储变量t0,ok=X.(string)断言失败,0为tring的初始值,即空字符串“”,t类型为string若t0,ok=X.(int)则断言成功,t0为X动态值X,t的类型为int,ok=true |
类型断言有2中情况:
1断言类型T是一个具体的类型
2断言类型T是一个接口类型
接口类型断言有2种方式:
1ok-pattern
2switch-type//要断言的接口类型种类较多时使用
1 | switch value := 接口变量.(type){ case 类型1: //类型1时的处理 case 类型2: //类型2时的处理 ... default: //默认时的处理} |
侵入式接口与非侵入式接口
侵入式接口:需要显示的创建一个类去实现一个接口(大部分语言)
非侵入式接口:与上相反,且接口设计更简洁、灵活,更注重实用性
非侵入式的3大好处:。。。
13并发与通道
并发编程包括多线程编程、多进程编程以及分布式程序,本章所所讲述的并发叫做协程,属于多线程编程。
调用栈:经常被用于存放子程序的返回地址
CSP:go语言的并发基于CSP(communication Sequential Process,通讯顺序进程)模型,该模型提倡通过通信来共享内存,而非通过共享内存来通信。go基于CSP,意味着显示锁都是可以避免的
gorountine
并行(Parallelism):指在同一时刻,有多条指令在多个处理器上同时执行。
并发(Concurrency):指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行,只是把时间片分成了若干段,使的多个进程快速交替的执行。
只需要在函数调用语句前面添加go关键字,就可以创建并发执行单元。开发人员无需了解任何执行细节,调度器会自动将其安排到合适的系统线程上去执行。
gorountine是并发的核心,也叫协程,go内部已实现gorountine之间的内存共享,它比线程更加易用、高效和轻便。
go语言中,每个并发执行单元叫作一个gorountine,在函数前加上go关键字,就能使这个函数以协程的方式运行
1 | go 函数名(函数参数) |
一旦使用go,就不能使用函数返回值来与主进程进行数据交换,而只能使用channel
1 | func Tesk1(){ |
所有gorountine函数在main函数结束时会一并结束。虽然gorountine类似于线程概念,但调度上不如线程细腻,而细腻程度取决于gorountine调度器的实现和运行环境。终止gorountine最好的方法是直接在函数中自然返回。
go关键字后也可以是匿名函数
1 | go func(参数列表){ |
runtime
runtime包实现了一个小型的任务调度器。
三大函数:
1Gosched()
使当前Go协程放弃处理器,以让其他Go协程运行。它不会挂起当前协程,因此当前Go协程会恢复执行。
1 | func main(){ |
Go语言的协程是抢占式调度,当一下几种情况,会主动将CPU转让出去:
*syscall
*C函数调用(本质和syscall一样)
*主动调用runtime.1Gosched
*某个gorountine的调用时间超过100ms,并且这个gorountine调用了非内联的函数
2Goexit()
终止调用它的Go协程,但其他Go协程不会受影响,效果和return一样
3GOMAXPROCS()
GOMAXPROCS(n int)函数可以设置程序在运行中所使用的CPU数。
可用NumCPU查询本机器逻辑CPU数
channel
goroutine运行在相同的地址空间,因此访问共享内存必须做好同步。引用类型channel是CSP模式的具体体现,用于多个goroutine之间的通讯。其内部实现了同步,确保并发安全。
channel是一种特殊的类型,和map类似,channel也是一个对应make创建的底层数据结构的引用。声明一个channel的方式如下:
1 | var 通道变量 chan 通道类型通道变量:是保存通道的引用变量通道类型:指该通道可传输的数据类型。 |
和其他引用类型一样,channel的零值也是nil
make创建channel
1 | make(chan Type)//等价于make(chan Type,0)make(chan Type, capacity)capacity为0时,channel无缓冲阻塞读写,大于0时,是有缓冲非阻塞的,直到写满capacity个元素彩阻塞写入。默认情况下,channel接收和发送数据都是阻塞的,除非另一端已准备好接收,这样goroutine的同步更加简单,而不需要显式锁channel<-value //发送value到channel<-channel //接收并将其丢弃x :=<-channel // 从channel中接收数据,并赋值给xx , ok :=<-channel //同上,并检测通道是否关闭,将此状态赋值给ok |
缓冲机制:
无缓冲通道,接收前没有能力保存任何值的通道
1 | make(chan Type)//等价于make(chan Type,0)需要发送goroutine和接收goroutine同时准备好,才能完成发送和接收操作。若两个goroutine没同时准备好,则导致先执行发送或接收的goroutine阻塞等待例如:func main(){ ch :=make(chan int,0) go func() { for i :=0;i<3;i++ { fmt.Printf("len(ch)=%v,cap(ch)=%v\n",len(ch),cap(ch)) ch <- i //发送 } }() for i :=0;i<3;i++{ time.Sleep(time.Second) //沉默1s 协程运行 fmt.Println(<-ch)//接收 }}执行结果len(ch)=0,cap(ch)=0 //无缓冲通道 打印通道len和cap,之后发送-阻塞0 //无缓冲通道 接收-打印通道-阻塞len(ch)=0,cap(ch)=01len(ch)=0,cap(ch)=02无缓冲的通道,保证进行发送和接收的goroutine会在同一时间进行数据交换。有缓冲的通道,无法保证 |
有缓冲通道,
1 | make(chan Type, capacity)存在缓存区,在缓存区为填满的情况下,程序不会被阻塞执行 |
close和range
close关闭channel()需注意:
1 | channel不像文件需要经常关闭,只有确实没有任何需要发送的数据时,或者想要显示地结束range循环等时,才会去关闭channel关闭channel后,无法向channel再次发送数据,再次发送将引发panic错误关闭channel后,可以继续从channel接收数据对于nil channel,无论接收还是发送都会被阻塞 |
range遍历channel
1 | for data :=range ch {}当channel关闭后,range也能自动结束本次遍历 |
单向channel
1 | var ch1 chan int //ch1为一个双向通道var ch2 chan<- int //ch2为一个只能接收的单向通道var <-chan int //ch3为一个只能发送的单向通道普通双向channel能隐式的转换为单向channel,但单向channel不能转为双向channel |
定时器
定时器的实现就是使用了单向channel
select
Go语言提供一个关键字select,通过select可以监听channel上的数据流动,select的用法和switch非常相似,由select开始一个新的选择块,每个选择条件有case语句来描述。
与switch语句可以选择任何可使用相比较的条件相比,select有较多的限制,其中最大一条限制就是每个case语句里面必须是一个IO操作,大致结构如下:
1 | select { case <-chan1: // 如果chan1成功读到数据,则执行该case语句 case chan2 <- 1: // 如果成功向chan2写入数据,则执行该case语句 default: //如果上面的case都没有执行成功,则执行该default语句 } |
沉默和阻塞(100ms&&调用了非内联函数)都对导致channel失去CPU控制权
1 | func main(){ |
select实现阻塞超时机制
1 | done :=make(chan bool) |
死锁:非缓冲信道上若发生了流入无流出,或者流出无流入,都会导致死锁
1 | func main(){ |
14反射
fmt.Println(),fmt包使用了reflect的反射标准库
反射的强大之处在于它非常灵活,但同时也带来了弊端,比如代码的可读性和可维护性变差,性能也大大折扣
reflect.TypeOf() 获取变量的值类型
1 | var typeOfNum reflect.type=reflect.TypeOf(num)typeameOfNum().Name() 获取类型的名称typeameOfNum().Kind() 获取类型的种类typeameOfNum().Elem() 对于指针类型变量,获取指针指向的元素类型获取结构体成员类型typeameOfNum().NumField()获取结构体成员数量typeameOfNum().Field()根据索引返回对应结构体字段的详细信息typeameOfNum().FieldByName(name string)查找字段名来获取字段信息typeameOfNum().FieldByIndex(index []int)通过索引获取字段信息 |
reflect.ValueOf() 获取变量的原始值
1 | var valueOfNum reflect.Value =reflect.ValueOf(num)valueOfNum.Int()用于获取int类型变量的值,若用于获取其他Kind的值,将会引发panic获取结构体成员字段的值valueOfNum.Field()通过索引返回对应结构体字段的valueOfNum反射值类型 |
1 | func main(){ |
反射三定律
1反射可以将接口类型变量 转换为反射类型变量
2反射可以将反射类型变量 转换为接口类型变量
3想要使用反射来修改变量的值,其值必须是可写的(CanSet)。这个值需满足两个条件一是可以被寻址(CanAddr),二是变量可导出(接固体字段首字母大写)
反射的性能
反射的性能的性能极差,在考虑性能的地方能不用反射就不用
15go命令行工具
相较于Java和C++的编译速度,Go的快速编译是一个主要的效率优势
编译相关指令
build
go build(无参编译)
如果我们在执行go build命令时不后跟任何代码包,那么命令将试图编译当前目录所对应的代码包。
go build + 文件列表
使用这种方式编译时,作为参数的多个Go源码文件必须在同一个目录中。
go build + 包
go build+包的方式编译需要将编译的包放到GOPATH下,否则编译器会找不到你想要编译的包。
减小体积。。。。
源码分三类:
1命令源码文件2库源码文件3测试源码文件
run
go run
该命令会编译执行Go源码文件,run的对象只能是单个或多个.go文件
(必须同属于一个main包),且不能为测试文件,无法针对包运行这个命令
1 | -work 显示当前的编译目录 |
install
go install
用于编译后安装,该命令依赖于GOPATH,是将编译的中间件放到pkg目录下,编译出的可执行文件放到bin目录下
交叉编译
在一个平台上生成另一个平台上的可执行代码,使同一个体系结构可以运行不同的操作系统。
Go的交叉编译很方便,只需要编译前设置Go环境变量CGO_ENABLED/GOOS/GOARCH即可,在1.5以后得版本中,Go语言编译器使用Go语言编写,而不再使用C语言
代码获取(get)
go get
用于远程仓库中下载安装远程代码包
1 | go get github.com/gin-gonic/gin |
-n
查看运行过程中使用的命令,可以发现go get
其实就是git clone
下载源码,之后再对源码进行编译安装,使用时,可能会因网络问题下载失败,这时可以给git添加代理,加速下载
1 | http代理:。。。 |
格式化代码
gofmt
+文件路径 格式化这个文件
gofmt
+目录 格式化这个目录所有.go文件
gofmt
格式化当前目录下的所有.go文件
注释文档
go doc
Gp语言文档只需要 在每个函数上方 用注释的方式 介绍该函数的作用及使用方法 go fmt
命令就会自动将这些注释转化为文档展示出来。
gofmt命令有个非常重要的参数-http
,作用是开启Web服务,提供交互式的文档查看页面
1 | godoc-http :8080 |
运行后,浏览器访问http:127.0.0.1:8080/即可以网页的方式查看Go语言文档
代码测试
go test
命令用于对Go语言编写的代码包进行测试。可以指定要测试的文件,也可以直接对整个包进行测试,但需要包中含有测试文件,测试文件都是以“_test.go”结尾,其中的函数名以“Test”为前缀,需传入*testing.T
类型的参数。
单元测试:目的是发现产品代码是否存在功能性问题、缺陷、以及是否符合预期的运行。结果要么PASS要么FALL,需用到testing测试框架
1 | func Add(a, b int) int{ |
基准测试
可提供自定义的计时器和一套基准测试算法,能方便快速的分析一段代码可能存在的CPU性能和分配问题
书写规范:
1 | "Benchmark"为前缀,后加测试函数名,使用“*testing.B”作函数参数 |
覆盖率测试
用于统计通过运行程序包的测试有多少代码得到执行,使用参数-cover
1 | go test -cover xxx.go |
性能分析
Go语言支持使用go tool pprof
工具 进行性能查看和调优,使用前序安装其依赖的图形绘制工具Graphviz
1通过文件方式:
输出CPU性能参数到文件cpu_file.prof,并对文件进行分析
1 | >go tool pprof cpu_file.prof |
通过http方式:
2需引入包
1 | import - "net/http/pprof" |
16正则表达
正则表达式介绍
正则表达式顾名思义:符合一定规则的表达式,就是用于匹配字符串中字符组合的模式。正则表达式使用单个字符串来描述、匹配一系列符合某个句法规则的字符串。在很多文本编辑器里,正则表达式通常被用来检索、替换那些匹配某个模式的文本(字符串)
正则表达式语法
普通字符
字符转义
元字符
限定符
定位符
分组构造
匹配模式
regexp包
MarchString函数接收一个要查找的正则表达式和目标字符串,并根据匹配结果返回true或flase,函数定义如下:
1 | func MatchString(pattern string, s string) (matched bool, err error) |
对于目标字符串:“hello world”,我们通过MatchString函数匹配其中的“hello”字符串,并返回匹配结果。
1 | package main |
FindStringIndex()/Compile()/MustCompile()
ReplaceAllString()
常用正则表达式参考。。。。。
17/18http编程
http简介
超文本传输协议(HTTP,HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议。它详细规定了浏览器和万维网服务器之间的相互通信规则,所有的WWW文件都必须遵守这个标准。设计HTTP最初的目的是为了提供一种发布和接收HTML页面的方法
我们在浏览网页时,从打开浏览器、输入网址到按下回车后网页上出现内容的过程中,浏览器到底做了哪些操作呢?
http客户端
1 | package main |
colly框架
1 | package main |
gin框架
1 | 项目名:awesomeProject1 |
命令行:
1 | >go build |
执行结果
1 | 浏览器 |
19Socket编程
OSI七层模型
OSI是一个开放性的通信系统互连参考模型
OSI的7层从上到下分别是 7 应用层 6 表示层 5 会话层 4 传输层 3 网络层 2 数据链路层 1 物理层
应用层 |
---|
表示层 |
会话层 |
传输层 |
网络层 |
数据链路层 |
物理层 |
TCP/IP模型是一个网络通信模型,以及一整个网络传输协议家族,为互联网的基础通信架构,常被视为是简化的七层OSI模型
OSI七层模型 | TCP/IP模型 |
---|---|
应用层 | 应用层 |
表示层 | |
会话层 | |
传输层 | 传输层 |
网络层 | 网络层 |
数据链路层 | 网络访问层 |
物理层 |
SOCKET基础
网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个Socket。建立网络通信连接至少要一对端口号(Socket)。Socket本质是编程接口(API),对TCP/IP的封装,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口
TCP编程
TCP(Transmission Control Protocol 传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793定义
TCP是因特网中的传输层协议,使用三次握手协议建立连接。当主动方发出SYN连接请求后,等待对方回答SYN+ACK,并最终对对方的 SYN 执行 ACK 确认。这种建立连接的方法可以防止产生错误的连接,TCP使用的流量控制协议是可变大小的滑动窗口协议。
1 | package main |
20数据库编程
mysql简介
MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,目前属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS (Relational Database Management System,关系数据库管理系统) 应用软件之一
MySQL有非常多的分支版本,官方版本(目前由Oracle维护)的下载地址为:
https://www.mysql.com/downloads/
安装完MySQL后,可以以使用如下格式连接MySQL服务器,需要注意的是-p后面与密码不能有空格。
mysql -h主机地址 –u 用户名 -p用户密码
数据库基本操作
数据库的最基本的操作便是增(create)、删(delete)、改(update)、查(read),简称CURD。数据库中有8类对象,分别是数据库、数据表、记录、字段、索引、查询、过滤器、视图,
Go语言中sql包提供了一个Open方法来创建一个数据库连接。
func Open(driverName, dataSourceName string) (*DB, error)
使用Go语言进行创建数据表需要使用Exec函数。
func (db *DB) Exec(query string, args …interface{}) (Result, error)
1 | func main() { |
MySQL 数据库中事务是用户一系列的数据库操作序列,这些操作要么全做要么全不做,是一个不可分割的工作单位。
事务具有 4 个特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持续性(Durability)。这 4 个特性简称为 ACID 原则。