GoLang读写数据---下

Posted 大忽悠爱忽悠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了GoLang读写数据---下相关的知识,希望对你有一定的参考价值。

GoLang读写数据---下


格式化 JSON 数据

数据结构要在网络中传输或保存到文件,就必须对其编码和解码;目前存在很多编码格式:JSON,XML,gob,Google 缓冲协议等等。Go 语言支持所有这些编码格式;在本节,我们将讨论前三种格式。

结构可能包含二进制数据,如果将其作为文本打印,那么可读性是很差的。另外结构内部可能包含匿名字段,而不清楚数据的用意。

通过把数据转换成纯文本,使用命名的字段来标注,让其具有可读性。这样的数据格式可以通过网络传输,而且是与平台无关的,任何类型的应用都能够读取和输出,不与操作系统和编程语言的类型相关。

下面是一些术语说明:

  • 数据结构 —> 指定格式 = 序列化 或 编码(传输之前)
  • 指定格式 —> 数据格式 = 反序列化 或 解码(传输之后)

序列化是在内存中把数据转换成指定格式(data -> string),反之亦然(string -> data structure)

编码也是一样的,只是输出一个数据流(实现了 io.Writer 接口);解码是从一个数据流(实现了 io.Reader)输出到一个数据结构。

我们都比较熟悉 XML 格式;但有些时候 JSON(javascript Object Notation,参阅 http://json.org)被作为首选,主要是由于其格式上非常简洁。通常 JSON 被用于 web 后端和浏览器之间的通讯,但是在其它场景也同样的有用。

这是一个简短的 JSON 片段:


    "Person": 
        "FirstName": "Laura",
        "LastName": "Lynn"
    

尽管 XML 被广泛的应用,但是 JSON 更加简洁、轻量(占用更少的内存、磁盘及网络带宽)和更好的可读性,这也使它越来越受欢迎。

Go 语言的 json 包可以让你在程序中方便的读取和写入 JSON 数据。

// json.go
package main

import (
	"encoding/json"
	"fmt"
	"log"
	"os"
)

type Address struct 
	Type    string
	City    string
	Country string


type VCard struct 
	FirstName string
	LastName  string
	Addresses []*Address
	Remark    string


func main() 
	pa := &Address"private", "Aartselaar", "Belgium"
	wa := &Address"work", "Boom", "Belgium"
	vc := VCard"Jan", "Kersschot", []*Addresspa, wa, "none"
	// JSON format:
	js, _ := json.Marshal(vc)
	fmt.Printf("JSON format: %s", js)

	// using an encoder:
	file, _ := os.OpenFile("vcard.json", os.O_CREATE|os.O_WRONLY, 0666)
	defer file.Close()
	enc := json.NewEncoder(file)
	err := enc.Encode(vc)
	if err != nil 
		log.Println("Error in encoding json")
	


json.Marshal() 的函数签名是 func Marshal(v interface) ([]byte, error),下面是数据编码后的 JSON 文本(实际上是一个 []byte):


    "FirstName": "Jan",
    "LastName": "Kersschot",
    "Addresses": [
        "Type": "private",
        "City": "Aartselaar",
        "Country": "Belgium"
    , 
        "Type": "work",
        "City": "Boom",
        "Country": "Belgium"
    ],
    "Remark": "none"

出于安全考虑,在 web 应用中最好使用 json.Marshalforhtml() 函数,其对数据执行HTML转码,所以文本可以被安全地嵌在 HTML <script> 标签中。

json.NewEncoder() 的函数签名是 func NewEncoder(w io.Writer) *Encoder,返回的Encoder类型的指针可调用方法 Encode(v interface),将数据对象 v 的json编码写入 io.Writer w 中。

JSON 与 Go 类型对应如下:

  • bool 对应 JSON 的 boolean
  • float64 对应 JSON 的 number
  • string 对应 JSON 的 string
  • nil 对应 JSON 的 null

不是所有的数据都可以编码为 JSON 类型:只有验证通过的数据结构才能被编码:

  • JSON 对象只支持字符串类型的 key;要编码一个 Go map 类型,map 必须是 map[string]T(T是 json 包中支持的任何类型)
  • Channel,复杂类型和函数类型不能被编码
  • 不支持循环数据结构;它将引起序列化进入一个无限循环
  • 指针可以被编码,实际上是对指针指向的值进行编码(或者指针是 nil)

反序列化:

UnMarshal() 的函数签名是 func Unmarshal(data []byte, v interface) error 把 JSON 解码为数据结构。

虽然反射能够让 JSON 字段去尝试匹配目标结构字段;但是只有真正匹配上的字段才会填充数据。字段没有匹配不会报错,而是直接忽略掉。

解码任意的数据:

json 包使用 map[string]interface 和 []interface 储存任意的 JSON 对象和数组;其可以被反序列化为任何的 JSON blob 存储到接口值中。

来看这个 JSON 数据,被存储在变量 b 中:

b := []byte(`"Name": "Wednesday", "Age": 6, "Parents": ["Gomez", "Morticia"]`)

不用理解这个数据的结构,我们可以直接使用 Unmarshal 把这个数据编码并保存在接口值中:

var f interface
err := json.Unmarshal(b, &f)

f 指向的值是一个 map,key 是一个字符串,value 是自身存储作为空接口类型的值:

map[string]interface 
    "Name": "Wednesday",
    "Age":  6,
    "Parents": []interface 
        "Gomez",
        "Morticia",
    ,

要访问这个数据,我们可以使用类型断言

m := f.(map[string]interface)

我们可以通过 for range 语法和 type switch 来访问其实际类型:

for k, v := range m 
    switch vv := v.(type) 
    case string:
        fmt.Println(k, "is string", vv)
    case int:
        fmt.Println(k, "is int", vv)
    case []interface:
        fmt.Println(k, "is an array:")
        for i, u := range vv 
            fmt.Println(i, u)
        
    default:
        fmt.Println(k, "is of a type I don’t know how to handle")
    

通过这种方式,你可以处理未知的 JSON 数据,同时可以确保类型安全。


解码数据到结构

如果我们事先知道 JSON 数据,我们可以定义一个适当的结构并对 JSON 数据反序列化。下面的例子中,我们将定义:

type FamilyMember struct 
    Name    string
    Age     int
    Parents []string

并对其反序列化:

var m FamilyMember
err := json.Unmarshal(b, &m)

程序实际上是分配了一个新的切片。这是一个典型的反序列化引用类型(指针、切片和 map)的例子。


编码和解码流

json 包提供 Decoder 和 Encoder 类型来支持常用 JSON 数据流读写。NewDecoder 和 NewEncoder 函数分别封装了 io.Reader 和 io.Writer 接口。

func NewDecoder(r io.Reader) *Decoder
func NewEncoder(w io.Writer) *Encoder

要想把 JSON 直接写入文件,可以使用 json.NewEncoder 初始化文件(或者任何实现 io.Writer 的类型),并调用 Encode();反过来与其对应的是使用 json.NewDecoder 和 Decode() 函数:

func NewDecoder(r io.Reader) *Decoder
func (dec *Decoder) Decode(v interface) error

来看下接口是如何对实现进行抽象的:数据结构可以是任何类型,只要其实现了某种接口,目标或源数据要能够被编码就必须实现 io.Writer 或 io.Reader 接口。由于 Go 语言中到处都实现了 Reader 和 Writer,因此 Encoder 和 Decoder 可被应用的场景非常广泛,例如读取或写入 HTTP 连接、websockets 或文件。


XML 数据格式

下面是与 JSON 例子等价的 XML 版本:

<Person>
    <FirstName>Laura</FirstName>
    <LastName>Lynn</LastName>
</Person>

如同 json 包一样,也有 Marshal() 和 UnMarshal() 从 XML 中编码和解码数据;但这个更通用,可以从文件中读取和写入(或者任何实现了 io.Reader 和 io.Writer 接口的类型)

和 JSON 的方式一样,XML 数据可以序列化为结构,或者从结构反序列化为 XML 数据;

encoding/xml 包实现了一个简单的 XML 解析器(SAX),用来解析 XML 数据内容。下面的例子说明如何使用解析器:

// xml.go
package main
import (
    "encoding/xml"
    "fmt"
    "strings"
)
var t, token xml.Token
var err error
func main() 
    input := "<Person><FirstName>Laura</FirstName><LastName>Lynn</LastName></Person>"
    inputReader := strings.NewReader(input)
    p := xml.NewDecoder(inputReader)
    for t, err = p.Token(); err == nil; t, err = p.Token() 
        switch token := t.(type) 
        case xml.StartElement:
            name := token.Name.Local
            fmt.Printf("Token name: %s\\n", name)
            for _, attr := range token.Attr 
                attrName := attr.Name.Local
                attrValue := attr.Value
                fmt.Printf("An attribute is: %s %s\\n", attrName, attrValue)
                // ...
            
        case xml.EndElement:
            fmt.Println("End of token")
        case xml.CharData:
            content := string([]byte(token))
            fmt.Printf("This is the content: %v\\n", content)
            // ...
        default:
            // ...
        
    

输出:

Token name: Person
Token name: FirstName
This is the content: Laura
End of token
Token name: LastName
This is the content: Lynn
End of token
End of token

包中定义了若干 XML 标签类型:StartElement,Chardata(这是从开始标签到结束标签之间的实际文本),EndElement,Comment,Directive 或 ProcInst。

包中同样定义了一个结构解析器:NewParser 方法持有一个 io.Reader(这里具体类型是 strings.NewReader)并生成一个解析器类型的对象。还有一个 Token() 方法返回输入流里的下一个 XML token。在输入流的结尾处,会返回(nil,io.EOF)

XML 文本被循环处理直到 Token() 返回一个错误,因为已经到达文件尾部,再没有内容可供处理了。通过一个 type-switch 可以根据一些 XML 标签进一步处理。Chardata 中的内容只是一个 []byte,通过字符串转换让其变得可读性强一些。


用 Gob 传输数据

Gob 是 Go 自己的以二进制形式序列化和反序列化程序数据的格式;可以在 encoding 包中找到。这种格式的数据简称为 Gob (即 Go binary 的缩写)。类似于 Python 的 “pickle” 和 Java 的 “Serialization”。

Gob 通常用于远程方法调用参数和结果的传输,以及应用程序和机器之间的数据传输。 它和 JSON 或 XML 有什么不同呢?

Gob 特定地用于纯 Go 的环境中,例如,两个用 Go 写的服务之间的通信。这样的话服务可以被实现得更加高效和优化。 Gob 不是可外部定义,语言无关的编码方式。因此它的首选格式是二进制,而不是像 JSON 和 XML 那样的文本格式。 Gob 并不是一种不同于 Go 的语言,而是在编码和解码过程中用到了 Go 的反射。

Gob 文件或流是完全自描述的:里面包含的所有类型都有一个对应的描述,并且总是可以用 Go 解码,而不需要了解文件的内容。

只有可导出的字段会被编码,零值会被忽略。在解码结构体的时候,只有同时匹配名称和可兼容类型的字段才会被解码。当源数据类型增加新字段后,Gob 解码客户端仍然可以以这种方式正常工作:解码客户端会继续识别以前存在的字段。并且还提供了很大的灵活性,比如在发送者看来,整数被编码成没有固定长度的可变长度,而忽略具体的 Go 类型。

假如在发送者这边有一个有结构 T:

type T struct  X, Y, Z int 
var t = TX: 7, Y: 0, Z: 8

而在接收者这边可以用一个结构体 U 类型的变量 u 来接收这个值:

type U struct  X, Y *int8 
var u U

在接收者中,X 的值是7,Y 的值是0(Y的值并没有从 t 中传递过来,因为它是零值)

和 JSON 的使用方式一样,Gob 使用通用的 io.Writer 接口,通过 NewEncoder() 函数创建 Encoder 对象并调用 Encode();相反的过程使用通用的 io.Reader 接口,通过 NewDecoder() 函数创建 Decoder 对象并调用 Decode()。

一个编解码,并且以字节缓冲模拟网络传输的简单例子:

// gob1.go
package main
import (
    "bytes"
    "fmt"
    "encoding/gob"
    "log"
)
type P struct 
    X, Y, Z int
    Name    string

type Q struct 
    X, Y *int32
    Name string

func main() 
    // Initialize the encoder and decoder.  Normally enc and dec would be      
    // bound to network connections and the encoder and decoder would      
    // run in different processes.      
    var network bytes.Buffer   // Stand-in for a network connection      
    enc := gob.NewEncoder(&network) // Will write to network.      
    dec := gob.NewDecoder(&network)    // Will read from network.      
    // Encode (send) the value.      
    err := enc.Encode(P3, 4, 5, "Pythagoras")
    if err != nil 
        log.Fatal("encode error:", err)
    
    // Decode (receive) the value.      
    var q Q
    err = dec.Decode(&q)
    if err != nil 
        log.Fatal("decode error:", err)
    
    fmt.Printf("%q: %d,%d\\n", q.Name, q.X, q.Y)

// Output:   "Pythagoras": 3,4

编码到文件:

// gob2.go
package main
import (
    "encoding/gob"
    "log"
    "os"
)
type Address struct 
    Type             string
    City             string
    Country          string

type VCard struct 
    FirstName    string
    LastName    string
    Addresses    []*Address
    Remark        string

var content    string
func main() 
    pa := &Address"private", "Aartselaar","Belgium"
    wa := &Address"work", "Boom", "Belgium"
    vc := VCard"Jan", "Kersschot", []*Addresspa,wa, "none"
    // fmt.Printf("%v: \\n", vc) // Jan Kersschot [0x126d2b80 0x126d2be0] none:
    // using an encoder:
    file, _ := os.OpenFile("vcard.gob", os.O_CREATE|os.O_WRONLY, 0666)
    defer file.Close()
    enc := gob.NewEncoder(file)
    err := enc.Encode(vc)
    if err != nil 
        log.Println("Error in encoding gob")
    


Go 中的密码学

通过网络传输的数据必须加密,以防止被 hacker(黑客)读取或篡改,并且保证发出的数据和收到的数据检验和一致。 鉴于 Go 母公司的业务,我们毫不惊讶地看到 Go 的标准库为该领域提供了超过 30 个的包:

  • hash 包:实现了 adler32、crc32、crc64 和 fnv 校验;
  • crypto 包:实现了其它的 hash 算法,比如 md4、md5、sha1 等。以及完整地实现了aes、blowfish、rc4、rsa、xtea 等加密算法。

下面的示例用 sha1 和 md5 计算并输出了一些校验值。

// hash_sha1.go
package main
import (
    "fmt"
    "crypto/sha1"
    "io"
    "log"
)
func main() 
    hasher := sha1.New()
    io.WriteString(hasher, "test")
    b := []byte
    fmt.Printf("Result: %x\\n", hasher.Sum(b))
    fmt.Printf("Result: %d\\n", hasher.Sum(b))
    //
    hasher.Reset()
    data := []byte("We shall overcome!")
    n, err := hasher.Write(data)
    if n!=len(data) || err!=nil 
        log.Printf("Hash write error: %v / %v", n, err)
    
    checksum := hasher.Sum(b)
    fmt.Printf("Result: %x\\n", checksum)

输出:

Result: a94a8fe5ccb19ba61c4c0873d391e987982fbbd3
Result: [169 74 143 229 204 177 155 166 28 76 8 115 211 145 233 135 152 47 187 211]
Result: e2222bfc59850bbb00a722e764a555603bb59b2a

通过调用 sha1.New() 创建了一个新的 hash.Hash 对象,用来计算 SHA1 校验值。Hash 类型实际上是一个接口,它实现了 io.Writer 接口:

type Hash interface 
    // Write (via the embedded io.Writer interface) adds more data to the running hash.
    // It never returns an error.
    io.Writer
    // Sum appends the current hash to b and returns the resulting slice.
    // It does not change the underlying hash state.
    Sum(b []byte) []byte
    // Reset resets the Hash to its initial state.
    Reset()
    // Size returns the number of bytes Sum will return.
    Size() int
    // BlockSize returns the hash's underlying block size.
    // The Write method must be able to accept any amount
    // of data, but it may operate more efficiently if all writes
    // are a multiple of the block size.
    BlockSize() int

通过 io.WriteString 或 hasher.Write 将给定的 []byte 附加到当前的 hash.Hash 对象中。

以上是关于GoLang读写数据---下的主要内容,如果未能解决你的问题,请参考以下文章

Golang面经ChannelContextGoroutine

GoLang读写数据---中

GoLang读写数据---上

Golang面经ChannelContextGoroutine

Golang 并发读写map安全问题详解

Golang读写文件的几种方式