参考了
1 log包
log.SetSetPrefix()
设置一个字符串,作为每个日志项的前缀log.SetFlags()
设置log的日期格式等样式,可以选用的包括:
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 //日期时间转为0时区的
LstdFlags = Ldate | Ltime //Go提供的标准抬头信息
log.Fatal
系列函数用来写日志消息,然后使用os.Exit(1)
终止程序log.Panic
系列函数用来写日志消息,然后触发一个panic
,除非程序执行recover函数,否则会导致程序打印调用栈后终止log.Print
系列函数只是进行日志写入log.New
创建并初始化一个Logger
类型的值,并返回该值的指针。New函数的定义为:
func New(out io.Writer, prefix string, flag int) *Logger{
return &Logger{out:out, prefix: prefix, flag:flag}
}
其中out
指定了日志要写入的目的地,prefix
指定了日志的前缀,flag
指定了样式(与log.SetFlags()
函数中的样式定义相同),例如(例子来源于《go in action》)
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)
io.MultiWriter
函数是一个变参函数,可以接受任意个实现了io.Writer
接口的值。这个函数会返回一个io.Writer
值,这个值会把所有传入的io.Writer
的值绑定在一起。当对这个返回值进行写入时,会向所有绑定在一起的io.Writer
值进行写入操作。
2 编码/解码
2.1 解码JSON
2.1.1 实现了io.Reader接口的对象
《Go in Action》中给了一个很好的例子,先来看一下Google API响应的JSON数据:
{
"responseData": {
"results": [
{
"GsearchResultClass": "GwebSearch",
"unescapedUrl": "https://www.reddit.com/r/golang",
"url": "https://reddit.com/r/golang",
"visibleUrl": "www.reddit.com",
"cacheUrl": "http://www.google.com/search?q=cache:...",
"title": "r/\u003cb\u003eGolang\u003c/b\u003e - Reddit",
"titleNoFormatting": "r/Golang - Reddit",
"content": "First Open Source \u003cb\u003eGolang\u..."
},
{
"GsearchResultClass": "GwebSearch",
"unescapedUrl": "http://tour.golang.org/",
"url": "http://tour.golang.org/",
"visibleUrl": "tour.golang.org",
"cacheUrl": "http://www.google.com/search?q=cache:...",
"title": "A Tour of Go",
"titleNoFormatting": "A Tour of Go",
"content": "Welcome to a tour of the Go programming..."
}
]
}
}
为了把上述数据映射到结构体中,可以使用如下代码:
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
)
type(
gResult struct {
GsearchResultClass string `json:"GsearchResultClass"`
UnescapedURL string `json:"unescapedUrl"`
URL string `json:"url"`
VisableUrl string `json:"visibleUrl"`
CacheURL string `json:"cacheURL"`
Title string `json:"title"`
TitleNoFormatting string `json:"titleNoFormatting"`
Content string `json:"content"`
}
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"
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)
}
其中最重要的就是json包中的NewDecoder()
函数以及Decoder()
方法,先来看一下这两个东东的声明:
func NewDecoder(r io.Reader) *Decoder
func (dec *Decoder) Decoder(v interface{}) error
2.1.2 字符串对象
如果需要转换的对象不是实现了io.Reader
接口的对象,而是string
对象,那么需要首先将字符串格式的json文档转换为byte的切片,然后使用Unmarshal函数进行反序列化处理。代码如下:
package main
import (
"encoding/json"
"fmt"
"log"
)
type Contact struct {
Name string `json:"name"`
Title string `json:"title"`
Contact struct {
Home string `json:"home"`
Cell string `json:"cell"`
} `json:"contact"`
}
var JSON = `{
"name": "Gopher",
"title": "programmer"
"contact": {
"home": "415.333.3333",
"cell": "415.555.5555"
}
}`
func main(){
var c Contact
err := json.Unmarshal([]byte(JSON), &c)
if err != nil {
log.Println("ERROR:", err)
return
}
fmt.Println(c)
}
如果只是对JSON文档进行简单的处理,除了将JSON对象转换为结构体外,还可以将其转换为map
对象,代码如下:
package main
import (
"encoding/json"
"fmt"
"log"
)
var JSON = `{
"name": "Gopher",
"title": "programmer"
"contact": {
"home": "415.333.3333",
"cell": "415.555.5555"
}
}`
func main() {
var c map[string]interface{}
err := json.Unmarshal([]byte(JSON), &c)
if err != nil {
log.Println("ERROR:", err)
return
}
fmt.Println("Name", c["name"])
fmt.Println("Title", c["title"])
fmt.Println("Contact")
fmt.Println("H:", c["contact"].(map[string]interface{})["home"])
fmt.Println("C:", c["contact"].(map[string]interface{})["cell"])
}
小结
-
json.NewDecoder(<io.Reader对象>).Decoder(&<结构体>)
:io.Reader
接口对象的反序列化 -
json.Unmarshal([]byte(<字符串>), &<结构体/map对象>)
:字符串对象的反序列化
2.2 编码JSON
结构体数据的序列化相对简单,只需要使用json
包的MarshalIndent
函数或者Marshal
函数(没有对json数据进行格式化)即可。其定义为:
func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error){
...
}
其中prefix
和indent
的区别可以参考如下代码:
package main
import (
"encoding/json"
"fmt"
"log"
)
func main() {
data := map[string]int{
"a": 1,
"b": 2,
}
json, err := json.MarshalIndent(data, "<prefix>", "<indent>")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(json))
}
其结果为:
{
<prefix><indent>"a": 1,
<prefix><indent>"b": 2
<prefix>}
3 输入与输出
go语言的io包能够以流的方式高效地处理数据,而不需要考虑数据是什么,数据来自哪里以及数据要发送到哪里的问题。与
stdout
和stdin
对应,这个包含有io.Writer
和io.Reader
两个接口,所有实现了这两个接口的类型的值,都可以使用io包提供的所有功能,也可以用于其他包里接收这两个接口的函数以及方法。——《Go in Action》
3.1 io.Writer
io.Writer接口的声明如下:
type Writer interface {
Write(p []byte) (n int, err error)
}
该方法输入是一个byte的切片,返回值有两个:
- n 表示写入的字节数
- err 表示错误值
3.1.1 一些实现了io.Writer的类型
ioutil.Discard
ioutil.Discard
虽然实现了io.Writer
的接口,但是实际上,它不会执行任何操作,但是执行写入操作后会成功返回。下面的代码展示了Discard的实现:
type devNull int
var Discard io.Writer = devNull(0)
func (devNull) Writer(p []byte) (int, error){
return len(p), nil
}
os.Stdin,os.Stdout,os.Stderr
三者的声明如下:
var{
Stdin = NewFile(uinptr(syscall.Stdin) , "/dev/stdin")
Stdout = NewFile(uinptr(syscall.Stdout), "/dev/stdout")
Stderr = NewFile(uinptr(syscall.Stderr), "/dev/stderr")
}
可以看到三个变量的声明,分别表示所有操作系统里都有的三个标准输入/输出,三个变量都是File
类型的指针(实现了io.Writer
接口)
3.2 io.Reader
io.Reader
接口的声明如下:
type Reader interface{
Read(p []byte) (n int, err error)
}
该接口的Read
方法与Writer
接口的Write
方法完全一样,其输入参数是一个byte
切片,返回值也是两个:
- n 表示读入的字节数
- err 表示错误值
读取与写入相比,需要说明的内容也更多一些,在使用过程中主要有以下几个方面需要注意:
-
Read
最多读入len(p)
个字节,并保存到p
中。如果读取的字节数小于byte切片的长度,不会等待新数据,而是直接返回已经读取到的数据。 - 当读到最后一个字节时,有两种选择:
- 返回最终读到的字节数,并且返回
EOF
作为错误值 - 返回最终读到的字节数,并返回
nil
作为错误值;之后再读取数据的时候,会返回字节长度为0,同时以EOF
作为错误值
- 返回最终读到的字节数,并且返回
- 【建议1】
Read
返回了读取的字节数,都应该先处理这些读取到的字节,再去检查EOF错误或者其他错误 - 【建议2】
Read
方法永远不要返回0个读取字节并以nil作为错误值,如果没有读取到数据,Read应该返回一个错误
附1 iota
iota的常规用法
iota
是go中的常量计数器,只能在常量中使用。iota在const出现时,将会被重置为0,const中每新增一行,都会使iota增加1。此外,iota还有缩略的写法,例如:
const (
i = iota
j = iota
k
l
)
其中i和j的赋值都采用的是完整形式,而k和l的赋值则使用了缩略形式,其完整写法应该为:
const (
i = iota
j = iota
k = iota
l = iota
)
不管采用哪种方式进行赋值,其结果都是:
i = 0
j = 1
k = 2
l = 3
但是,如果我们把代码写成这样:
const (
i = iota
j
)
const (
k = iota
l
)
情况就会有所不同,其结果应该为:
i = 0
j = 1
k = 0
l = 1
如果想要跳过某些值,仍然可以使用go中常用的下划线来进行占位,例如:
const (
i = iota
j
_
k
l
)
那么结果就变成了:
i = 0
j = 1
k = 3
l = 4
iota的一些其他用法
iota与移位操作相结合
const (
Ldate = 1 << iota // 1 << 0 = 00000001 = 1
Ltime // 1 << 1 = 00000010 = 2
Lmicroseconds // 1 << 2 = 00000100 = 4
Llongfile // 1 << 3 = 00001000 = 8
Lshortfile // 1 << 4 = 00010000 = 16
)
iota定义在一行
如果将iota定义在同一行,则其值不会发生变化,请看下面的例子:
const (
Apple, Banana = iota + 1, iota + 2
Cherimoya, Durian
Elderberry, Fig
)
其结果为:
Apple = 1
Banana = 2
Cherimoya = 2
Durian = 3
Elderberry = 3
Fig = 4