golang數據傳輸格式-序列化與反序列化
作者:尹正傑
版權聲明:原創作品,謝絕轉載!否則將追究法律責任。
想必計算機專業畢業的小伙伴應該都知道數據想要持久化存儲,必須將其存在I/O設備里面,這些I/O設備可以是光盤,U盤,機械硬盤,移動硬盤等等。那么這些數據是以哪種方式進程存取的呢?這就是我們聊的數據傳輸格式。
數據格式(data format)是描述數據保存在文件或記錄中的規則。可以是字符形式的文本格式,或二進制數據形式的壓縮格式。字符形式的文本格式占用的存貯空間多但透明度高,二進制數形式的壓縮格式占用的存貯空間少但缺少透明度。數據結構是計算機存儲、組織數據的方式。數據結構是指相互之間存在一種或多種特定關系的數據元素的集合。通常情況下,精心選擇的數據結構可以帶來更高的運行或者存儲效率。
數據結構往往同高效的檢索算法和索引技術有關。[1] 數據結構要在網絡中傳輸或保存到文件,就必須對其編碼和解碼;目前存在很多編碼格式:JSON,XML,gob,Google 緩沖協議等等。Go 語言支持所有這些編碼格式。不過本篇博客將討論前三種格式。
一.XML
XML作為一種數據交換和信息傳遞的格式已經十分普及。而隨着 Web服務日益廣泛的應用,現在XML在日常的開發工作中也扮演了愈發重要的角色。如同 json 包一樣,也有 Marshal() 和 UnMarshal() 從 XML 中編碼和解碼數據;但這個更通用,可以從文件中讀取和寫入(或者任何實現了 io.Reader 和 io.Writer 接口的類型)和 JSON 的方式一樣,XML 數據可以序列化為結構,或者從結構反序列化為 XML 數據;
接下來我們將對“yinzhengjie.xml”文件進行反序列化,其文件內容如下:
1 <?xml version="1.0" encoding="UTF-8"?> <!--第一行是 XML 聲明。它定義 XML 的版本 (1.0) 和所使用的"UTF-8"編碼。--> 2 <UserInformation> <!--第一行描述文檔的根元素(像在說:“本文檔是記錄用戶信息的”):--> 3 <string age="18">yinzhengjie</string> <!--本行和以下兩行都是跟的子元素,當然你可以給它嵌套更多的子子孫孫。注意,這里的標簽名稱是“string”,這個標簽名稱你也可以自己隨意更改的喲!--> 4 <string age="20">尹正傑</string> 5 </UserInformation> <!--最后一行定義根元素的結尾-->
1.Xml數據格式的反序列化
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 package main 9 10 import ( 11 "encoding/xml" 12 "io/ioutil" 13 "log" 14 "fmt" 15 ) 16 17 var ( 18 InputFile = "E:\\Code\\Golang\\Golang_Program\\數據格式進階\\yinzhengjie.xml" 19 ) 20 21 22 type RootElement struct { //這是定義根元素的,我們需要做的就是講XML數據的結構先定義出來,就是為了方便主函數一會直接通過這個結構去讀取數據。 23 XMLName xml.Name `xml:"UserInformation"` //這個我們指定XML的根元素,我們也可以叫它最外層標簽。 24 ResourceString []ChildElement `xml:"string"` //上面是指定父標簽的話,這個應該就不用多做解釋吧,當然是子標簽了啦。如果你的子表情有多個屬性的,或是有多個同類型的標簽的話,就可以用切片來存儲。 25 } 26 27 type ChildElement struct { //這個結構體是定義子元素的結構的。 28 XMLName xml.Name `xml:"string"` //注意,這里是我們的子元素的一級標簽,因此該標簽的名稱必須填寫正確,不然反序列化的時候就會報錯。 29 StringName string `xml:"age,attr"` //這個是定義,一級標簽的屬性的,我們需要用attr來進行表示,於此同時,我們還需要輸入屬性的關鍵字“age” 30 InnerText string `xml:",innerxml"` //這行就是定義標簽里面的具體內容經的,對了,其中關鍵字“,innerxml”里的逗號不要忘記喲。 31 } 32 33 func main() { 34 GolangXml, err := ioutil.ReadFile(InputFile) //我們把數據一次性讀取到一個切片中。 35 if err != nil { 36 log.Fatal(err) 37 } 38 39 var result RootElement 40 fmt.Printf("序列化之前GolangXml = [%v]\n",result) 41 err = xml.Unmarshal(GolangXml, &result) //然后對數據進行反序列化。 42 if err != nil { 43 log.Fatal(err) 44 } 45 fmt.Printf("序列化之后GolangXml = [%v]\n",result) 46 fmt.Println(result.ResourceString) 47 } 48 49 50 51 #以上代碼執行結果如下: 52 序列化之前GolangXml = [{{ } []}] 53 序列化之后GolangXml = [{{ UserInformation} [{{ string} 18 yinzhengjie} {{ string} 20 尹正傑}]}] 54 [{{ string} 18 yinzhengjie} {{ string} 20 尹正傑}]
2.Xml數據格式的序列化
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 package main 9 10 import ( 11 "encoding/xml" 12 "io/ioutil" 13 "log" 14 "fmt" 15 "strings" 16 "os" 17 ) 18 19 var ( 20 InputFile = "E:\\Code\\Golang\\Golang_Program\\數據格式進階\\yinzhengjie.xml" 21 OutputFile = "E:\\Code\\Golang\\Golang_Program\\數據格式進階\\yinzhengjie.xml.bak" 22 ) 23 24 25 type RootElement struct { //這是定義根元素的,我們需要做的就是講XML數據的結構先定義出來,就是為了方便主函數一會直接通過這個結構去讀取數據。 26 XMLName xml.Name `xml:"UserInformation"` //這個我們指定XML的根元素,我們也可以叫它最外層標簽。 27 ResourceString []ChildElement `xml:"string"` //上面是指定父標簽的話,這個應該就不用多做解釋吧,當然是子標簽了啦。如果你的子表情有多個屬性的,或是有多個同類型的標簽的話,就可以用切片來存儲。 28 } 29 30 type ChildElement struct { //這個結構體是定義子元素的結構的。 31 XMLName xml.Name `xml:"string"` //注意,這里是我們的子元素的一級標簽,因此該標簽的名稱必須填寫正確,不然反序列化的時候就會報錯。 32 StringName string `xml:"age,attr"` //這個是定義,一級標簽的屬性的,我們需要用attr來進行表示,於此同時,我們還需要輸入屬性的關鍵字“age” 33 InnerText string `xml:",innerxml"` //這行就是定義標簽里面的具體內容經的,對了,其中關鍵字“,innerxml”里的逗號不要忘記喲。 34 } 35 36 func main() { 37 GolangXml, err := ioutil.ReadFile(InputFile) //我們把數據一次性讀取到一個切片中。 38 if err != nil { 39 log.Fatal(err) 40 } 41 42 var result RootElement 43 err = xml.Unmarshal(GolangXml, &result) //然后對數據進行反序列化。 44 if err != nil { 45 log.Fatal(err) 46 } 47 for key,value := range result.ResourceString{ 48 if strings.EqualFold(value.StringName,"18") { //只修改屬性值為"18"節點的內部文本innerText 49 result.ResourceString[key].InnerText = "666666666666666" //注意修改的不是value對象,而是直接使用result中的真實對象 50 fmt.Println("內容修改完畢!") 51 } 52 } 53 XmlOutput,err := xml.MarshalIndent(result,"","") //保存修改后的內容 54 if err == nil { 55 HeaderBytes := []byte(xml.Header) //加入XML頭信息 56 XmlData := append(HeaderBytes,XmlOutput...) //拼接XML頭和實際XML內容 57 ioutil.WriteFile(OutputFile,XmlData,os.ModeAppend) //寫入文件 58 fmt.Println(string(XmlData)) 59 fmt.Println("文件寫入成功!") 60 }else { 61 fmt.Println(err) 62 } 63 } 64 65 66 67 #以上代碼執行結果如下: 68 內容修改完畢! 69 <?xml version="1.0" encoding="UTF-8"?> 70 <UserInformation><string age="18">666666666666666</string><string age="20">尹正傑</string></UserInformation> 71 文件寫入成功!
執行以上代碼之后,會生成一個新的文件,即“yinzhengjie.xml.bak”文件。其內容如下:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <UserInformation><string age="18">666666666666666</string><string age="20">尹正傑</string></UserInformation>
更多關於學習XML知識的話,我推薦兩個學習的網站給大家:
a>.http://www.xml.org/
b>.http://www.w3school.com.cn/xml/
二.JSON
JSON(JavaScript Object Notation)是一種輕量級的數據交換格式。人類閱讀和寫作很容易。機器解析和生成很容易。它基於JavaScript編程語言的一個子集 , 標准ECMA-262第3版 - 1999年12月。JSON是完全獨立於語言的文本格式,但是使用C語言家族的程序員熟悉的約定,包括C,C ++,C#,Java,JavaScript,Perl,Python等等。這些屬性使JSON成為理想的數據交換語言。
1.Json數據格式的序列化
在golang語言中,我們用json.Marshal() 進行序列化。json.Marshal() 的函數簽名是 func Marshal(v interface{}) ([]byte, error)。出於安全考慮,在 web 應用中最好使用 json.MarshalforHTML() 函數,其對數據執行HTML轉碼,所以文本可以被安全地嵌在 HTML <script> 標簽中。序列化是在內存中把數據轉換成指定格式(data -> string),反之亦然(string -> data structure)編碼也是一樣的,只是輸出一個數據流(實現了 io.Writer 接口);解碼是從一個數據流(實現了io.Reader)輸出到一個數據結構。
JSON 與 Go 類型對應如下:
a>.bool 對應 JSON 的 booleans;
b>.float64 對應 JSON 的 numbers;
c>.string 對應 JSON 的 strings;
d>.nil 對應 JSON 的 null;
不是所有的數據都可以編碼為 JSON 類型:只有驗證通過的數據結構才能被編碼:
a>.JSON 對象只支持字符串類型的 key;要編碼一個 Go map 類型,map 必須是 map[string]T(T是json 包中支持的任何類型);
b>.Channel,復雜類型和函數類型不能被編碼;
c>.不支持循環數據結構;它將引起序列化進入一個無限循環;
d>.指針可以被編碼,實際上是對指針指向的值進行編碼(或者指針是 nil);
Go 語言的 json 包可以讓你在程序中方便的讀取和寫入 JSON 數據。接下來我們一起看一下golang是如何使用json包的:
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 package main 9 10 import ( 11 "encoding/json" 12 "fmt" 13 "log" 14 "os" 15 ) 16 17 var ( 18 OutputFile = "E:\\Code\\Golang\\Golang_Program\\數據格式進階\\yinzhengjie.json" 19 ) 20 21 type TenScenicSpots struct { //定義10個景區的名稱。 22 FirstScenic string 23 SecondScenic string 24 ThirdScenic string 25 FourthScenic string 26 FifrhScenic string 27 SixthScenic string 28 SeventhScenic string 29 EigthtScenic string 30 NinthScenic string 31 TenthScenic string 32 } 33 34 type TouristInformation struct { //定義游客信息 35 VisitorName string //游客姓名 36 Nationality string //游客國籍 37 City string //想要去的城市 38 ScenicSpot []*TenScenicSpots //想要去看的景區 39 40 } 41 42 func main() { 43 ChaoyangDistrict := &TenScenicSpots{"中華名族園","北京奧林匹克公園","國家體育館","中國科學技術官","奧林匹克公園網球場","蟹島綠色生態農庄","國家游泳中心(水立方)","中國紫檀博物館","北京歡樂谷","元大都城"} 44 DaxingDistrict := &TenScenicSpots{ "北京野生動物園","男孩子麋鹿苑","中華文化園","留民營生態農場","中國印刷博物館","北普陀影視城","大興濱河森林公園","呀路古熱帶植物園","龐各庄萬畝梨園","西黃垈村"} 45 District := TouristInformation{"尹正傑", "中國", "北京", []*TenScenicSpots{ChaoyangDistrict, DaxingDistrict}} 46 GolangJson, err := json.Marshal(District) //這個步驟就是序列化的過程。json.Marshal方法會返回一個字節數組,即GolangJson,與此同時,District已經是JSON格式的啦。 47 if err != nil { 48 log.Fatal("序列化報錯是:%s",err) 49 } 50 fmt.Printf("JSON format: %s", GolangJson) 51 52 file, _ := os.OpenFile(OutputFile, os.O_CREATE|os.O_WRONLY, 0) 53 defer file.Close() 54 Write := json.NewEncoder(file) //創建一個編碼器。 55 err = Write.Encode(District) //由於District已經被json.Marshal方法處理過了,所以我們直接把JSON格式的District傳給Write寫入器,調用該寫入器的Encode方法可以對JSON格式的數據進行編碼。如果順利的話,我們會得到一個nil參數,否則我們會得到編碼的錯誤信息。 56 if err != nil { 57 log.Println("Error in encoding json") 58 } 59 } 60 61 62 63 64 #以上代碼執行結果如下: 65 JSON format: {"VisitorName":"尹正傑","Nationality":"中國","City":"北京","ScenicSpot":[{"FirstScenic":"中華名族園","SecondScenic":"北京奧林匹克公園","ThirdScenic":"國家體育館","FourthScenic":"中國科學技術官","FifrhScenic":"奧林匹克公園網球場","SixthScenic":"蟹島綠色生態農庄","SeventhScenic":"國家游泳中心(水立方)","EigthtScenic":"中國紫檀博物館","NinthScenic":"北京歡樂谷","TenthScenic":"元大都城"},{"FirstScenic":"北京野生動物園","SecondScenic":"男孩子麋鹿苑","ThirdScenic":"中華文化園","FourthScenic":"留民營生態農場","FifrhScenic":"中國印刷博物館","SixthScenic":"北普陀影視城","SeventhScenic":"大興濱河森林公園","EigthtScenic":"呀路古熱帶植物園","NinthScenic":"龐各庄萬畝梨園","TenthScenic":"西黃垈村"}]}
2.Json數據格式的反序列化
如果我們事先知道 JSON 數據,我們可以定義一個適當的結構並對 JSON 數據反序列化。我們上面做序列化的時候創建了一個“yinzhengjie.json”的文件,里面的數據結果我們是心知肚明的,因此,接下來反序列化就是一件很Easy的事情啦。
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 package main 9 10 11 import ( 12 "encoding/json" 13 "fmt" 14 "log" 15 "io/ioutil" 16 ) 17 18 var ( 19 InputFile = "E:\\Code\\Golang\\Golang_Program\\數據格式進階\\yinzhengjie.json" 20 ) 21 22 type TenScenicSpots struct { //定義10個景區的名稱。 23 FirstScenic string 24 SecondScenic string 25 ThirdScenic string 26 FourthScenic string 27 FifrhScenic string 28 SixthScenic string 29 SeventhScenic string 30 EigthtScenic string 31 NinthScenic string 32 TenthScenic string 33 } 34 35 type TouristInformation struct { //定義游客信息 36 VisitorName string //游客姓名 37 Nationality string //游客國籍 38 City string //想要去的城市 39 ScenicSpot []*TenScenicSpots //想要去看的景區 40 41 } 42 43 func main() { 44 var GolangJson TouristInformation //定義反序列化JSON的格式 45 46 file,err := ioutil.ReadFile(InputFile) //得到的文件是一個字節切片喲 47 if err != nil { 48 log.Println(err) 49 } 50 51 err = json.Unmarshal(file,&GolangJson) //將得到的字節進行反序列化 52 if err != nil { 53 log.Fatal("反序列化報錯啦:%s",err) 54 } 55 56 fmt.Printf("JSON format: %v\n", GolangJson) //將反序列化的文件打印出來。 57 fmt.Println(GolangJson.City) 58 fmt.Println(GolangJson.ScenicSpot[1]) 59 } 60 61 62 63 #以上代碼執行結果如下: 64 JSON format: {尹正傑 中國 北京 [0xc042050140 0xc042050280]} 65 北京 66 &{北京野生動物園 男孩子麋鹿苑 中華文化園 留民營生態農場 中國印刷博物館 北普陀影視城 大興濱河森林公園 呀路古熱帶植物園 龐各庄萬畝梨園 西黃垈村}
更多關於學習XML知識的話,我推薦兩個學習的網站給大家:
a>.http://www.json.org/
b>.http://www.w3school.com.cn/json/
三.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 類型。
Golang的解碼和編碼就相對簡單啦,就我個人而言,我對golang專有的gob格式還是那么的情有獨鍾。它的使用方法很簡單,我們可以一起看一下它的解碼和編碼的過程,具體代碼 如下:
1.Gob的編碼(序列化)
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 package main 9 10 import ( 11 "bytes" 12 "encoding/gob" 13 "log" 14 "io/ioutil" 15 ) 16 17 var ( 18 OutputFile = "E:\\Code\\Golang\\Golang_Program\\數據格式進階\\yinzhengjie.gob" 19 ) 20 21 type P struct { 22 X, Y, Z int 23 Name string 24 } 25 26 func main() { 27 var GolngGob bytes.Buffer 28 29 enc := gob.NewEncoder(&GolngGob) //生成一個的編碼器 30 31 yzj := P{100, 200, 300, "Yinzhengjie"} 32 err := enc.Encode(yzj) //編碼結構體和數據 33 if err != nil { 34 log.Fatal("encode error:", err) 35 } 36 ioutil.WriteFile(OutputFile, GolngGob.Bytes(), 0644) //我們把編碼后的數據寫入文件 37 }
執行以上代碼之后會生成一個“yinzhengjie.gob”文件,這個文件的內容是二進制編碼的,因此我們用gbk編碼或是utf-8編碼格式直接去打開的話可能不是很理想。因此我們可以通過gob的反序列化來進行讀取操作,具體代碼請參考以下代碼。
2.Gob的解碼(反序列化)
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 package main 9 10 import ( 11 "encoding/gob" 12 "log" 13 "os" 14 "fmt" 15 ) 16 17 var ( 18 InputFile = "E:\\Code\\Golang\\Golang_Program\\數據格式進階\\yinzhengjie.gob" 19 ) 20 21 type P struct { 22 X, Y, Z int 23 Name string 24 } 25 26 type Q struct { 27 X, Y,Z*int32 28 Name string 29 } 30 31 func main() { 32 file,err := os.Open(InputFile) 33 dec := gob.NewDecoder(file) //生成一個解碼器 34 35 var GolangGob Q 36 fmt.Printf("解碼前GolangGob的內容為[%v]\n",GolangGob) 37 err = dec.Decode(&GolangGob) //開始按照我們定義好的GolangGob結構體開始解碼。 38 if err != nil { 39 log.Fatal("decode error:", err) 40 } 41 fmt.Printf("解碼后GolangGob的內容為[%v]\n",GolangGob) 42 43 fmt.Printf("詳細內容為:【%d,%d,%d,%q】\n", *GolangGob.X, *GolangGob.Y, *GolangGob.Z, GolangGob.Name) //注意傳參的順序喲! 44 } 45 46 47 48 #以上代碼執行結果如下: 49 解碼前GolangGob的內容為[{<nil> <nil> <nil> }] 50 解碼后GolangGob的內容為[{0xc04203c838 0xc04203c83c 0xc04203c840 Yinzhengjie}] 51 詳細內容為:【100,200,300,"Yinzhengjie"】
四.數據傳輸進階知識-golang中的密碼學
通過網絡傳輸的數據必須加密,以防止被 hacker(黑客)讀取或篡改,並且保證發出的數據和收到的數據檢驗和一致。 鑒於 Go 母公司的業務,我們毫不驚訝地看到 Go 的標准庫為該領域提供了超過 30 個包:
1>.hash 包:實現了 adler32 、 crc32 、 crc64 和 fnv 校驗;
2>.crypto 包:實現了其它的 hash 算法,比如 md4 、 md5 、 sha1 等。以及完整地實現了 aes 、blowfish 、 rc4 、 rsa 、 xtea 等加密算法。
想要了解更多關於golang加密方法的可以參考:http://www.cnblogs.com/yinzhengjie/p/7368030.html
