Go常用标准库

输入输出

输入

Scan 系列

  • Scan 从标准输入扫描文本,读取由空白符分隔的值保存到传递给本函数的参数中,换行符视为空白符。

  • Scanf 从标准输入扫描文本,根据format参数指定的格式去读取由空白符分隔的值保存到传递给本函数的参数中。

  • Scanln 类似 Scan,它在遇到换行时才停止扫描。最后一个数据后面必须有换行或者到达结束位置。

  • 本系列函数返回成功扫描的数据个数和遇到的任何错误。如果读取的数据个数比提供的参数少,会返回一个错误报告原因。

func Scan(a ...interface{}) (n int, err error)
func Scanf(format string, a ...interface{}) (n int, err error)
func Scanln(a ...interface{}) (n int, err error)

示例

var (
	name    string
	age     int
	married bool
)

func main() {
	fmt.Scan(&name, &age, &married)
	fmt.Printf("扫描结果 name:%s age:%d married:%t \n", name, age, married)
}

func main() {
    // fmt.Scanf 不同于 fmt.Scan 简单的以空格作为输入数据的分隔符
    // fmt.Scanf 为输入数据指定了具体的输入内容格式
    // 只有按照格式输入数据才会被扫描并存入对应变量
	fmt.Scanf("1:%s 2:%d 3:%t", &name, &age, &married)
	fmt.Printf("扫描结果 name:%s age:%d married:%t \n", name, age, married)
}

func main() {
	fmt.Scanln(&name, &age, &married)
	fmt.Printf("扫描结果 name:%s age:%d married:%t \n", name, age, married)
}

Fscan 系列

不是从标准输入中读取数据而是从 io.Reader 中读取数据

Sscan系列

从指定字符串中读取数据

bufio.NewReader

有时候我们想完整获取输入的内容,而输入的内容可能包含空格,这种情况下可以使用 bufio 包来实现

输出

Print 系列

  • Print 直接输出内容

  • Printf 支持格式化输出字符串

  • Println 会在输出内容的结尾添加一个换行符

示例

Fprint 系列

Fprint 系列函数会将内容输出到一个 io.Writer 接口类型的变量 w 中,通常用这个函数往文件中写入内容

示例

Sprint 系列

Sprint 系列函数会把传入的数据生成并返回一个字符串

示例

Errorf

Errorf 函数根据 format 参数生成格式化字符串并返回一个包含该字符串的错误

通常使用这种方式来自定义错误类型,例如:

Go1.13版本为 fmt.Errorf 函数新加了一个 %w 占位符用来生成一个可以包裹 Error 的 Wrapping Error

格式化占位符

*printf 系列函数都支持 format 格式化参数,在这里我们按照占位符将被替换的变量类型划分,方便查询和记忆

占位符
说明

%v

值的默认格式表示

%+v

类似%v,但输出结构体时会添加字段名

%#v

值的Go语法表示

%T

打印值的类型

%%

百分号

%t

true或false

%b

表示为二进制

%c

该值对应的unicode码值

%d

表示为十进制

%o

表示为八进制

%x

表示为十六进制,使用a-f

%X

表示为十六进制,使用A-F

%U

表示为Unicode格式:U+1234,等价于”U+%04X”

%q

该值对应的单引号括起来的go语法字符字面值,必要时会采用安全的转义表示

%b

无小数部分、二进制指数的科学计数法,如-123456p-78

%e

科学计数法,如-1234.456e+78

%E

科学计数法,如-1234.456E+78

%f

有小数部分但无指数部分,如123.456

%F

等价于%f

%g

根据实际情况采用%e或%f格式(以获得更简洁、准确的输出)

%G

根据实际情况采用%E或%F格式(以获得更简洁、准确的输出)

%s

直接输出字符串或者[]byte

%q

该值对应的双引号括起来的go语法字符串字面值,必要时会采用安全的转义表示

%x

每个字节用两字符十六进制数表示(使用a-f)

%X

每个字节用两字符十六进制数表示(使用A-F)

%p

表示为十六进制,并加上前导的0x

%f

默认宽度,默认精度

%9f

宽度9,默认精度

%.2f

默认宽度,精度2

%9.2f

宽度9,精度2

%9.f

宽度9,精度0

’+’

总是输出数值的正负号;对%q(%+q)会生成全部是ASCII字符的输出(通过转义)

’ ‘

对数值,正数前加空格而负数前加负号;对字符串采用%x或%X时(% x或% X)会给各打印的字节之间加空格

’-’

在输出右边填充空白而不是默认的左边(即从默认的右对齐切换为左对齐)

’#’

八进制数前加0(%#o),十六进制数前加0x(%#x)或0X(%#X),指针去掉前面的0x(%#p)对%q(%#q),对%U(%#U)会输出空格和单引号括起来的go字面值;

‘0’

使用0而不是空格填充,对于数值类型会把填充的0放在正负号后面;

命令行参数解析

Go语言内置的 flag 包实现了命令行参数的解析,flag 包使得开发命令行工具更为简单

os.Args

如果你只是简单的想要获取命令行参数,可以像下面的代码示例一样使用 os.Args 来获取命令行参数

将上面的代码执行 go build -o "args_demo" 编译之后,执行:

参数类型

flag 包支持的命令行参数类型有 boolintint64uintuint64float float64stringduration

flag参数
有效值

字符串flag

合法字符串

整数flag

1234、0664、0x1234等类型,也可以是负数。

浮点数flag

合法浮点数

bool类型flag

1, 0, t, f, T, F, true, false, TRUE, FALSE, True, False。

时间段flag

任何合法的时间段字符串。如”300ms”、”-1.5h”、”2h45m”。 合法的单位有”ns”、”us” /“µs”、”ms”、”s”、”m”、”h”。

定义

flag.Type()

示例

需要注意的是,此时 nameagemarrieddelay 均为对应类型的指针

flag.TypeVar()

示例

flag.Parse()

通过以上两种方法定义好命令行 flag 参数后,需要通过调用 flag.Parse() 来对命令行参数进行解析

支持的命令行参数格式有以下几种:

  • -flag xxx

  • --flag xxx

  • -flag=xxx

  • --flag=xxx

其中,布尔类型的参数必须使用等号的方式指定

flag 其他函数

示例

数据类型转换

strconv 包实现了基本数据类型与其字符串表示的转换,主要有以下常用函数:Atoi()Itoa()parse系列format系列append系列

Atoi()

字符串 => int

Itoa()

int => 字符串

Parse系列函数

Parse 类函数用于转换字符串为给定类型的值:ParseBool()ParseFloat()ParseInt()ParseUint()

ParseBool()

字符串 => bool,接受 1、0、t、f、T、F、true、false、True、False、TRUE、FALSE 否则返回错误

ParseInt()

字符串 => int,接受正负号

base 指定进制(2到36),如果 base 为 0,则会从字符串前置判断,”0x” 是 16 进制,”0” 是 8 进制,否则是 10 进制

bitSize 指定结果必须能无溢出赋值的整数类型,0、8、16、32、64 分别代表 int、int8、int16、int32、int64

返回的 err 是 *NumErr 类型的,如果语法有误,err.Error = ErrSyntax;如果结果超出类型范围 err.Error = ErrRange

ParseUint()

字符串 => uint,不接受正负号

ParseFloat()

字符串 => float

如果 s 合乎语法规则,函数会返回最为接近 s 表示值的一个浮点数(使用 IEEE754 规范舍入)

bitSize 指定了期望的接收类型,32 是 float32(返回值可以不改变精确值的赋值给 float32),64 是 float64

返回值 err 是 *NumErr 类型的,如果语法有误,err.Error=ErrSyntax;如果结果超出表示范围的,返回值 f 为 ±Inf,err.Error= ErrRange

Format系列函数

Format 系列函数实现了将给定类型数据格式化为 string 类型数据的功能

FormatBool()

根据 b 的值返回 truefalse

FormatInt()

int => base进制字符串

base 必须在 2 到 36 之间,结果中会使用小写字母 az 表示大于 10 的数字

FormatUint()

uint => base进制字符串

FormatFloat()

float => 字符串

bitSize 表示 f 的来源类型(32:float32、64:float64),会据此进行舍入

fmt 表示格式:

  • ’f’(-ddd.dddd)

  • ’b’(-ddddp±ddd,二进制指数)

  • ’e’(-d.dddde±dd,十进制指数)

  • ’E’(-d.ddddE±dd,十进制指数)

  • ’g’(指数很大时用’e’格式,否则’f’格式)

  • ’G’(指数很大时用’E’格式,否则’f’格式)

prec 控制精度(排除指数部分):对 ’f’、’e’、’E’,它表示小数点后的数字个数;对 ’g’、’G’,它控制总的数字个数。如果 prec 为 -1,则代表使用最少数量的、但又必需的数字来表示 f

其他

isPrint()

返回一个可打印的字符,类似于 unicode.IsPrint

r 必须是:字母(广义)、数字、标点、符号、ASCII空格

CanBackquote()

返回一个反引号字符串

s 必须是:单行、没有空格和tab

其他

除上文列出的函数外,strconv 包中还有 Append系列Quote系列 等函数。具体用法可查看官方文档

文件操作

计算机中的文件是存储在外部介质(通常是磁盘)上的数据集合,文件分为文本文件和二进制文件。

读取

file.Read()

示例

循环读取

bufio读取文件

bufio 是在 file 的基础上封装了一层 API,支持更多的功能。

ioutil读取整个文件

io/ioutil 包的 ReadFile 方法能够读取完整的文件,只需要将文件名作为参数传入

写入

os.OpenFile()

perm 为文件权限,一个八进制数。

flag 为打开文件的模式,有以下几种:

模式
含义

os.O_WRONLY

只写

os.O_CREATE

创建文件

os.O_RDONLY

只读

os.O_RDWR

读写

os.O_TRUNC

清空

os.O_APPEND

追加

Write 和 WriteString

bufio.NewWriter

ioutil.WriteFile

清空文件内容进行写入,会自动创建文件

示例

copyFile

借助 io.Copy() 实现一个拷贝文件函数

实现一个cat命令

日志

Go 语言内置的 log 包实现了简单的日志服务

使用logger

log 包定义了 logger 类型,该类型提供了一些格式化输出的方法

本包也提供了一个预定义的“标准” logger,可以通过调用下列函数来使用,比自行创建一个 logger 对象更容易使用

  • Print系列(Print|Printf|Println)

  • Fatal系列(Fatal|Fatalf|Fatalln)

  • Panic系列(Panic|Panicf|Panicln)

例如,我们可以像下面的代码一样直接通过 log 包来调用上面提到的方法,默认它们会将日志信息打印到终端界面:

编译并执行上面的代码会得到如下输出:

logger 会打印每条日志信息的日期、时间,默认输出到系统的标准错误

  • Fatal系列 函数会在写入日志信息后调用 os.Exit(1)

  • Panic系列 函数会在写入日志信息后 panic

配置logger

标准logger的配置

  • 默认情况下的 logger 只会提供日志的时间信息,但是很多情况下我们希望得到更多信息,比如记录该日志的文件名和行号等

  • log 标准库中为我们提供了定制这些设置的方法

  • log 标准库中的 Flags 函数会返回标准 logger 的输出配置,而 SetFlags 函数用来设置标准 logger 的输出配置

flag选项

log 标准库提供了如下的 flag 选项,它们是一系列定义好的常量

下面我们在记录日志之前先设置一下标准 logger 的输出选项如下:

编译执行后得到的输出结果如下:

配置日志前缀

log 标准库中还提供了关于日志信息前缀的两个方法:

其中 Prefix 函数用来查看标准 logger 的输出前缀,SetPrefix 函数用来设置输出前缀

上面的代码输出如下:

这样我们就能够在代码中为我们的日志信息添加指定的前缀,方便之后对日志信息进行检索和处理

配置日志输出位置

SetOutput 函数用来设置标准 logger 的输出目的地,默认是标准错误输出

例如,下面的代码会把日志输出到同目录下的 xx.log 文件中

如果你要使用标准的 logger,我们通常会把上面的配置操作写到 init 函数中

创建logger

log 标准库中还提供了一个创建新 logger 对象的构造函数 New,支持我们创建自己的 logger 示例

New 创建一个 Logger 对象

  • 参数 out 设置日志信息写入的目的地

  • 参数 prefix 会添加到生成的每一条日志前面

  • 参数 flag 定义日志的属性(时间、文件等等)

将上面的代码编译执行之后,得到结果如下:

总结

Go 内置的 log 库功能有限,例如无法满足记录不同级别日志的情况,我们在实际的项目中根据自己的需要选择使用第三方的日志库,如 logruszap

net/http

客户端

基本的HTTP/HTTPS请求

Get、Head、Post 和 PostForm 函数发出 HTTP/HTTPS 请求

GET请求示例

将上面的代码保存之后编译成可执行文件,执行之后就能在终端打印 baidu.com 网站首页的内容了

带参数的GET请求

GET请求 的参数需要使用Go语言内置的 net/url 这个标准库来处理

对应的 Server端 HandlerFunc 如下

Post请求示例

对应的 Server端 HandlerFunc 如下

自定义Client

要管理HTTP客户端的头域、重定向策略和其他设置,创建一个Client

自定义Transport

要管理代理、TLS配置、keep-alive、压缩和其他设置,创建一个Transport

Client 和 Transport 类型都可以安全的被多个 goroutine 同时使用。出于效率考虑,应该一次建立、尽量重用。

服务端

默认的Server

ListenAndServe 使用指定的监听地址和处理器启动一个 HTTP 服务端。

处理器参数通常是 nil,这表示采用包变量 DefaultServeMux 作为处理器。

Handle 和 HandleFunc 函数可以向 DefaultServeMux 添加处理器。

示例

使用Go语言中的 net/http 包来编写一个简单的接收 HTTP请求 的 Server端 示例

net/http 包是对 net包 的进一步封装,专门用来处理HTTP协议的数据

将上面的代码编译之后执行,打开你电脑上的浏览器在地址栏输入 127.0.0.1:9090 回车即可看到页面

自定义Server

要管理服务端的行为,可以创建一个自定义的Server

时间

时间和日期是我们编程中经常会用到的,本文主要介绍了 Go 语言内置的 time 包的基本用法。time 包提供了一些关于时间显示和测量用的函数。time 包中日历的计算采用的是公历,不考虑润秒。

时间类型

Go 语言中使用 time.Time 类型表示时间。我们可以通过 time.Now 函数获取当前的时间对象,然后从时间对象中可以获取到年、月、日、时、分、秒等信息。

Location和time zone

Go 语言中使用 location 来映射具体的时区。时区(Time Zone)是根据世界各国家与地区不同的经度而划分的时间定义,全球共分为24个时区。中国差不多跨 5 个时区,但为了使用方便只用东八时区的标准时即北京时间为准。

下面的示例代码中使用 beijing 来表示东八区 8 小时的偏移量,其中 time.FixedZonetime.LoadLocation 这两个函数则是用来获取 location 信息。

在日常编码过程中使用时间对象的时候一定要注意其时区信息。

Unix Time

Unix Time 是自 1970年1月1日 00:00:00 UTC 至当前时间经过的总秒数。下面的代码片段演示了如何基于时间对象获取到 Unix 时间。

time 包还提供了一系列将 int64 类型的时间戳转换为时间对象的方法。

时间间隔

time.Durationtime 包定义的一个类型,它代表两个时间点之间经过的时间,以纳秒为单位。time.Duration 表示一段时间间隔,可表示的最长时间段大约 290 年。

time 包中定义的时间间隔类型的常量如下:

例如:time.Duration 表示 1 纳秒,time.Second 表示 1 秒。

时间操作

Add

举个例子,求一个小时之后的时间:

Sub

求两个时间之间的差值:

返回一个时间段 t-u。如果结果超出了 Duration 可以表示的最大值/最小值,将返回最大值/最小值。要获取时间点 t-d(d为Duration),可以使用 t.Add(-d)。

Equal

判断两个时间是否相同,会考虑时区的影响,因此不同时区标准的时间也可以正确比较。本方法和用 t==u 不同,这种方法还会比较地点和时区信息。

Before

如果 t 代表的时间点在 u 之前,返回真;否则返回假。

After

如果 t 代表的时间点在 u 之后,返回真;否则返回假。

定时器

使用 time.Tick(时间间隔) 来设置定时器,定时器的本质上是一个通道(channel)。

时间格式化

time.Format 函数能够将一个时间对象格式化输出为指定布局的文本表示形式,需要注意的是 Go 语言中时间格式化的布局不是常见的 Y-m-d H:M:S,而是使用 2006-01-02 15:04:05.000(记忆口诀为2006 1 2 3 4 5)。

其中:

  • 2006:年(Y)

  • 01:月(m)

  • 02:日(d)

  • 15:时(H)

  • 04:分(M)

  • 05:秒(S)

补充

  • 如果想格式化为 12 小时格式,需在格式化布局中添加 PM

  • 小数部分想保留指定位数就写 0,如果想省略末尾可能的 0 就写 9。

解析字符串格式的时间

对于从文本的时间表示中解析出时间对象,time 包中提供了 time.Parsetime.ParseInLocation 两个函数。

其中 time.Parse 在解析时不需要额外指定时区信息。

time.ParseInLocation 函数需要在解析时额外指定时区信息。

context

在 Go http 包的 Server 中,每一个请求都有一个对应的 goroutine 去处理。请求处理函数通常会启动额外的 goroutine 用来访问后端服务,比如数据库和 RPC 服务。用来处理一个请求的 goroutine 通常需要访问一些与请求特定的数据,比如终端用户的身份认证信息、验证相关的 token、请求的截止时间。 当一个请求被取消或超时时,所有用来处理该请求的 goroutine 都应该迅速退出,然后系统才能释放这些 goroutine 占用的资源。

为什么需要Context

基本示例

全局变量方式

通道方式

官方版的方案

当子 goroutine 又开启另外一个 goroutine 时,只需要将 ctx 传入即可:

Context初识

Go1.7 加入了一个新的标准库 context,它定义了 Context 类型,专门用来简化 对于处理单个请求的多个 goroutine 之间与请求域的数据、取消信号、截止时间等相关操作,这些操作可能涉及多个 API 调用。

对服务器传入的请求应该创建上下文,而对服务器的传出调用应该接受上下文。它们之间的函数调用链必须传递上下文,或者可以使用 WithCancelWithDeadlineWithTimeoutWithValue 创建的派生上下文。当一个上下文被取消时,它派生的所有上下文也被取消。

Context接口

context.Context 是一个接口,该接口定义了四个需要实现的方法。具体签名如下:

其中:

  • Deadline 方法需要返回当前 Context 被取消的时间,也就是完成工作的截止时间(deadline);

  • Done 方法需要返回一个 Channel,这个Channel会在当前工作完成或者上下文被取消之后关闭,多次调用 Done 方法会返回同一个 Channel;

  • Err 方法会返回当前 Context 结束的原因,它只会在Done返回的 Channel 被关闭时才会返回非空的值;

    • 如果当前 Context 被取消就会返回 Canceled 错误;

    • 如果当前 Context 超时就会返回 DeadlineExceeded 错误;

  • Value 方法会从 Context 中返回键对应的值,对于同一个上下文来说,多次调用 Value 并传入相同的 Key 会返回相同的结果,该方法仅用于传递跨 API 和进程间跟请求域的数据;

Background()和TODO()

Go内置两个函数:Background()TODO(),这两个函数分别返回一个实现了 Context 接口的 backgroundtodo。我们代码中最开始都是以这两个内置的上下文对象作为最顶层的 partent context,衍生出更多的子上下文对象。

Background()主要用于 main 函数、初始化以及测试代码中,作为 Context 这个树结构的最顶层的 Context,也就是根 Context。

TODO(),它目前还不知道具体的使用场景,如果我们不知道该使用什么 Context 的时候,可以使用这个。

backgroundtodo 本质上都是 emptyCtx 结构体类型,是一个不可取消,没有设置截止时间,没有携带任何值的 Context。

With系列函数

此外,context 包中还定义了四个 With 系列函数。

WithCancel

WithCancel 的函数签名如下:

WithCancel 返回带有新 Done 通道的父节点的副本。当调用返回的 cancel 函数或当关闭父上下文的 Done 通道时,将关闭返回上下文的 Done 通道,无论先发生什么情况。

取消此上下文将释放与其关联的资源,因此代码应该在此上下文中运行的操作完成后立即调用 cancel。

上面的示例代码中,gen 函数在单独的 goroutine 中生成整数并将它们发送到返回的通道。gen 的调用者在使用生成的整数之后需要取消上下文,以免 gen 启动的内部 goroutine 发生泄漏。

WithDeadline

WithDeadline 的函数签名如下:

返回父上下文的副本,并将 deadline 调整为不迟于 d。如果父上下文的 deadline 已经早于 d,则 WithDeadline(parent, d)在语义上等同于父上下文。当截止日过期时,当调用返回的 cancel 函数时,或者当父上下文的 Done 通道关闭时,返回上下文的 Done 通道将被关闭,以最先发生的情况为准。

取消此上下文将释放与其关联的资源,因此代码应该在此上下文中运行的操作完成后立即调用 cancel。

上面的代码中,定义了一个 50 毫秒之后过期的 deadline,然后我们调用 context.WithDeadline(context.Background(), d) 得到一个上下文(ctx)和一个取消函数(cancel),然后使用一个 select 让主程序陷入等待:等待 1 秒后打印 overslept 退出或者等待 ctx 过期后退出。

在上面的示例代码中,因为 ctx 50 毫秒后就会过期,所以 ctx.Done() 会先接收到 context 到期通知,并且会打印 ctx.Err() 的内容。

WithTimeout

WithTimeout 的函数签名如下:

WithTimeout 返回 WithDeadline(parent, time.Now().Add(timeout))

取消此上下文将释放与其相关的资源,因此代码应该在此上下文中运行的操作完成后立即调用 cancel,通常用于数据库或者网络连接的超时控制。具体示例如下:

WithValue

WithValue 函数能够将请求作用域的数据与 Context 对象建立关系。声明如下:

WithValue 返回父节点的副本,其中与 key 关联的值为 val。

仅对 API 和进程间传递请求域的数据使用上下文值,而不是使用它来传递可选参数给函数。

所提供的键必须是可比较的,并且不应该是 string 类型或任何其他内置类型,以避免使用上下文在包之间发生冲突。WithValue 的用户应该为键定义自己的类型。为了避免在分配给 interface{} 时进行分配,上下文键通常具有具体类型 struct{}。或者,导出的上下文关键变量的静态类型应该是指针或接口。

使用Context的注意事项

  • 推荐以参数的方式显示传递 Context

  • 以 Context 作为参数的函数方法,应该把 Context 作为第一个参数。

  • 给一个函数方法传递 Context 的时候,不要传递 nil,如果不知道传递什么,就使用 context.TODO()

  • Context 的 Value 相关方法应该传递请求域的必要数据,不应该用于传递可选参数

  • Context 是线程安全的,可以放心的在多个 goroutine 中传递

客户端超时取消示例

调用服务端API时如何在客户端实现超时控制?

server端

client端

Last updated