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