Go 语言实战 第 8 章 标准库
Go 标准库是一组核心包,用来扩展和增强语言的能力。
这些包为语言增加了大量不同的类型。
开发人员可以直接使用这些类型,而不用再写自己的包或者去下载其他人发布的第三方包。
由于这些包和语言绑在一起发布,它们会得到以下特殊的保证:
- 每次语言更新,哪怕是小更新,都会带有标准库;
- 这些标准库会严格遵守向后兼容的承诺;
- 标准库是 Go 语言开发、构建、发布过程的一部分;
- 标准库由 Go 的构建者们维护和评审;
- 每次 Go 语言发布新版本时,标准库都会被测试,并评估性能。
这些保证让标准库变得很特殊,开发人员应该尽量利用这些标准库。
使用标准库里的包可以使管理代码变得更容易,并且保证代码的稳定。
不用担心程序无法兼容不同的 Go 语言版本,也不用管理第三方依赖。
8.1 文档和源代码
书中的文档地址打不开,国内的可以参考这个地址:https://golang.google.cn/pkg/。
这里可以看到每个包中的方法定义、说明及示例。
8.2 记录日志
跟踪日志示例:
TRACE: 2022/05/03 14:53:11.470823 D:/github/goinaction/code/chapter8/listing03/listing03.go:15: message
示例:
// This sample program demonstrates how to use the base log package.
package main
import (
"log"
)
func init() {
log.SetPrefix("TRACE: ")
log.SetFlags(log.Ldate | log.Lmicroseconds | log.Llongfile)
}
func main() {
// Println writes to the standard logger.
log.Println("message")
// Fatalln is Println() followed by a call to os.Exit(1).
log.Fatalln("fatal message")
// Panicln is Println() followed by a call to panic().
log.Panicln("panic message")
}
Fatal
系列函数用来写日志消息,然后使用 os.Exit(1)
终止程序。Panic
系列函数用来写日志消息,然后触发一个 panic
。除非程序使用 recover
函数,否则会导致程序打印调用栈后终止。Print
系列函数是写日志消息的标准方法。
log 包有一个很方便的地方就是,这些日志记录器是多 goroutine 安全的。
关键字 iota
在常量声明区里有特殊的作用。这个关键字让编译器为每个常量复制相同的表达式,直到声明区结束,或者遇到一个新的赋值语句。
关键字 iota
的另一个功能是,iota
的初始值为 0,之后 iota
的值在每次处理为常量后,都会自增 1。
定制的日志记录器:
// This sample program demonstrates how to create customized loggers.
package main
import (
"io"
"io/ioutil"
"log"
"os"
)
var (
Trace *log.Logger // Just about anything
Info *log.Logger // Important information
Warning *log.Logger // Be concerned
Error *log.Logger // Critical problem
)
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),
"ERROR: ",
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 about")
Error.Println("Something has failed")
}
当某个等级的日志不重要时,使用 ioutil.Discard
变量可以禁用这个等级的日志。
io.MultiWriter
函数可以实现同时向多个 Writer
写入。
运行结果:
INFO: 2022/05/03 15:16:57 listing11.go:44: Special Information
WARNING: 2022/05/03 15:16:57 listing11.go:45: There is something you need to know about
ERROR: 2022/05/03 15:16:57 listing11.go:46: Something has failed
8.3 编码/解码
标准库里提供了 xml 和 json 包用来处理对应格式的数据。
这是一个解码 json 响应数据的示例:
// This sample program demonstrates how to decode a JSON response
// using the json package and NewDecoder function.
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
)
type (
// gResult maps to the result document received from the search.
gResult struct {
GsearchResultClass string `json:"GsearchResultClass"`
UnescapedURL string `json:"unescapedUrl"`
URL string `json:"url"`
VisibleURL string `json:"visibleUrl"`
CacheURL string `json:"cacheUrl"`
Title string `json:"title"`
TitleNoFormatting string `json:"titleNoFormatting"`
Content string `json:"content"`
}
// gResponse contains the top level document.
gResponse struct {
ResponseData struct {
Results []gResult `json:"results"`
} `json:"responseData"`
}
)
func main() {
uri := "http://ajax.googleapis.com/ajax/services/search/web?v=1.0&rsz=8&q=golang"
// Issue the search against Google.
resp, err := http.Get(uri)
if err != nil {
log.Println("ERROR:", err)
return
}
defer resp.Body.Close()
// Decode the JSON response into our struct type.
var gr gResponse
err = json.NewDecoder(resp.Body).Decode(&gr)
if err != nil {
log.Println("ERROR:", err)
return
}
fmt.Println(gr)
// Marshal the struct type into a pretty print
// version of the JSON document.
pretty, err := json.MarshalIndent(gr, "", " ")
if err != nil {
log.Println("ERROR:", err)
return
}
fmt.Println(string(pretty))
}
结构字段类型后面的 json:"GsearchResultClass"
叫做 标签(tag) ,是提供每个字段的元信息的一种机制,将 JSON 文档和结构类型里的字段一一映射起来。如果不存在标签,对应的结构类型里的字段就包含其零值。
解码 JSON 字符串:
// Unmarshal the JSON string into our variable.
var c Contact
err := json.Unmarshal([]byte(JSON), &c)
解码 JSON 字符串到 map 变量:
var c map[string]interface{}
err := json.Unmarshal([]byte(JSON), &c)
编码 JSON:
序列化(marshal) 是指将数据转换为 JSON 字符串的过程。
下面是将一个 map 序列化为 JSON 字符串的示例:
// This sample program demonstrates how to marshal a JSON string.
package main
import (
"encoding/json"
"fmt"
"log"
)
func main() {
// Create a map of key/value pairs.
c := make(map[string]interface{})
c["name"] = "Gopher"
c["title"] = "programmer"
c["contact"] = map[string]interface{}{
"home": "415.333.3333",
"cell": "415.555.5555",
}
// Marshal the map into a JSON string.
data, err := json.MarshalIndent(c, "", " ")
if err != nil {
log.Println("ERROR:", err)
return
}
fmt.Println(string(data))
}
打印结果:
{
"contact": {
"cell": "415.555.5555",
"home": "415.333.3333"
},
"name": "Gopher",
"title": "programmer"
}
使用 json.MarshalIndent
函数将一个 map
值转换为 JSON 字符串。
MarshalIndent
函数返回一个 byte
切片。MarshalIndent
函数使用反射来确定如何将 map
类型转换为 JSON 字符串。
8.4 输入和输出
io 包是围绕着实现了 io.Writer
和 io.Reader
接口类型的值而构建的。所有实现了这两个接口的类型的值,都可以使用 io 包提供的所有功能,也可以用其它包里接受这两个接口的函数以及方法。
// Sample program to show how different functions from the
// standard library use the io.Writer interface.
package main
import (
"bytes"
"fmt"
"os"
)
// main is the entry point for the application.
func main() {
// Create a Buffer value and write a string to the buffer.
// Using the Write method that implements io.Writer.
var b bytes.Buffer
b.Write([]byte("Hello "))
// Use Fprintf to concatenate a string to the Buffer.
// Passing the address of a bytes.Buffer value for io.Writer.
fmt.Fprintf(&b, "World!")
// Write the content of the Buffer to the stdout device.
// Passing the address of a os.File value for io.Writer.
b.WriteTo(os.Stdout)
}
使用 io 包实现一个类型 curl 命令的功能。
// Sample program to show how to write a simple version of curl using
// the io.Reader and io.Writer interface support.
package main
import (
"io"
"log"
"net/http"
"os"
)
// main is the entry point for the application.
func main() {
// r here is a response, and r.Body is an io.Reader.
r, err := http.Get(os.Args[1])
if err != nil {
log.Fatalln(err)
}
// Create a file to persist the response.
file, err := os.Create(os.Args[2])
if err != nil {
log.Fatalln(err)
}
defer file.Close()
// Use MultiWriter so we can write to stdout and
// a file on the same write operation.
dest := io.MultiWriter(os.Stdout, file)
// Read the response and write to both destinations.
io.Copy(dest, r.Body)
if err := r.Body.Close(); err != nil {
log.Println(err)
}
}
8.5 小结
- 标准库有特殊的保证,并且被社区广泛应用。
- 使用标准库的包会让你的代码更易于管理,别人也会更信任你的代码。
- 100 余个包被合理组织,分布在 38 个类别里。
- 标准库里的 log 包拥有记录日志所需的一切功能。
- 标准库里的 xml 和 json 包让处理这两种数据格式变得很简单。
- io 包支持以流的方式高效处理数据。
- 接口允许你的代码组合已有的功能。
- 阅读标准库的代码是熟悉 Go 语言习惯的好方法。