文章目录
序列化与反序列化
为什么要进行序列化
程序中至少存在两种形式的数据:
1 在内存中,数据保存在对象,结构体,列表,数组,哈希表,树等中。 这些数据结构针对CPU的高效访问和操作进行了优化(通常使用指针)。
2 如果要将数据写入文件,或通过网络发送,则必须将其 编码(encode) 为某种自包含的字节序列(例如,JSON文档)。 由于每个进程都有自己独立的地址空间,一个进程中的指针对任何其他进程都没有意义,所以这个字节序列表示会与通常在内存中使用的数据结构完全不同。
所以,需要在两种表示之间进行某种类型的翻译。
1 从内存中表示到字节序列的转换称为 (Encoding) (也称为序列化(serialization) 或编组(marshalling)),反过来称为解码(Decoding)
2(解析(Parsing),反序列化(deserialization),反编组( unmarshalling))
在go语言中有几种方法进行序列化和反序列化操作:
- 自定义协议
- ASN.1
- JSON
- gob
本文主要讲自定义和JSON 进行序列化和反序列化
1 自定义协议
在网络中传输都是字节流,那我们如何将一个字节流按不同的数据类型的字节大小进行封装与解封?
简单的方法,我们可以通过自定义协议,比如一个消息有消息长度、消息id、消息内容,协议规定消息长度为4个字节、消息id为四个字节,不规定数据内容的大小。
其主要通过binary 包进行操作。
import (
"bytes"
"encoding/binary"
"fmt"
"sync"
)
type Message struct {
Msglen uint32
Msgid uint32
Msgdata []byte
}
//封包函数
func Pack(len uint32,id uint32,data []byte)([]byte,error) {
var bufferPool = sync.Pool{
New:func() interface{}{
return new(bytes.Buffer)
},
}
//获取一个存放bytes的缓冲区,存储字节序列
dataBuff := bufferPool.Get().(*bytes.Buffer)
//将数据长度写入字节流
err := binary.Write(dataBuff,binary.LittleEndian,len)
checkerr(err)
//将id写入字节流
err = binary.Write(dataBuff,binary.LittleEndian,id)
checkerr(err)
//将数据内容写入字节流
err = binary.Write(dataBuff,binary.LittleEndian,data)
checkerr(err)
return dataBuff.Bytes(),nil
}
//解包函数
func Unpack(data []byte)(*Message,error){
//这里可以不需要额外创建一个数据缓冲
//创建一个io。Reader
boolBuffer := bytes.NewReader(data)
msg := &Message{}
//读取数据长度和id
err := binary.Read(boolBuffer, binary.LittleEndian, &msg.Msglen)
checkerr(err)
err = binary.Read(boolBuffer, binary.LittleEndian, &msg.Msgid)
checkerr(err)
//数据包限制
//if
//
//}
return msg,nil
}
func checkerr(err error){
if err != nil{
fmt.Println("数据写入与读取失败")
}
}
2JSON 序列化与反序列化
在网络传输时会先将数据(结构体、map等)序列化成json字符串,到接收方得到json字符串时,再反序列化恢复成原来的数据类型(结构体、map等)。
方法一:
序列化(encode):
json.Marshal(data)
反序列化 (decode):
json.Unmarshal([]byte(str), &Msg)
import (
"encoding/json"
"fmt"
"log"
)
func main(){
var a map[string]interface{}
a = make(map[string]interface{})
a["num"] = 3
a["name"] = "tom"
a["age"]=18
// 序列化
marshal, err := json.Marshal(a)
if err!=nil{
log.Fatal(err)
}
fmt.Printf("序列化的json byte数组为%v\n",string(marshal))
var name map[string] interface{}
err = json.Unmarshal(marshal, &name)
if err!=nil{
log.Fatal(err)
}
fmt.Printf("反序列化后的值为%v",name)
}
方法二:
序列化:
json.NewEncoder(io.Writer).Encode()
反序列化:
err = json.NewDecoder(io.Reader).Decode(value)
package main
import (
"encoding/json"
"fmt"
"os"
)
type PersonInfo struct {
Name string
age int32
Sex bool
Hobbies []string
}
func main() {
writeFile()
readFile()
}
func readFile() {
filePtr, err := os.Open("person_info.json")
if err != nil {
fmt.Println("Open file failed [Err:%s]", err.Error())
return
}
defer filePtr.Close()
var person []PersonInfo
// 创建json解码器
decoder := json.NewDecoder(filePtr)
err = decoder.Decode(&person)
if err != nil {
fmt.Println("Decoder failed", err.Error())
} else {
fmt.Println("Decoder success")
fmt.Println(person)
}
}
func writeFile() {
personInfo := []PersonInfo{{"David", 30, true, []string{"跑步", "读书", "看电影"}}, {"Lee", 27, false, []string{"工作", "读书", "看电影"}}}
// 创建文件
filePtr, err := os.Create("person_info.json")
if err != nil {
fmt.Println("Create file failed", err.Error())
return
}
defer filePtr.Close()
// 创建Json编码器
encoder := json.NewEncoder(filePtr)
err = encoder.Encode(personInfo)
if err != nil {
fmt.Println("Encoder failed", err.Error())
} else {
fmt.Println("Encoder success")
}
注意:json 写入文件用从 json.Encoder 文件中读入json流用json.Decoder
文件操作
小技巧
小技巧:
为了方便操作我们可以将file相关结构封装成结构体或全局变量
type DBFile struct {
File *os.File
Offset int64
}
var (
newFile *os.File
err error
)
创建空文件
package main
import (
"log"
"os"
)
var (
newFile *os.File
err error
)
func main() {
newFile, err = os.Create("test.txt")
if err != nil {
log.Fatal(err)
}
log.Println(newFile)
newFile.Close()
}
获取文件信息
主要函数 os.Stat()
package main
import (
"fmt"
"log"
"os"
)
var (
fileInfo os.FileInfo
err error
)
func main() {
// 如果文件不存在,则返回错误
fileInfo, err = os.Stat("test.txt")
if err != nil {
log.Fatal(err)
}
fmt.Println("File name:", fileInfo.Name())
fmt.Println("Size in bytes:", fileInfo.Size())
fmt.Println("Permissions:", fileInfo.Mode())
fmt.Println("Last modified:", fileInfo.ModTime())
fmt.Println("Is Directory: ", fileInfo.IsDir())
fmt.Printf("System interface type: %T\n", fileInfo.Sys())
fmt.Printf("System info: %+v\n\n", fileInfo.Sys())
}
重命名文件
主要函数 os.Rename()
package main
import (
"log"
"os"
)
func main() {
originalPath := "test.txt"
newPath := "test2.txt"
err := os.Rename(originalPath, newPath)
if err != nil {
log.Fatal(err)
}
}
删除文件
主要函数: os.Remove()
package main
import (
"log"
"os"
)
func main() {
err := os.Remove("test.txt")
if err != nil {
log.Fatal(err)
}
}
打开文件关闭文件
只读打开 os.Open()
指定操作打开 os.OpenFile()
关闭文件 file.Close()
package main
import (
"log"
"os"
)
func main() {
// 简单地以只读的方式打开。下面的例子会介绍读写的例子。
file, err := os.Open("test.txt")
if err != nil {
log.Fatal(err)
}
file.Close()
// OpenFile提供更多的选项。
// 最后一个参数是权限模式permission mode
// 第二个是打开时的属性
file, err = os.OpenFile("test.txt", os.O_APPEND, 0666)
if err != nil {
log.Fatal(err)
}
file.Close()
// 下面的属性可以单独使用,也可以组合使用。
// 组合使用时可以使用 OR 操作设置 OpenFile的第二个参数,例如:
// os.O_CREATE|os.O_APPEND
// 或者 os.O_CREATE|os.O_TRUNC|os.O_WRONLY
// os.O_RDONLY // 只读
// os.O_WRONLY // 只写
// os.O_RDWR // 读写
// os.O_APPEND // 往文件中添建(Append)
// os.O_CREATE // 如果文件不存在则先创建
// os.O_TRUNC // 文件打开时裁剪文件
// os.O_EXCL // 和O_CREATE一起使用,文件不能存在
// os.O_SYNC // 以同步I/O的方式打开
}
改变权限、拥有者、时间戳
主要函数:
改变权限: os.Chmod(“test.txt”, 0777)
改变拥有者:err = os.Chown(“test.txt”, os.Getuid(), os.Getgid())
改变时间戳:os.Chtimes(“test.txt”, lastAccessTime, lastModifyTime)
package main
import (
"log"
"os"
"time"
)
func main() {
// 使用Linux风格改变文件权限
err := os.Chmod("test.txt", 0777)
if err != nil {
log.Println(err)
}
// 改变文件所有者
err = os.Chown("test.txt", os.Getuid(), os.Getgid())
if err != nil {
log.Println(err)
}
// 改变时间戳
twoDaysFromNow := time.Now().Add(48 * time.Hour)
lastAccessTime := twoDaysFromNow
lastModifyTime := twoDaysFromNow
err = os.Chtimes("test.txt", lastAccessTime, lastModifyTime)
if err != nil {
log.Println(err)
}
}
检查文件是否存在
主要函数 os.IsNotExist()
package main
import (
"log"
"os"
)
var (
fileInfo *os.FileInfo
err error
)
func main() {
// 文件不存在则返回error
fileInfo, err := os.Stat("test.txt")
if err != nil {
if os.IsNotExist(err) {
log.Fatal("File does not exist.")
}
}
log.Println("File does exist. File information:")
log.Println(fileInfo)
}
复制文件
主要函数
拷贝文件: io.Copy(newFile, originalFile)
将文件内容flush到硬盘中 newFile.Sync()
package main
import (
"os"
"log"
"io"
)
func main() {
// 打开原始文件
originalFile, err := os.Open("test.txt")
if err != nil {
log.Fatal(err)
}
defer originalFile.Close()
// 创建新的文件作为目标文件
newFile, err := os.Create("test_copy.txt")
if err != nil {
log.Fatal(err)
}
defer newFile.Close()
// 从源中复制字节到目标文件
bytesWritten, err := io.Copy(newFile, originalFile)
if err != nil {
log.Fatal(err)
}
log.Printf("Copied %d bytes.", bytesWritten)
// 将文件内容flush到硬盘中
err = newFile.Sync()
if err != nil {
log.Fatal(err)
}
}
跳转文件到指定位置
主要函数 file.Seek(offset, whence)
package main
import (
"os"
"fmt"
"log"
)
func main() {
file, _ := os.Open("test.txt")
defer file.Close()
// 偏离位置,可以是正数也可以是负数
var offset int64 = 5
// 用来计算offset的初始位置
// 0 = 文件开始位置
// 1 = 当前位置
// 2 = 文件结尾处
var whence int = 0
newPosition, err := file.Seek(offset, whence)
if err != nil {
log.Fatal(err)
}
fmt.Println("Just moved to 5:", newPosition)
// 从当前位置回退两个字节
newPosition, err = file.Seek(-2, 1)
if err != nil {
log.Fatal(err)
}
fmt.Println("Just moved back two:", newPosition)
// 使用下面的技巧得到当前的位置
currentPosition, err := file.Seek(0, 1)
fmt.Println("Current position:", currentPosition)
// 转到文件开始处
newPosition, err = file.Seek(0, 0)
if err != nil {
log.Fatal(err)
}
fmt.Println("Position after seeking 0,0:", newPosition)
}
写文件
package main
import (
"os"
"log"
)
func main() {
// 可写方式打开文件
file, err := os.OpenFile(
"test.txt",
os.O_WRONLY|os.O_TRUNC|os.O_CREATE,
0666,
)
if err != nil {
log.Fatal(err)
}
defer file.Close()
// 写字节到文件中
byteSlice := []byte("Bytes!\n")
bytesWritten, err := file.Write(byteSlice)
if err != nil {
log.Fatal(err)
}
log.Printf("Wrote %d bytes.\n", bytesWritten)
}
或者使用函数
fmt.Fprintf(ofile, "%v %v\n", intermediate[i].Key, output)
ofile 输出的文件名,后面为输出到文件的格式
读文件(读取指定字节)
package main
import (
"os"
"log"
"io"
)
func main() {
// Open file for reading
file, err := os.Open("test.txt")
if err != nil {
log.Fatal(err)
}
// file.Read()可以读取一个小文件到大的byte slice中,
// 但是io.ReadFull()在文件的字节数小于byte slice字节数的时候会返回错误
byteSlice := make([]byte, 2)
// 读取的数据会放到byteSlice
numBytesRead, err := io.ReadFull(file, byteSlice)
if err != nil {
log.Fatal(err)
}
log.Printf("Number of bytes read: %d\n", numBytesRead)
log.Printf("Data read: %s\n", byteSlice)
}
package main
import (
"os"
"log"
)
func main() {
// 打开文件,只读
file, err := os.Open("test.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
// 从文件中读取len(b)字节的文件。
// 返回0字节意味着读取到文件尾了
// 读取到文件会返回io.EOF的error
byteSlice := make([]byte, 16)
bytesRead, err := file.Read(byteSlice)
if err != nil {
log.Fatal(err)
}
log.Printf("Number of bytes read: %d\n", bytesRead)
log.Printf("Data read: %s\n", byteSlice)
}
读取文件全部字节
ioutil.ReadAll(file)
os.File.Read(), io.ReadFull() 和
io.ReadAtLeast() 在读取之前都需要一个固定大小的byte slice。
但ioutil.ReadAll()会读取reader(这个例子中是file)的每一个字节,然后把字节slice返回。
package main
import (
"os"
"log"
"fmt"
"io/ioutil"
)
func main() {
file, err := os.Open("test.txt")
if err != nil {
log.Fatal(err)
}
// os.File.Read(), io.ReadFull() 和
// io.ReadAtLeast() 在读取之前都需要一个固定大小的byte slice。
// 但ioutil.ReadAll()会读取reader(这个例子中是file)的每一个字节,然后把字节slice返回。
data, err := ioutil.ReadAll(file)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Data as hex: %x\n", data)
fmt.Printf("Data as string: %s\n", data)
fmt.Println("Number of bytes read:", len(data))
}
通过HTTP下载文件
package main
import (
"os"
"io"
"log"
"net/http"
)
func main() {
newFile, err := os.Create("devdungeon.html")
if err != nil {
log.Fatal(err)
}
defer newFile.Close()
url := "http://www.devdungeon.com/archive"
response, err := http.Get(url)
defer response.Body.Close()
// 将HTTP response Body中的内容写入到文件
// Body满足reader接口,因此我们可以使用ioutil.Copy
numBytesWritten, err := io.Copy(newFile, response.Body)
if err != nil {
log.Fatal(err)
}
log.Printf("Downloaded %d byte file.\n", numBytesWritten)
}
使用json 进行文件的读取
参考博客:
https://www.devdungeon.com/
https://colobu.com/2016/10/12/go-file-operations/#%E4%B8%87%E7%89%A9%E7%9A%86%E6%96%87%E4%BB%B6