golang读书笔记(四)

参考了

《Golang 使用 iota》

《Go语言Writer和Reader接口简述 》)

io — 基本的 IO 接口 · Go语言标准库 (studygolang.com)

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){
    ...
}

其中prefixindent的区别可以参考如下代码:

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包能够以流的方式高效地处理数据,而不需要考虑数据是什么,数据来自哪里以及数据要发送到哪里的问题。与stdoutstdin对应,这个包含有io.Writerio.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

推荐阅读更多精彩内容