golang序列化方法有:1、利用Gob包管理gob流,gob是和類型綁定的,如果發(fā)現(xiàn)多了或者少了,會依據(jù)順序填充或者截斷。2、利用json包,能實現(xiàn)RFC 7159中定義的JSON編碼和解碼;在序列化的過程中,如果結(jié)構(gòu)體內(nèi)的成員是小寫的,則會出現(xiàn)錯誤。3、利用Binary包,能實現(xiàn)數(shù)字和字節(jié)序列之間的簡單轉(zhuǎn)換以及varint的編碼和解碼。4、利用protobuf協(xié)議。
本教程操作環(huán)境:windows7系統(tǒng)、GO 1.18版本、Dell G3電腦。
在編程過程中,我們總是要遇到這樣的問題,就是將我們的數(shù)據(jù)對象要在網(wǎng)絡(luò)中傳輸或保存到文件,這就需要對其編碼和解碼動作。
目前存在很多編碼格式:json, XML, Gob, Google Protocol Buffer 等,在Go 語言中,如何對數(shù)據(jù)進行這樣的編碼和解碼呢?
序列化和反序列化定義
序列化 (Serialization)是將對象的狀態(tài)信息轉(zhuǎn)換為可以存儲或傳輸?shù)男问降倪^程。在序列化期間,對象將其當前狀態(tài)寫入到臨時或持久性存儲區(qū)。
反過來,把變量從從存儲區(qū)中重新讀取,重新創(chuàng)建該對象,則為反序列化。
在Go語言中,encoding 包就是專門來處理這類序列化的編碼和解碼的問題。
序列化方式–Gob
gob
包管理 gob 流–編碼器(發(fā)送器)和解碼器(接收器)之間交換的二進制值。一個典型的用途是傳輸遠程過程調(diào)用(RPCs)的參數(shù)和結(jié)果,如 "net/rpc "包中就使用了gobs 流。
具體可以參考文檔:https://docs.studygolang.com/pkg/encoding/gob/
他的官網(wǎng)給出了一個示例:
package main import ( "bytes" "encoding/gob" "fmt" "log" ) type P struct { X, Y, Z int Name string } type Q struct { X, Y *int32 Name string } // This example shows the basic usage of the package: Create an encoder, // transmit some values, receive them with a decoder. 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 //Buffer是具有Read和Write方法的可變大小的字節(jié)緩沖區(qū)。 enc := gob.NewEncoder(&network) // Will write to network. dec := gob.NewDecoder(&network) // Will read from network. // Encode (send) some values. err := enc.Encode(P{3, 4, 5, "Pythagoras"}) if err != nil { log.Fatal("encode error:", err) } err = enc.Encode(P{1782, 1841, 1922, "Treehouse"}) if err != nil { log.Fatal("encode error:", err) } // Decode (receive) and print the values. var q Q err = dec.Decode(&q) if err != nil { log.Fatal("decode error 1:", err) } fmt.Printf("%q: {%d, %d}n", q.Name, *q.X, *q.Y) err = dec.Decode(&q) if err != nil { log.Fatal("decode error 2:", err) } fmt.Printf("%q: {%d, %d}n", q.Name, *q.X, *q.Y) }
運行結(jié)果是:
"Pythagoras": {3, 4} "Treehouse": {1782, 1841}
個人認為這個例子是真的好。我們看到,結(jié)構(gòu)體P
和 Q
是不同的,我們看到Q
少了一個 Z
變量。
但是,在解碼的時候,仍然能解析得出來,這說明,使用 gob
時,是根據(jù)類型綁定的,如果發(fā)現(xiàn)多了或者少了,會依據(jù)順序填充或者截斷。
接下來,我們詳情說說怎么編碼吧:
1. bytes.Buffer
類型
首先,我們需要定義一個 bytes.Buffer
類型,用來承接需要序列化的結(jié)構(gòu)體,這個類型是這樣的:
// A Buffer is a variable-sized buffer of bytes with Read and Write methods.(Buffer是具有Read和Write方法的可變大小的字節(jié)緩沖區(qū)) // The zero value for Buffer is an empty buffer ready to use. type Buffer struct { buf []byte // contents are the bytes buf[off : len(buf)] off int // read at &buf[off], write at &buf[len(buf)] lastRead readOp // last read operation, so that Unread* can work correctly. }
使用上面的例子,可以看到輸出是:
"Pythagoras": {3, 4} ==> {[42 255 129 3 1 1 1 80 1 255 130 0 1 4 1 1 88 1 4 0 1 1 89 1 4 0 1 1 90 1 4 0 1 4 78 97 109 101 1 12 0 0 0 21 255 130 1 6 1 8 1 10 1 10 80 121 116 104 97 103 111 114 97 115 0] 0 0}
可以看到,Buffer
里,是二進制數(shù)(一個字節(jié)8個bit,最高255)
2. Encode 編碼
之后,對需要編碼序列化的結(jié)構(gòu)體進行編碼:
enc := gob.NewEncoder(&network) // Will write to network. // Encode (send) some values. if err := enc.Encode(P{3, 4, 5, "Pythagoras"}); err != nil { log.Fatal("encode error:", err) }
這里,首先是要獲得 *Encoder
對象,獲得對象后,利用 *Encoder
對象的方法 Encode
進行編碼。
這里,需要注意的是,
Encode
如果是網(wǎng)絡(luò)編程的,其實是可以直接發(fā)送消息給對方的,而不必進行 socket 的send 操作。
比如:在 srever
端有代碼:
func main() { l, err := net.Listen("tcp", "127.0.0.1:8000") //監(jiān)聽端口 if err != nil { log.Fatal("net Listen() error is ", err) } p := P{ 1, 2, 3, "name"} conn, err := l.Accept() if err != nil { log.Fatal("net Accept() error is ", err) } defer func() { _ = conn.Close() }() //參數(shù)是conn 時,即可發(fā)出 enc := gob.NewEncoder(conn) if err = enc.Encode(p); err != nil { //發(fā)生結(jié)構(gòu)體數(shù)據(jù) log.Fatal("enc Encode() error is ", err) } }
在客戶端client有:
func main() { conn,err := net.Dial("tcp","127.0.0.1:8000") if err != nil { log.Fatal("net Dial() error is ", err) } defer func() { _ = conn.Close() }() /** type Q struct { X, Y int Name string } */ var q Q dec := gob.NewDecoder(conn) if err = dec.Decode(&q); err != nil { log.Fatal("enc Encode() error is ", err) } fmt.Println(q) }
輸出:
{1 2 name}
3. Decode 解碼
最后,對其解碼的步驟為:
dec := gob.NewDecoder(&network) // Will read from network. if err = dec.Decode(&q);err != nil { log.Fatal("decode error 2:", err) }
序列化方式–json
json
包實現(xiàn)了 RFC 7159
中定義的 JSON
編碼和解碼。JSON和Go值之間的映射在 Marshal 和 Unmarshal 函數(shù)的文檔中進行了描述。
有關(guān)此程序包的介紹,請參見“ JSON和Go”:https://www.php.cn/link/241200d15bc67211b50bd10815259e58json/
示例如下:
type Message struct { QQ string Address string } type Student struct { Id uint64 `json:"id"` //可以保證json字段按照規(guī)定的字段轉(zhuǎn)義,而不是輸出 Id Age uint64 `json:"age"` Data []Message } func main() { m1 := Message{QQ: "123", Address: "beijing"} m2 := Message{QQ: "456", Address: "beijing"} s1 := Student{3, 19, append([]Message{}, m1, m2)} var buf []byte var err error if buf, err = json.Marshal(s1); err != nil { log.Fatal("json marshal error:", err) } fmt.Println(string(buf)) var s2 Student if err = json.Unmarshal(buf, &s2); err != nil { log.Fatal("json unmarshal error:", err) } fmt.Println(s2) } //輸出: //{"id":3,"age":19,"Data":[{"QQ":"123","Address":"beijing"},{"QQ":"456","Address":"beijing"}]} //{3 19 [{123 beijing} {456 beijing}]}
注意
在序列化的過程中,如果結(jié)構(gòu)體內(nèi)的成員是小寫的,則會出現(xiàn)錯誤。以上兩種方式,都會出現(xiàn)這樣的結(jié)果
我們以 json
序列化為例子,看一下如果是小寫的話,會出現(xiàn)什么樣的結(jié)果:
package main import ( "encoding/json" "fmt" "log" ) type Message struct { qq string address string } type Student struct { Id uint64 `json:"id"` //可以保證json字段按照規(guī)定的字段轉(zhuǎn)義,而不是輸出 Id Age uint64 `json:"age"` Data []Message } func main() { m1 := Message{"123", "beijing"} m2 := Message{"456", "beijing"} s1 := Student{3, 19, append([]Message{}, m1, m2)} var buf []byte var err error if buf, err = json.Marshal(s1); err != nil { log.Fatal("json marshal error:", err) } fmt.Println(string(buf)) var s2 Student if err = json.Unmarshal(buf, &s2); err != nil { log.Fatal("json unmarshal error:", err) } fmt.Println(s2) }
輸出:
{"id":3,"age":19,"Data":[{},{}]} {3 19 [{ } { }]}
我們看到,小寫的部分將不會被序列化到,也就是說,會是空值。
這個雖然不會報錯,但是很明顯,不是我們想要看到的結(jié)果。
報錯:gob: type xxx has no exported fields
我們來看一個會報錯的例子:
type Message struct { qq string address string } type Student struct { Id uint64 `json:"id"` //可以保證json字段按照規(guī)定的字段轉(zhuǎn)義,而不是輸出 Id Age uint64 `json:"age"` Data []Message } func main() { m1 := Message{"123", "beijing"} m2 := Message{"456", "beijing"} s1 := Student{3, 19, append([]Message{}, m1, m2)} var buf bytes.Buffer enc := gob.NewEncoder(&buf) if err := enc.Encode(s1); err != nil { log.Fatal("encode error:", err) //報錯 } fmt.Println(string(buf.Bytes())) }
這段代碼會報錯:
2020/12/30 16:44:47 encode error:gob: type main.Message has no exported fields
提醒我們注意,結(jié)構(gòu)體的大小寫是很敏感的?。?!
序列化方式–Binary
Binary
包實現(xiàn) 數(shù)字 和 字節(jié) 序列之間的簡單轉(zhuǎn)換以及varint的編碼和解碼。
通過讀取和寫入固定大小的值來轉(zhuǎn)換數(shù)字。 固定大小的值可以是固定大小的算術(shù)類型(bool,int8,uint8,int16,float32,complex64等),也可以是僅包含固定大小值的數(shù)組或結(jié)構(gòu)體。詳情可參考:https://www.php.cn/link/241200d15bc67211b50bd10815259e58binary/#Write
示例:
package main import ( "bytes" "encoding/binary" "fmt" ) func main() { buf := new(bytes.Buffer) var pi int64 = 255 err := binary.Write(buf, binary.LittleEndian, pi) if err != nil { fmt.Println("binary.Write failed:", err) } fmt.Println( buf.Bytes()) } //輸出: [255 0 0 0 0 0 0 0]
這里需要注意:如果序列化的類型是 int
類型的話,將會報錯:
binary.Write failed: binary.Write: invalid type int
而且,序列化的值是空的。
這是由于,他在前面已經(jīng)解釋清楚了,只能序列化固定大小的類型(bool,int8,uint8,int16,float32,complex64…),或者是結(jié)構(gòu)體和固定大小的數(shù)組。
其他序列化方法
當然,go語言還有其他的序列化方法,如 protobuf
協(xié)議,參考:https://geektutu.com/post/quick-go-protobuf.html
【