go语言实战 - 标准库

第八章 标准库

  • 标准库是一组核心包,用于扩展和增强语言的能力
    • 每次语言更新都会带有标准库
    • 标准库会严格遵守哦向后兼容的承诺
    • 标准库是go语言开发,构建,发布过程的一部分
    • 标准库由go的构建者们维护和评审
    • go语言发布新版本时,标准库都会被测试,并评估性能
  • 开发中尽量使用标准库

文档与源代码

  • 作为go发布包的一部分,标准库的源代码是经过预编译的,称为归档文件
  • 这些文件是go静态库文件,由go的构建工具创建,并在编译和链接最终程序时被使用

日志

log包简介

  • log包日志项包含前缀、日期时间戳、源文件、行数、日志消息等组成
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main
import "log"
func init() {
log.SetPrefix("TRACE: ")
log.SetFlags(log.Ldate | log.Lmicroseconds | log.Llongfile)
}
func main() {
log.Println("message")
log.Fatalln("fatal message")
log.Panicln("panic message")
}
/* 输出结果
TRACE: 2020/04/21 14:34:16.281971 /Users/dxchang/go/src/api/books/go-in-action/8-pkg/log.go:11: message
TRACE: 2020/04/21 14:34:16.282102 /Users/dxchang/go/src/api/books/go-in-action/8-pkg/log.go:13: fatal message
*/
  • init函数作为初始化的一部分,会在main函数执行前执行
  • SetPrefix()方法进行设置前缀,一般全部大写
  • SetFlags设定标志来控制可以写到每个日志项的输出信息,如下
  • 这样可以用一个字节来存储日志的标志位,log.Ldate | log.Ltime | log.Lmicroseconds | log.Llongfile和传入00001111是等价的
  • 日志中以ln结尾的函数会换行,以f结尾的函数可以自定义格式化输出
  • log包是多线程安全的,多个goroutine同时来调用一个日志记录器的函数,不会存在写冲突
1
2
3
4
5
6
7
8
9
const (
Ldate = 1 << iota // 本地日期: 2009/01/23
Ltime // 本地时间: 01:23:23
Lmicroseconds // 时间毫秒数: 01:23:23.123123.
Llongfile // 日志打印的文件全路径: /a/b/c/d.go:23
Lshortfile // 日志打印的文件短路径: d.go:23
LUTC // 使用UTC时区时间
LstdFlags = Ldate | Ltime // 默认日志,打印日期和时间,到秒
)
  • Fatal系列函数来先写消息,然后调用os.Exit方法退出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Fatal is equivalent to Print() followed by a call to os.Exit(1).
func Fatal(v ...interface{}) {
std.Output(2, fmt.Sprint(v...))
os.Exit(1)
}
// Fatalf is equivalent to Printf() followed by a call to os.Exit(1).
func Fatalf(format string, v ...interface{}) {
std.Output(2, fmt.Sprintf(format, v...))
os.Exit(1)
}
// Fatalln is equivalent to Println() followed by a call to os.Exit(1).
func Fatalln(v ...interface{}) {
std.Output(2, fmt.Sprintln(v...))
os.Exit(1)
}
  • Panic函数写日志消息,触发panic,打印堆栈然后退出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Panic is equivalent to Print() followed by a call to panic().
func Panic(v ...interface{}) {
s := fmt.Sprint(v...)
std.Output(2, s)
panic(s)
}
// Panicf is equivalent to Printf() followed by a call to panic().
func Panicf(format string, v ...interface{}) {
s := fmt.Sprintf(format, v...)
std.Output(2, s)
panic(s)
}
// Panicln is equivalent to Println() followed by a call to panic().
func Panicln(v ...interface{}) {
s := fmt.Sprintln(v...)
std.Output(2, s)
panic(s)
}
  • Print函数是写日志消息的标准方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Print calls Output to print to the standard logger.
// Arguments are handled in the manner of fmt.Print.
func Print(v ...interface{}) {
std.Output(2, fmt.Sprint(v...))
}
// Printf calls Output to print to the standard logger.
// Arguments are handled in the manner of fmt.Printf.
func Printf(format string, v ...interface{}) {
std.Output(2, fmt.Sprintf(format, v...))
}
// Println calls Output to print to the standard logger.
// Arguments are handled in the manner of fmt.Println.
func Println(v ...interface{}) {
std.Output(2, fmt.Sprintln(v...))
}

定制的日志记录器

  • 创建一个定制的日志记录器,需要创建一个Logger类型值
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
var (
Trace *log.Logger // 记录所有日志
Info *log.Logger // 重要的信息
Warning *log.Logger // 警告
Error *log.Logger // 错误
)
func init() {
file, err := os.OpenFile("errors.txt", os.O_CREATE | os.O_WRONLY | os.O_APPEND, 0666)
if err != nil {
log.Fatalln("Failed to open error log file:", err)
}
Trace = log.New(ioutil.Discard, "TRACE: ", log.Ldate | log.Ltime | log.Lshortfile)
Info = log.New(os.Stdout, "INFO: ", log.Ldate | log.Ltime | log.Lshortfile)
Warning = log.New(os.Stdout, "WARNING: ", log.Ldate | log.Ltime | log.Lshortfile)
Error = log.New(io.MultiWriter(file, os.Stderr), "TRACE: ", log.Ldate | log.Ltime | log.Lshortfile)
}
func main() {
Trace.Println("I have something standard to say")
Info.Println("Special Information")
Warning.Println("There is something you need to know")
Error.Println("Something has failed")
}
  • log包的New()函数完成Logger类型的初始化,设定输出位置,日志前缀,及日志输出内容
1
2
3
func New(out io.Writer, prefix string, flag int) *Logger {
return &Logger{out: out, prefix: prefix, flag: flag}
}
  • ioutil.Discard实现了io.Writer接口,但是并不会发生实际的写入,使用Discard可以禁用这个类型的日志
  • os.Stdin、os.Stdout、os.Stderr分别指向标准输入,标准输出,标准错误
  • io.MultiWriter()方法可以向多个实现了io.Writer接口的位置写入日志

log包中的其余函数

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 Flags() int {
return std.Flags()
}
func SetFlags(flag int) {
std.SetFlags(flag)
}
func Prefix() string {
return std.Prefix()
}
func SetPrefix(prefix string) {
std.SetPrefix(prefix)
}
// SetOutput sets the output destination for the standard logger.
func SetOutput(w io.Writer) {
std.mu.Lock()
defer std.mu.Unlock()
std.out = w
}
func Output(calldepth int, s string) error {
return std.Output(calldepth+1, s) // +1 for this frame.
}
  • logger结构体
1
2
3
4
5
6
7
type Logger struct {
mu sync.Mutex // 互斥锁,支持原子写入
prefix string // 日志前缀
flag int // 日志输出内容标志位
out io.Writer // 日志输出位置
buf []byte // 要追加的日志内容
}
  • logger核心方法 Output
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
// s为要写入的日志,calldepth目前均为2
func (l *Logger) Output(calldepth int, s string) error {
now := time.Now() // get this early.
var file string
var line int
l.mu.Lock()
defer l.mu.Unlock()
if l.flag&(Lshortfile|Llongfile) != 0 {
// Release lock while getting caller info - it's expensive.
l.mu.Unlock()
var ok bool
_, file, line, ok = runtime.Caller(calldepth)
if !ok {
file = "???"
line = 0
}
l.mu.Lock()
}
l.buf = l.buf[:0]
l.formatHeader(&l.buf, now, file, line)
l.buf = append(l.buf, s...)
if len(s) == 0 || s[len(s)-1] != '\n' {
l.buf = append(l.buf, '\n')
}
_, err := l.out.Write(l.buf)
return err
}

编码/解码

  • xml/json包可以完成json和xml的编码解码

解码JSON

  • 使用json包的NewDecode函数和Decode方法来完成json解码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
type gResponse struct {
ErrCode int `json:"errcode"`
ErrMsg string `json:"errmsg"`
}
func main() {
uri := "https://api.weixin.qq.com/cgi-bin/user/info"
resp, err := http.Get(uri)
if err != nil {
log.Println("ERROR: ", err)
return
}
defer resp.Body.Close()
var gr gResponse
err = json.NewDecoder(resp.Body).Decode(&gr)
if err != nil {
log.Println("ERROR: ", err)
return
}
fmt.Println(gr)
}
// {41001 access_token missing hints: [xHkBIOLoRa-GmALMa!]}
  • json中NewDecoder方法返回Decoder对象
  • Decoder结构体从输入流中读取数据并解码成json
  • Decode方法完成JSON的解析映射
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
func NewDecoder(r io.Reader) *Decoder {
return &Decoder{r: r}
}
// A Decoder reads and decodes JSON values from an input stream.
type Decoder struct {
r io.Reader
buf []byte
d decodeState
scanp int // start of unread data in buf
scanned int64 // amount of data already scanned
scan scanner
err error
tokenState int
tokenStack []int
}
func (dec *Decoder) Decode(v interface{}) error {
if dec.err != nil {
return dec.err
}
if err := dec.tokenPrepareForDecode(); err != nil {
return err
}
if !dec.tokenValueAllowed() {
return &SyntaxError{msg: "not at beginning of value", Offset: dec.offset()}
}
// Read whole value into buffer.
n, err := dec.readValue()
if err != nil {
return err
}
dec.d.init(dec.buf[dec.scanp : dec.scanp+n])
dec.scanp += n
err = dec.d.unmarshal(v)
dec.tokenValueEnd()
return err
}
  • json文档以string的形式存在,此时使用json.Unmarshal方法完成反序列化处理
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
type gResponse struct {
ErrCode int `json:"errcode"`
ErrMsg string `json:"errmsg"`
}
func main() {
uri := "https://api.weixin.qq.com/cgi-bin/user/info"
resp, err := http.Get(uri)
if err != nil {
log.Println("ERROR: ", err)
return
}
defer resp.Body.Close()
bytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Println("ERROR: ", err)
return
}
var gr gResponse
err = json.Unmarshal(bytes, &gr)
if err != nil {
log.Println("ERROR: ", err)
return
}
fmt.Println(gr)
}

编码JSON

  • MarshalIndent方法可以完成map和结构体到json的映射

输入和输出

Writer和Reader接口

  • io包的核心就是Writer和Reader接口
    • Writer接口
      • Writer接口接收字节切片,返回两个值,写入的字节数和错误输出
      • Writer接口从p里向底层的数据流写入len(p)字节的数据
      • Writer无论如何也不能改p中的数据
    • Reader接口
      • Reader接口接收字节切片,返回两个值,读取的字节数和错误输出
      • Read最多读入len(p)个字节,保存到p
      • 可以使用所有的p的空间来存储临时数据
      • 当字节长度不足len(p),Read会立刻返回可用的数据,而不是等待更多的数据
      • 当成功读取了部分字节后,发生错误或者读取完成,Read方法会返回读入的字节数
      • 即使有错误发生,当读取的字节数>0时,需要先处理读取的数据,再处理错误
      • 读取的实现,不鼓励返回0个字节并且返回nil值的错误
        1
        2
        3
        4
        5
        6
        7
        type Writer interface {
        Write(p []byte) (n int, err error)
        }
        type Reader interface {
        Read(p []byte) (n int, err error)
        }

例子

1
2
3
4
5
6
7
8
func main() {
var b bytes.Buffer
b.Write([]byte("Hello "))
fmt.Fprintf(&b, "World")
b.WriteTo(os.Stdout)
}
  • Buffe的Write方法
1
2
3
4
5
6
7
8
9
10
11
// Write appends the contents of p to the buffer, growing the buffer as
// needed. The return value n is the length of p; err is always nil. If the
// buffer becomes too large, Write will panic with ErrTooLarge.
func (b *Buffer) Write(p []byte) (n int, err error) {
b.lastRead = opInvalid
m, ok := b.tryGrowByReslice(len(p))
if !ok {
m = b.grow(len(p))
}
return copy(b.buf[m:], p), nil
}

curl

  • 一个简单的curl,第一个参数指定curl的网站,,第二个参数指定将curl获取的response的body写入的文件名
  • defer 打开关闭的资源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func main() {
r, err := http.Get(os.Args[1])
if err != nil {
log.Fatalln(err)
}
file, err := os.Create(os.Args[2])
if err != nil {
log.Fatalln(err)
}
defer file.Close()
dest := io.MultiWriter(os.Stdout, file)
io.Copy(dest, r.Body)
if err := r.Body.Close(); err != nil {
log.Fatalln(err)
}
}

小结

  • 标准库由特殊的保证,并且被社区广泛应用
  • 使用标准库会让你的代码更易于管理
  • 接口允许你的代码组合已有的功能
  • 阅读标准库的代码是熟悉go语言习惯的好方法
Donate comment here