Go语言常用标准库

c

文件操作

文件的打开:

使用 os.Open() 函数可以打开一个文件返回一个*File和一个err。相对的,使用 [文件对象].Close() 可以关闭对应文件

注:为防止忘记关闭文件,一般我们会加上 defer 推迟语句保证执行后关闭

读取文件:

1、使用 read 方法

Read方法定义如下:

1
func (f *File) Read(b []byte) (n int, err error)

它接收一个字节切片,返回读取的字节数和可能的具体错误,读到文件末尾时会返回 0io.EOF

需要传入的参数是一个字节切片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func read1()  {
obj,err:=os.Open("./jk.txt")
if err!=nil{
fmt.Println("open file failed,err:",err)
return
}
defer obj.Close()
var s [2000]byte
for {
n, err := obj.Read(s[:])
if err == io.EOF {
println("文件读取结束")
return
}
if err != nil {
fmt.Println("错误!err:", err)
}
fmt.Println(string(s[:n]))
}
}
2、使用 bufio 方法

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

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

import (
"bufio"
"fmt"
"io"
"os"
)

// bufio按行读取示例
func main() {
file, err := os.Open("./xx.txt")
if err != nil {
fmt.Println("open file failed, err:", err)
return
}
defer file.Close()
reader := bufio.NewReader(file)//从文件生成读对象
for {
line, err := reader.ReadString('\n') //注意是字符
if err == io.EOF {
if len(line) != 0 {
fmt.Println(line)
}
fmt.Println("文件读完了")
break
}
if err != nil {
fmt.Println("read file failed, err:", err)
return
}
fmt.Print(line)
}
}
3、使用 ioutil 直接读取整个文件

(很消耗内存,大文件读取不推荐)

1
2
3
4
5
6
7
8
func read3(){
ret,err:=ioutil.ReadFile("./jk.txt")
if err!=nil{
fmt.Println("错误!err:", err)
return
}
fmt.Println(string(ret))
}

文件写入操作:

首先我们需要认识 os.OpenFile 这个新函数,它的定义是:

1
func OpenFile(name string, flag int, perm FileMode) (*File, error)

传入参数中,name 指的是要打开的文件名字,flag 指打开文件的模式,主要有以下几种:

模式 含义
os.O_WRONLY 只写
os.O_CREATE 创建文件
os.O_RDONLY 只读
os.O_RDWR 读写
os.O_TRUNC 清空
os.O_APPEND 追加

当需要进行多种操作时,只需要用 “|” 连接(毕竟都是 int 值)

这个 perm 倒是看上去莫名其妙的,实际上这里装的是一个关系权限的八进制数,可以通过这个函数获取结果:

1
n := os.FileMode(0666).String()

得出 -rw-rw-rw- 的答案:

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
一般文件属性标识如下: 

-rwxrwxrwx

1位:文件属性,一般常用的是"-",表示是普通文件;"d"表示是一个目录。

24位:文件所有者的权限rwx (可读/可写/可执行)。

57位:文件所属用户组的权限rwx (可读/可写/可执行)。

810位:其他人的权限rwx (可读/可写/可执行)。



在golang中,可以使用os.FileMode(perm).String()来查看权限标识:

os.FileMode(0777).String() //返回 -rwxrwxrwx

os.FileMode(0666).String() //返回 -rw-rw-rw-

os.FileMode(0644).String() //返回 -rw-r--r--



0777表示:创建了一个普通文件,所有人拥有所有的读、写、执行权限

0666表示:创建了一个普通文件,所有人拥有对该文件的读、写权限,但是都不可执行

0644表示:创建了一个普通文件,文件所有者对该文件有读写权限,用户组和其他人只有读权限,
都没有执行权限

然后和读取相对的,写入也有三种不同的方法:

1、使用 Write 和 WriteString
1
2
3
4
5
6
7
8
9
10
11
func main() {
file, err := os.OpenFile("hw.txt", os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
fmt.Println("open file failed, err:", err)
return
}
defer file.Close()
str := "hello world"
file.Write([]byte(str)) //写入字节切片数据
file.WriteString("hello world") //直接写入字符串数据
}
2、bufio.NewWriter
1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
file, err := os.OpenFile("hw.txt", os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
fmt.Println("open file failed, err:", err)
return
}
defer file.Close()
writer := bufio.NewWriter(file)
for i := 0; i < 10; i++ {
writer.WriteString("hello world\n") //将数据先写入缓存
}
writer.Flush() //将缓存中的内容写入文件
}

这样一来,会循环写入十次 “hello world” 而只需要一次 Flush

3、ioutil.WriteFile
1
2
3
4
5
6
7
8
func main() {
str := "hello world"
err := ioutil.WriteFile("./hw.txt", []byte(str), 0666)
if err != nil {
fmt.Println("write file failed, err:", err)
return
}
}

这个用法自带清场效果,慎用

fmt

有些类似于 iostream 这些输入输出流的库,实现了类似C语言 printf 和 scanf 的格式化 I/O。主要分为向外输出内容和获取输入内容两大部分。

相关定义如下:

1
2
3
func Print(a ...interface{}) (n int, err error)
func Printf(format string, a ...interface{}) (n int, err error)
func Println(a ...interface{}) (n int, err error)

print 直接输出内容,Printf 支持格式化输出字符串(%d,%s…)与 C 中用法类似,而 Println 和 Print 相似,只不过输出后会自动换行。

可以看到他们都可以自定义接口,详见之前的 Go 语言入门。

Fprint

定义如下:

1
2
3
func Fprint(w io.Writer, a ...interface{}) (n int, err error)
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error)
func Fprintln(w io.Writer, a ...interface{}) (n int, err error)

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

1
2
3
4
5
6
7
8
9
10
// 向标准输出写入内容
fmt.Fprintln(os.Stdout, "向标准输出写入内容")
fileObj, err := os.OpenFile("./hw.txt", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
fmt.Println("打开文件出错,err:", err)
return
}
n := "hello world"
// 向打开的文件句柄中写入内容
fmt.Fprintf(fileObj, "往文件中写入信息:%s", n)

只要满足 io.Writer 接口类型,都可以写入

Sprint

定义如下:

1
2
3
func Sprint(a ...interface{}) string
func Sprintf(format string, a ...interface{}) string
func Sprintln(a ...interface{}) string

这个函数主要是用来转字符串的,它可以将传入数据转化成字符串,传入数据格式和 Print 类是一样的。

1
比如 str:=Sprint("hello world!") 此时 str 中存储的就是 “hello world!”

Errorf

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

1
func Errorf(format string, a ...interface{}) error

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

1
err := fmt.Errorf("这是一个错误")

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

1
2
e := errors.New("原始错误e")
w := fmt.Errorf("Wrap了一个错误%w", e)

格式化占位符

点击跳过

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

通用占位符
占位符 说明
%v 值的默认格式表示
%+v 类似%v,但输出结构体时会添加字段名
%#v 值的Go语法表示
%T 打印值的类型
%% 百分号

示例代码如下:

1
2
3
4
5
6
7
fmt.Printf("%v\n", 100)
fmt.Printf("%v\n", false)
o := struct{ name string }{"小王子"}
fmt.Printf("%v\n", o)
fmt.Printf("%#v\n", o)
fmt.Printf("%T\n", o)
fmt.Printf("100%%\n")

输出结果如下:

1
2
3
4
5
6
100
false
{小王子}
struct { name string }{name:"小王子"}
struct { name string }
100%
布尔型
占位符 说明
%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格式(以获得更简洁、准确的输出)

示例代码如下:

1
2
3
4
5
6
7
f := 12.34
fmt.Printf("%b\n", f)
fmt.Printf("%e\n", f)
fmt.Printf("%E\n", f)
fmt.Printf("%f\n", f)
fmt.Printf("%g\n", f)
fmt.Printf("%G\n", f)

输出结果如下:

1
2
3
4
5
6
6946802425218990p-49
1.234000e+01
1.234000E+01
12.340000
12.34
12.34
字符串和[]byte
占位符 说明
%s 直接输出字符串或者[]byte
%q 该值对应的双引号括起来的go语法字符串字面值,必要时会采用安全的转义表示
%x 每个字节用两字符十六进制数表示(使用a-f
%X 每个字节用两字符十六进制数表示(使用A-F)
指针
占位符 说明
%p 表示为十六进制,并加上前导的0x

示例代码如下:

1
2
3
a := 10
fmt.Printf("%p\n", &a)
fmt.Printf("%#p\n", &a)

输出结果如下:

1
2
0xc000094000
c000094000
宽度标识符

宽度通过一个紧跟在百分号后面的十进制数指定,如果未指定宽度,则表示值时除必需之外不作填充。精度通过(可选的)宽度后跟点号后跟的十进制数指定。如果未指定精度,会使用默认精度;如果点号后没有跟数字,表示精度为0。举例如下:

占位符 说明
%f 默认宽度,默认精度
%9f 宽度9,默认精度
%.2f 默认宽度,精度2
%9.2f 宽度9,精度2
%9.f 宽度9,精度0

示例代码如下:

1
2
3
4
5
6
n := 12.34
fmt.Printf("%f\n", n)
fmt.Printf("%9f\n", n)
fmt.Printf("%.2f\n", n)
fmt.Printf("%9.2f\n", n)
fmt.Printf("%9.f\n", n)

输出结果如下:

1
2
3
4
5
12.340000
12.340000
12.34
12.34
12
其他flag
占位符 说明
’+’ 总是输出数值的正负号;对%q(%+q)会生成全部是ASCII字符的输出(通过转义);
’ ‘ 对数值,正数前加空格而负数前加负号;对字符串采用%x或%X时(% x或% X)会给各打印的字节之间加空格
’-’ 在输出右边填充空白而不是默认的左边(即从默认的右对齐切换为左对齐);
’#’ 八进制数前加0(%#o),十六进制数前加0x(%#x)或0X(%#X),指针去掉前面的0x(%#p)对%q(%#q),对%U(%#U)会输出空格和单引号括起来的go字面值;
‘0’ 使用0而不是空格填充,对于数值类型会把填充的0放在正负号后面;

举个例子:

1
2
3
4
5
6
7
8
s := "小王子"
fmt.Printf("%s\n", s)
fmt.Printf("%5s\n", s)
fmt.Printf("%-5s\n", s)
fmt.Printf("%5.7s\n", s)
fmt.Printf("%-5.7s\n", s)
fmt.Printf("%5.2s\n", s)
fmt.Printf("%05s\n", s)

输出结果如下:

1
2
3
4
5
6
7
小王子
小王子
小王子
小王子
小王子
小王
00小王子

获取输入

fmt.Scan

定义如下:

1
func Scan(a ...interface{}) (n int, err error)
  • Scan从标准输入扫描文本,读取由空白符分隔的值保存到传递给本函数的参数中,换行符视为空白符。
  • 本函数返回成功扫描的数据个数和遇到的任何错误。如果读取的数据个数比提供的参数少,会返回一个错误报告原因。
  • 需要使用 ‘&’ 取址
fmt.Scanf
1
func Scanf(format string, a ...interface{}) (n int, err error)

由上面的代码看到,string 之前有一个 “format” ,说明需要适当的格式化输入。可类比 Printf

同样,读入时也是按空白符分割。

fmt.Scanln
1
func Scanln(a ...interface{}) (n int, err error)
  • Scanln类似Scan,它在遇到换行时才停止扫描。最后一个数据后面必须有换行或者到达结束位置。
  • 本函数返回成功扫描的数据个数和遇到的任何错误。
bufio.NewReader

之前在读取文件时,用到了 bufio,其实在读取用户输入时,为了获取包含空格的内容,bufio 也十分管用。

1
2
3
4
5
6
7
func bufioDemo() {
reader := bufio.NewReader(os.Stdin) // 从标准输入生成读对象
fmt.Print("请输入内容:")
text, _ := reader.ReadString('\n') // 读到换行
text = strings.TrimSpace(text)
fmt.Printf("%#v\n", text)
}
Fscan系列

这几个函数功能分别类似于fmt.Scanfmt.Scanffmt.Scanln三个函数,只不过它们不是从标准输入中读取数据而是从io.Reader中读取数据。

1
2
3
func Fscan(r io.Reader, a ...interface{}) (n int, err error)
func Fscanln(r io.Reader, a ...interface{}) (n int, err error)
func Fscanf(r io.Reader, format string, a ...interface{}) (n int, err error)
Sscan系列

这几个函数功能分别类似于fmt.Scanfmt.Scanffmt.Scanln三个函数,只不过它们不是从标准输入中读取数据而是从指定字符串中读取数据。

1
2
3
func Sscan(str string, a ...interface{}) (n int, err error)
func Sscanln(str string, a ...interface{}) (n int, err error)
func Sscanf(str string, format string, a ...interface{}) (n int, err error)

time 包

时间类型

time.Time 类型表示时间。可以通过 time.Now() 获取当前时间,而具体时间也有相关获取函数:

函数 效果
time.Year() 获取年
time.Month() 获取月
time.Day() 获取日
time.Hour() 获取时
time.Minute() 获取分
time.Second() 获取秒

Go 的时间库算是比较强大了,毕竟甚至可以进行 Tues+1==Wed 这样的运算

时间戳

时间戳是自1970年1月1日(08:00:00GMT)至当前时间的总毫秒数。它也被称为Unix时间戳(UnixTimestamp)。

同样,对应时间类型,只需要 .Unix 获取时间戳或者用 .Unixnano 获取纳秒时间戳即可

值得注意的是,时间戳格式还可以重新换成时间格式:

1
timeObj := time.Unix(timestamp, 0) //将时间戳转为时间格式

这个函数里面的两个参数分别对应时间戳和纳秒时间戳,通常将目标以外的 另一个参数置零

时间间隔

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

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

1
2
3
4
5
6
7
8
const (
Nanosecond Duration = 1
Microsecond = 1000 * Nanosecond
Millisecond = 1000 * Microsecond
Second = 1000 * Millisecond
Minute = 60 * Second
Hour = 60 * Minute
)

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

时间操作

time

有时候我们需要进行一些时间操作,这个时候 Go 语言丰富的方法就为我们提供了便捷。

Add

我们在日常的编码过程中可能会遇到要求时间+时间间隔的需求,Go语言的时间对象有提供Add方法如下:

1
func (t Time) Add(d Duration) Time

即对任意 Time 类型参数 t ,都可以使用 t.Add(间隔时间参数) 得到增加后的时间数据。

另外,如果是想进行减去时间间隔的运算,只需要将参数 d 设置为对应负数即可。

Sub

求两个时间之间的差值:

1
func (t Time) Sub(u Time) Duration

返回一个时间段 **t-u (顺序调换会异号)**。如果结果超出了Duration可以表示的最大值/最小值,将返回最大值/最小值。

Equal
1
func (t Time) Equal(u Time) bool

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

Before/After
1
func (t Time) Before(u Time) bool

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

1
func (t Time) After(u Time) bool

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

定时器

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

1
2
3
4
5
6
func tickDemo() {
ticker := time.Tick(time.Second) //定义一个1秒间隔的定时器
for i := range ticker {
fmt.Println(i)//每秒都会执行的任务
}
}

不过既然是定时器,配套设施也很多,比如我们的 time.Sleep() 和它的完全体 time.NewTicker()

time.Sleep() 主要提供一个进程休眠的过程,而 Tick()本身就 只是一个相对残缺的部分,它只传回一个 channel,而 time.NewTicker() 则提供了包含关闭 Ticker 的方法,在需要回收的情况下更合适。

1
2
3
4
5
6
7
8
9
10
11
12
func main() {
boomer:=time.NewTicker(time.Second)
go func() {
for _=range boomer.C{
fmt.Println("di...")
}
}()

time.Sleep(time.Second*5)
boomer.Stop()//终止 boomer 的行为
println("Booooooom!!!")
}

时间格式化

Time 类型拥有一个 Format 方法来进行格式化,然而 Go 语言毕竟是人家大公司的作品,连个格式化都要搞一手致敬——它的格式化模板不是常见的 Y-m-d H:M:S ,而是使用Go的诞生时间2006年1月2号15点04分。

注:想格式化为12小时类型的化,还需指定PM

1
2
3
4
5
6
7
8
9
10
11
func formatDemo() {
now := time.Now()
// 格式化的模板为Go的出生时间2006年1月2号15点04分 Mon Jan
// 24小时制
fmt.Println(now.Format("2006-01-02 15:04:05.000 Mon Jan"))
// 12小时制
fmt.Println(now.Format("2006-01-02 03:04:05.000 PM Mon Jan"))
fmt.Println(now.Format("2006/01/02 15:04"))
fmt.Println(now.Format("15:04 2006/01/02"))
fmt.Println(now.Format("2006/01/02"))
}
解析字符串格式的时间
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
now := time.Now()
fmt.Println(now)
// 加载时区
loc, err := time.LoadLocation("Asia/Shanghai")
if err != nil {
fmt.Println(err)
return
}
// 按照指定时区和指定格式解析字符串时间
timeObj, err := time.ParseInLocation("2006/01/02 15:04:05", "2019/08/04 14:15:20", loc)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(timeObj)
fmt.Println(timeObj.Sub(now))

log

Go语言内置的log包实现了简单的日志服务。本文介绍了标准库log的基本使用。

使用Logger

log 包定义了 Logger 类型,该类型提供了一些格式化输出的方法。本包也提供了一个预定义的“标准” logger,可以通过调用函数 Print系列(Print|Printf|Println)、Fatal系列(Fatal|Fatalf|Fatalln)、和 Panic系列(Panic|Panicf|Panicln)来使用,比自行创建一个 logger 对象更容易使用。

1
2
3
4
5
6
7
8
func main() {
//log.Fatalln("this is a piece of diary which can cause fatal")
//log.Panicln("this is a piece of diary which can cause panic")
log.Println("this is a piece of diary which is really normal")
for i:=0;i<10;i++ {
fmt.Println("hey!")
}
}

logger 会打印每条日志信息的日期、时间,默认输出到系统的标准错误。Fatal 系列函数会在写入日志信息后调用 os.Exit(1)。Panic 系列函数会在写入日志信息后 panic。

os.Exit(1):指非正常运行导致退出程序

os.Exit(0):正常运行导致退出程序

fatal 以及 panic 触发后,后续语句都不会执行。

配置logger

默认情况下只会让 log 返回一部分时间信息,要是我们想获取更多信息该怎么办呢?

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

1
2
func Flags() int
func SetFlags(flag int)
flag选项

通过 flag 可以选择输出配置,他们是一系列定义好的常量

1
2
3
4
5
6
7
8
9
10
11
const (
// 控制输出日志信息的细节,不能控制输出的顺序和格式。
// 输出的日志在每一项后会有一个冒号分隔:例如2009/01/23 01:23:23.123123 /a/b/c/d.go:23: message
Ldate = 1 << iota // 日期:2009/01/23
Ltime // 时间:01:23:23
Lmicroseconds // 微秒级别的时间:01:23:23.123123(用于增强Ltime位)
Llongfile // 文件全路径名+行号: /a/b/c/d.go:23
Lshortfile // 文件名+行号:d.go:23(会覆盖掉Llongfile)
LUTC // 使用UTC时间
LstdFlags = Ldate | Ltime // 标准logger的初始值
)

使用 log.SetFlags() 设置标准logger的输出

1
2
3
func main() {
log.SetFlags(log.Llongfile | log.Lmicroseconds | log.Ldate)
}
配置日志前缀

类比我们的 Flag,前缀使用 Prefix 表示,log标准库中还提供了关于日志信息前缀的两个方法:

1
2
func Prefix() string//查看 logger 的输出前缀
func SetPrefix(prefix string)//用来设置 logger 的输出前缀

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

配置日志输出位置

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

1
func SetOutput(w io.Writer)

看见 io.Writer 就知道可能要用 os.OpenFile 了,这里设定是将日志输出到对应文件(不过你用 os.stdout 也没毛病)

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

1
2
3
4
5
6
7
8
9
func init() {
logFile, err := os.OpenFile("./xx.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
fmt.Println("open log file failed, err:", err)
return
}
log.SetOutput(logFile)
log.SetFlags(log.Llongfile | log.Lmicroseconds | log.Ldate)
}

创建logger

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

1
func New(out io.Writer, prefix string, flag int) *Logger

New 创建一个 Logger 对象。其中,参数 out 设置日志信息写入的目的地。参数 prefix 会添加到生成的每一条日志前面。参数 flag 定义日志的属性(时间、文件等等)。

举个例子:

1
2
3
4
func main() {
logger := log.New(os.Stdout, "<New>", log.Lshortfile|log.Ldate|log.Ltime)
logger.Println("这是自定义的 logger 记录的日志。")
}

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

1
<New>2017/06/19 14:06:51 main.go:34: 这是自定义的 logger 记录的日志。

net/http

HTTP协议

超文本传输协议(HTTP,HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络传输协议,所有的WWW文件都必须遵守这个标准。设计HTTP最初的目的是为了提供一种发布和接收HTML页面的方法。

HTTP客户端

基本的HTTP/HTTPS请求

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

1
2
3
4
5
6
resp, err := http.Get("http://example.com/")
...
resp, err := http.Post("http://example.com/upload", "image/jpeg", &buf)
...
resp, err := http.PostForm("http://example.com/form",
url.Values{"key": {"Value"}, "id": {"123"}})

程序在使用完response后必须关闭回复的主体。

1
2
3
4
5
6
7
resp, err := http.Get("http://example.com/")
if err != nil {
// handle error
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
// ...