转载原笔迹链接[https://www.imooc.com/article/263623]
文章目录
一、序列化
1.序列化概念
互联通讯的双方需要采用约定的协议,序列化和反序列化属于通讯协议的一部分。通讯协议往往采用分层模型,不同模型每层的功能定义以及颗粒度不同,例如:TCP/IP协议是一个四层协议,而OSI模型却是七层协议模型。在OSI七层协议模型中表示层(Presentation Layer)的主要功能是把应用层的对象转换成一段连续的二进制串,或者反过来,把二进制串转换成应用层的对象–这两个功能就是序列化和反序列化。一般而言,TCP/IP协议的应用层对应与OSI七层协议模型的应用层,表示层和会话层,所以序列化协议属于TCP/IP协议应用层的一部分。本文对序列化协议的讲解主要基于OSI七层协议模型。
序列化: 将数据结构或对象转换成二进制串的过程。
反序列化:将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程。
2.对象转换
这个概念是从Java序列化中抽取而来,不同的计算机语言中,数据结构,对象以及二进制串的表示方式并不相同。
数据结构和对象:对于类似Java这种完全面向对象的语言,工程师所操作的一切都是对象(Object),来自于类的实例化。在Java语言中最接近数据结构的概念,就是POJO(Plain Old Java Object)或者JavaBean--那些只有setter/getter方法的类。而在C二进制串:序列化所生成的二进制串指的是存储在内存中的一块数据。C语言的字符串可以直接被传输层使用,因为其本质上就是以’0’结尾的存储在内存中的二进制串。在Java语言里面,二进制串的概念容易和String混淆。实际上String 是Java的一等公民,是一种特殊对象(Object)。对于跨语言间的通讯,序列化后的数据当然不能是某种语言的特殊数据类型。二进制串在Java里面所指的是byte[],byte是Java的8中原生数据类型之一(Primitive data types)。
在Go中,我们同样是将区块对象转换成字节数组([]byte)进行序列化并进行数据存储。
3.Gob编码工具
Golang包自带的一个数据结构序列化的编码/解码工具。编码使用Encoder,解码使用Decoder。一种典型的应用场景就是RPC(remote procedure calls)。
gob由发送端使用Encoder对数据结构进行编码。在接收端收到消息之后,接收端使用Decoder将序列化的数据变化成本地变量。
gob包是golang提供的“私有”的编解码方式,官方文档中也提及其它的效率会比json,xml等更高。因此在两个Go 服务之间的相互通信建议不要再使用json传递了,完全可以直接使用gob来进行数据传递。
import ( "encoding/gob")
4.序列化实现
4.1 区块定义
//区块的结构体,
type Block struct {
//1. 区块高度,也就是区块的编号,第几个区块(第几个区块)
Height int64
//2.上一个区块的Hash值,父Hash
PrevBlockHash []byte
//3.交易数据(最终都属于transaction事务)
Data []byte //字节数组
//4.时间戳
Timestamp int64
//5.Hash(当前区块的Hash)
Hash []byte
//6.Nonce 随机数,用于验证工作量证明
Nonce int64
}
4.2 序列化区块
//定义Block的方法Serialize(),将区块序列化成字节数组
func (block *Block) Serialize() []byte {
//1.定义result的字节buffer,用于存储序列化后的区块
var result bytes.Buffer //缓冲区
//2.初始化序列化对象encoder
encoder := gob.NewEncoder(&result)
//3.通过Encode()方法对区块进行序列化
err := encoder.Encode(block)
if err != nil {
log.Panic(err)
}
//4.返回result的字节数组
return result.Bytes()
}
4.3 反序列化字节数组
//定义函数DeserializeBlock(),传入参数为字节数组,返回值为Block
func DeserializeBlock(blockBytes []byte) *Block {
//1.定义一个Block指针对象
var block Block
//2.初始化反序列化对象decoder
decoder := gob.NewDecoder(bytes.NewReader(blockBytes))
//3.通过Decode()进行反序列化
err := decoder.Decode(&block)
if err != nil {
log.Panic(err)
}
//4.返回block对象
return &block
}
二.BoltDB数据库使用
1.BoltDB简介
Bolt是一个纯粹Key/Value模型的程序。该项目的目标是为不需要完整数据库服务器(如Postgres或MySQL)的项目提供一个简单,快速,可靠的数据库。
BoltDB只需要将其链接到你的应用程序代码中即可使用BoltDB提供的API来高效的存取数据。而且BoltDB支持完全可序列化的ACID事务,让应用程序可以更简单的处理复杂操作。
其源码地址为:https://github.com/boltdb/bolt
2.BoltDB特性
BoltDB设计源于LMDB,具有以下特点:
- 使用Go语言编写
- 不需要服务器即可运行
- 支持数据结构
- 直接使用API存取数据,没有查询语句;
- 支持完全可序列化的ACID事务,这个特性比LevelDB强;
- 数据保存在内存映射的文件里。没有wal、线程压缩和垃圾回收;
- 通过COW技术,可实现无锁的读写并发,但是无法实现无锁的写写并发,这就注定了读性能超高,但写性能一般,适合与读多写少的场景。
BoltDB是一个Key/Value(键/值)存储,意味着没有像SQLRDBMS(MySQL,PostgreSQL等)中的表,没有行,没有列。相反,数据作为键值对存储(如在Golang Maps中)。键值对存储在Buckets中,它们旨在对相似的对进行分组(这与RDBMS中的表类似)。因此,为了获得Value(值),需要知道该Value所在的桶和钥匙。
3.BoltDB简单使用
//通过go get "github.com/boltdb/bolt" 下载并import
import "github.com/boltdb/bolt"
3.1 打开或创建数据库
//1.数据库的创建打开(打开当前目录中的my.db数据文件,如果不存在,它将被创建。)
func main() {
// 创建或打开数据库
db, err := bolt.Open("my.db", 0600, nil)
if err != nil {
//Fatal等同于Print(),然后调用os.Exit(1)
log.Fatal(err)
}
//defer延迟执行的语句,用来添加函数结束时执行的代码.
defer db.Close()
}
注意:
如果通过go build main.go ; .\main 执行生成的my.db,会保存在当前目录$GOPATH /src/Project/package下
3.2 数据库操作
3.2.1 创建数据库表与数据写入操作
//1. 调用Update方法进行数据的写入
err = db.Update(func(tx *bolt.Tx) error {
//2.通过CreateBucket()方法创建BlockBucket(表),初次使用创建
b, err := tx.CreateBucket([]byte("BlockBucket"))
if err != nil {
return fmt.Errorf("create BlockBucket:%s", err)
}
//3.通过Put()方法往表里面存储一条数据(key,value),注意类型必须为[]byte
if b != nil {
err := b.Put([]byte("l"), []byte("Send 100 BTC To 强哥....."))
if err != nil {
log.Panic("数据存储失败......")
}
}
//返回nil,以便数据库处理响应的操作
return nil
})
//数据Update失败,退出程序
if err != nil {
log.Panic(err)
}
3.2.2 数据读取
//1.通过View方法获取数据
err = db.View(func(tx *bolt.Tx) error {
//2.打开BlockBucket表,获取表对象
b := tx.Bucket([]byte("BlockBucket"))
//3.Get()方法通过key读取value
if b != nil {
data := b.Get([]byte("l"))
fmt.Printf("%s\n", data)
data = b.Get([]byte("ll"))
fmt.Printf("%s\n", data)
}
//返回nil,以便数据库处理响应的操作
return nil
})