-
Notifications
You must be signed in to change notification settings - Fork 205
Hprose 序列化
Hprose 提供了一套自己的序列化格式用来实现高效的跨语言跨平台的数据存储和交换。
这些操作定义在 github.com/hprose/hprose-golang/io 包中。其中包括了一些常量,结构体和函数。
其中 io 包中的常量定义了 hprose 序列化和 hprose RPC 通讯协议的标记。这里就不做详细介绍了。
下面我们先从结构体讲起。
io 包中定义了下面几个结构体:
- ByteReader
- ByteWriter
- RawReader
- Reader
- Writer
它们之间的关系是:
+---------------------------------------------------------+
| _ _ _ _ |
| | | | | (_) | |
| | | | |_ __ _| |_ ___ _ __ |
| | |/\| | '__| | __/ _ \ '__| |
| \ /\ / | | | || __/ | |
| \/ \/|_| |_|\__\___|_| |
| |
| +-----------------------------------------------------+ |
| | ______ _ _ _ _ _ | |
| | | ___ \ | | | | | | (_) | | |
| | | |_/ /_ _| |_ ___ | | | |_ __ _| |_ ___ _ __ | |
| | | ___ \ | | | __/ _ \| |/\| | '__| | __/ _ \ '__| | |
| | | |_/ / |_| | || __/\ /\ / | | | || __/ | | |
| | \____/ \__, |\__\___| \/ \/|_| |_|\__\___|_| | |
| | __/ | | |
| | |___/ | |
| +-----------------------------------------------------+ |
+---------------------------------------------------------+
+-------------------------------------------------------------+
| ______ _ |
| | ___ \ | | |
| | |_/ /___ __ _ __| | ___ _ __ |
| | // _ \/ _` |/ _` |/ _ \ '__| |
| | |\ \ __/ (_| | (_| | __/ | |
| \_| \_\___|\__,_|\__,_|\___|_| |
| |
| +---------------------------------------------------------+ |
| | ______ ______ _ | |
| | | ___ \ | ___ \ | | | |
| | | |_/ /__ ___ _| |_/ /___ __ _ __| | ___ _ __ | |
| | | // _` \ \ /\ / / // _ \/ _` |/ _` |/ _ \ '__| | |
| | | |\ \ (_| |\ V V /| |\ \ __/ (_| | (_| | __/ | | |
| | \_| \_\__,_| \_/\_/ \_| \_\___|\__,_|\__,_|\___|_| | |
| | +-----------------------------------------------------+ | |
| | |______ _ ______ _ | | |
| | || ___ \ | | | ___ \ | | | | |
| | || |_/ /_ _| |_ ___| |_/ /___ __ _ __| | ___ _ __ | | |
| | || ___ \ | | | __/ _ \ // _ \/ _` |/ _` |/ _ \ '__|| | |
| | || |_/ / |_| | || __/ |\ \ __/ (_| | (_| | __/ | | | |
| | |\____/ \__, |\__\___\_| \_\___|\__,_|\__,_|\___|_| | | |
| | | __/ | | | |
| | | |___/ | | |
| | +-----------------------------------------------------+ | |
| +---------------------------------------------------------+ |
+-------------------------------------------------------------+
其中 ByteReader
和 ByteWriter
是用于对字节序列的读写,它本身跟 hprose 序列化无关,可以单独作为字节序列读写的工具来用,因为是读写分离的,所以性能上要比 bytes
包下面的那几个用于对字节序列化读写的结构体高效一点点,但是功能也少一些。
RawReader
用于读取 hprose 的原始数据,用户通常不需要使用该结构体。
Writer
和 Reader
用于对数据进行 hprose 的细粒度序列化和反序列化操作。
func NewByteWriter(buf []byte) (w *ByteWriter)
参数是用于写入的字节切片,可以为 nil
。
func (w *ByteWriter) Bytes() []byte
返回 ByteWriter
已写入的字节切片,该返回值与 ByteWriter
对象的数据是共享的,对返回值的修改会影响 ByteWriter
本身的值。
func (w *ByteWriter) Clear()
清空已写入的数据。
func (w *ByteWriter) Grow(n int)
当你已知需要写入的数据量很大,但是在后面的写入操作中却需要多次执行小块或单个字节的写入时,主动调用 Grow
方法来扩展 BYteWriter
的容量会让后面的写入速度加快。
func (w *ByteWriter) Len() int
返回已写入的数据长度。
func (w *ByteWriter) String() string
该方法以字符串的形式返回 w
中的内容,如果 w
本身是 nil
,则返回 "<nil>"
。
func (w *ByteWriter) Write(b []byte) (int, error)
该方法是 golang 标准库中 io.Writer
接口的实现。
func (w *ByteWriter) WriteByte(c byte) error
该方法是 golang 标准库中 io.ByteWriter
接口的实现。
func NewByteReader(buf []byte) (reader *ByteReader)
参数是用于读取的字节切片。
func (r *ByteReader) Init(buf []byte)
在使用 ByteReader
对象时,我们可以通过 NewByteReader(nil)
来创建一个空的 ByteReader
对象,后面再通过 Init
方法来初始化这个对象。也可以在通过 Init
方法来重新初始化读取的数据,以便复用 ByteReader
对象。
func (r *ByteReader) Next(n int) (data []byte)
返回接下来的 n
个字节的字节切片,该切片跟 ByteReader
包含的字节切片所包含的数据是共享的,所以,如果当你读取的数据用于只读目的时,使用该方法会非常高效。
func (r *ByteReader) Read(b []byte) (n int, err error)
该方法是 golang 标准库中 io.Reader
接口的实现。
func (r *ByteReader) ReadByte() (byte, error)
该方法是 golang 标准库中 io.ByteReader
接口的实现。
func (r *ByteReader) Unread(n int)
将读取指针向前移动 n
个字节,进行该操作之后,你可以重新读取之前读过的数据。该方法可以被安全的连续或非连续的多次调用。如果移动的字节数多于已经读取的字节数,则读取指针将移动到开始位置,而不会出错。
func (r *ByteReader) UnreadByte() error
该方法将读取指针向前移动 1 个字节。该方法签名中包含 error
返回值,仅仅是为了兼容 bytes.Buffer
, bytes.Reader
, strings.Reader
,bufio.Reader
这几个结构体上的 UnreadByte
方法的签名,但是该方法的返回值永远为 nil
。所以该方法可以被安全的连续或非连续的多次调用。如果读取指针已经移动到了开始位置,调用该方法将什么都不做,而不会出错。
func NewWriter(simple bool, buf ...byte) (w *Writer)
simple
如果为 true
,则不使用引用方式序列化,通常在序列化的数据中不包含引用类型数据时,设置为 true
可以加快速度。当包含引用类型数据时,需要设置为 false
(即默认值),尤其是当引用数据中包括递归数据时,如果不使用引用方式,会陷入死循环导致堆栈溢出的错误。
buf
为初始写入的数据。
同上面的 simple
参数。
func (w *Writer) Reset()
将序列化的引用计数器重置。
func (w *Writer) Serialize(v interface{}) *Writer
序列化数据 v
。其中 v
为任意 hprose 支持的类型。
func (w *Writer) WriteBigFloat(bf *big.Float)
序列化一个 *big.Float
数据,以浮点数方式序列化。
func (w *Writer) WriteBigInt(bi *big.Int)
序列化一个 *big.Int
数据,以长整数类型序列化。
func (w *Writer) WriteBigRat(br *big.Rat)
序列化一个 *big.Rat
数据,如果该数字为整数,则以长整数类型序列化,否则以字符串类型序列化。
func (w *Writer) WriteBool(b bool)
序列化一个 bool
类型数据。
func (w *Writer) WriteBytes(bytes []byte)
序列化一个 []byte
类型数据,以二进制串类型数据序列化。
func (w *Writer) WriteComplex128(c complex128)
序列化一个 complex128
类型数据。如果虚部为 0
,则按照浮点数类型序列化,否则按照包含两个浮点数的元素的数组序列化。
func (w *Writer) WriteComplex64(c complex64)
序列化一个 complex64
类型数据。如果虚部为 0
,则按照浮点数类型序列化,否则按照包含两个浮点数的元素的数组序列化。
func (w *Writer) WriteFloat(f float64, bitSize int)
序列化一个浮点数。如果 f
原本是 float32
,bitSize
的值为 32
,否则 bitSize
的值为 64
。
func (w *Writer) WriteInt(i int64)
序列化一个整数 i
。如果 i
在 int32
的范围内,则按照 hprose 的整数类型序列化,否则按照 hprose 的长整数类型序列化。
func (w *Writer) WriteList(lst *list.List)
序列化一个 *list.List
类型的数据。按照 hprose 的数组列表类型序列化。
func (w *Writer) WriteNil()
序列化一个 nil
值。按照 hprose 的 Null 类型序列化。
func (w *Writer) WriteSlice(slice []reflect.Value)
序列化一个 []reflect.Value
类型的数据,按照 hprose 的数组列表类型序列化。
func (w *Writer) WriteString(str string)
序列化一个 string
类型的数据。如果 str 是 UTF8 编码,则按照字符串序列化,否则按照二进制串序列化。
func (w *Writer) WriteStringSlice(slice []string)
序列化一个 []string
类型的数据。序列化为 hprose 的数组列表类型。
func (w *Writer) WriteTime(t *time.Time)
序列化一个 *time.Time
类型的数据。按照 hprose 的日期,时间或者日期时间类型序列化。
func (w *Writer) WriteTuple(tuple ...interface{})
将多个数据按照 hprose 的数组列表类型序列化。
func (w *Writer) WriteUint(i uint64)
序列化一个无符号整数 i
。如果 i
范围在 int32
范围内,则按照整数类型序列化,否则按照长整数类型序列化。
func (w *Writer) WriteValue(v reflect.Value)
序列化一个 reflect.Value
类型的数据。
func NewReader(buf []byte, simple bool) (reader *Reader)
buf
为反序列化数据的数据。
simple
如果为 true
,则不使用引用方式反序列化,通常在反序列化的数据中不包含引用类型数据时,设置为 true
可以加快速度。当包含引用类型数据时,需要设置为 false
(即默认值),否则会抛出异常。
同上面的 simple
参数。
反序列化数据时,如果反序列化的数据为 interface{}
的指针,而不是某个具体类型数据的指针,那么将会按照默认类型序列化。下面是 hprose 类型与反序列化默认类型的对应关系表:
hprose 类型 | go 类型 |
---|---|
Integer | int |
Long | *big.Int
Double | float64 Boolean | bool UTF8 char | string Null | nil Empty | "" DateTime | time.Time Bytes | []byte String | string GUID | string List | []interface{} Map | map[interface{}]interface{} Object | 结构体对象指针
我们会发现 Map 类型的默认映射类型是:map[interface{}]interface{}
,如果将 JSONCompatible
字段设置为 true
,则 Map 类型的默认映射类型是:map[string]interface{}
,当你确认你的 Map 数据是 JSON 兼容的,即 map 的 key 一定是 string
类型或者可以转换为 string
类型的数据时,可以将该字段设置为 true
。
func (r *Reader) CheckTag(expectTag byte) (tag byte)
检查当前位置的字节是否为 expectTag
,如果是返回该 tag
字节,否则 panic。
func (r *Reader) CheckTags(expectTags []byte) (tag byte)
检查当前位置的字节是否为 expectTags
之一,如果是返回该 tag
字节,否则 panic。
func (r *Reader) ReadBigIntWithoutTag() *big.Int
从 r
的当前位置读取数据并反序列化为一个 *big.Int
类型的数据。该方法假设序列化标记已被读取,并且其值为 TagLong
。
如果反序列化过程中发生错误,则 panic。
func (r *Reader) ReadBool() bool
从 r
的当前位置读取数据并反序列化为一个 bool
值。
如果反序列化过程中发生错误,则 panic。
func (r *Reader) ReadBytesWithoutTag() (b []byte)
从 r
的当前位置读取数据并反序列化为 []byte
结果并返回。该方法假设序列化标记已被读取,并且其值为 TagBytes。
如果反序列化过程中发生错误,则 panic。
func (r *Reader) ReadComplex128() complex128
从 r
的当前位置读取数据并反序列化为一个 complex128
值。
如果反序列化过程中发生错误,则 panic。
func (r *Reader) ReadComplex64() complex64
从 r
的当前位置读取数据并反序列化为一个 complex64
值。
如果反序列化过程中发生错误,则 panic。
func (r *Reader) ReadCount() int
从 r
的当前位置读取数组,slice,map 或者结构体字段的数量。该方法假设序列化标记已被读取。该方法主要用于 RPC
实现,用户通常不需要直接使用该方法。
如果反序列化过程中发生错误,则 panic。
func (r *Reader) ReadDateTimeWithoutTag() (dt time.Time)
从 r
的当前位置读取数据并反序列化为一个 time.Time
值。该方法假设序列化标记已被读取,并且其值为 TagDate
。
如果反序列化过程中发生错误,则 panic。
func (r *Reader) ReadFloat32() float32
从 r
的当前位置读取数据并反序列化为一个 float32
值。
如果反序列化过程中发生错误,则 panic。
func (r *Reader) ReadFloat64() float64
从 r
的当前位置读取数据并反序列化为一个 float64
值。
如果反序列化过程中发生错误,则 panic。
func (r *Reader) ReadInt() int64
从 r
的当前位置读取数据并反序列化为一个 int64
值。
如果反序列化过程中发生错误,则 panic。
func (r *Reader) ReadIntWithoutTag() int
从 r
的当前位置读取数据并反序列化为一个 int
值。该方法假设序列化标记已被读取,并且其值为 TagInteger
。
如果反序列化过程中发生错误,则 panic。
func (r *Reader) ReadSlice(v []reflect.Value)
从 r
的当前位置读取数据并反序列化到 v
中。该方法假设序列化标记已被读取,并且其值为 TagList
,且元素个数也已被读取,并且等于 v
的长度,反序列化时,每个元素按照 v
中每个元素的类型进行反序列化。该方法用于 RPC
实现,用户通常不需要使用该方法。
如果反序列化过程中发生错误,则 panic。
func (r *Reader) ReadSliceWithoutTag() []reflect.Value
从 r
的当前位置读取数据并反序列化为一个 []reflect.Value
类型的数据。该方法假设序列化标记已被读取,并且其值为 TagList
。
如果反序列化过程中发生错误,则 panic。
func (r *Reader) ReadString() (str string)
从 r
的当前位置读取数据并反序列化为一个 string
类型的数据。
如果反序列化过程中发生错误,则 panic。
func (r *Reader) ReadStringWithoutTag() (str string)
从 r
的当前位置读取数据并反序列化为一个 string
类型的数据。该方法假设序列化标记已被读取,并且其值为 TagString
。
如果反序列化过程中发生错误,则 panic。
func (r *Reader) ReadTime() (dt time.Time)
从 r
的当前位置读取数据并反序列化为一个 time.Time
类型的数据。
如果反序列化过程中发生错误,则 panic。
func (r *Reader) ReadTimeWithoutTag() (t time.Time)
从 r
的当前位置读取数据并反序列化为一个 time.Time
类型的数据。该方法假设序列化标记已被读取,并且其值为 TagTime
。
如果反序列化过程中发生错误,则 panic。
func (r *Reader) ReadUint() uint64
从 r
的当前位置读取数据并反序列化为一个 uint64
类型的数据。
如果反序列化过程中发生错误,则 panic。
func (r *Reader) ReadValue(v reflect.Value)
从 r
的当前位置读取数据并反序列化到参数 v
中。反序列化的数据类型跟 v
中所包含的类型一致。
如果反序列化过程中发生错误,则 panic。
func (r *Reader) Reset()
将反序列化的引用计数器重置。
func (r *Reader) Unserialize(p interface{})
从 r
的当前位置读取数据并反序列化结果到 p
中,p
是一个指向结果的指针。
如果反序列化过程中发生错误,则 panic。
func Serialize(v interface{}, simple bool) []byte
将 v
序列化为字节切片。simple
参数的含义与 Writer
结构体的序列化的 simple
参数相同。
func Unserialize(b []byte, p interface{}, simple bool)
将字节切片数据 b
反序列化到 p
所指向的变量中,p
是一个指向反序列化变量的指针。simple
参数的含义与 Reader
结构体的序列化的 simple
参数相同。
func Marshal(v interface{}) []byte
相当于 Serialize(v, true)
。
func Unmarshal(b []byte, p interface{})
相当于 Unmarshal(b, p, true)
。
func RegisterSliceEncoder(s interface{}, encoder func(*Writer, interface{}))
该函数用于注册一个 slice 编码器实现对 slice 的快速的 hprose 序列化。hprose 对如下 slice 类型已经提供了内置的快速编码器:
- []bool
- []int
- []int8
- []int16
- []int32
- []int64
- []uint
- []uint8
- []uint16
- []uint32
- []uint64
- []uintptr
- []float32
- []float64
- []complex64
- []complex128
- []string
- [][]byte
- []interface{}
用户不需要为上述 slice 类型注册自定义编码器。
对于没有提供快速编码器的类型,hprose 采用反射的方式进行序列化。如果用户想要加快某个复杂的 slice 类型的序列化,可以使用该函数来注册自定义编码器,下面来看一个例子:
package main
import (
"fmt"
"time"
"github.com/hprose/hprose-golang/io"
"github.com/hprose/hprose-golang/util"
)
func mySliceEncoder(w *io.Writer, v interface{}) {
slice := v.([]map[string]interface{})
var buf [20]byte
for i := range slice {
w.WriteByte(io.TagMap)
w.Write(util.GetIntBytes(buf[:], int64(len(slice[i]))))
w.WriteByte(io.TagOpenbrace)
for key, val := range slice[i] {
w.WriteString(key)
w.Serialize(val)
}
w.WriteByte(io.TagClosebrace)
}
}
func test(slice []map[string]interface{}) {
start := time.Now()
for i := 0; i < 500000; i++ {
io.Marshal(slice)
}
stop := time.Now()
fmt.Println((stop.UnixNano() - start.UnixNano()) / 1000000)
}
func main() {
slice := make([]map[string]interface{}, 10)
for i := 0; i < 10; i++ {
slice[i] = make(map[string]interface{})
slice[i]["id"] = i
}
test(slice)
io.RegisterSliceEncoder(([]map[string]interface{})(nil), mySliceEncoder)
test(slice)
}
该程序运行结果为:
844
664
我们看到,实现快速编码器之后,效率提高了大约 20% 左右。当然实现快速编码器需要了解 hprose 的编码规范。如果上面的编码器只是实现为:
func mySliceEncoder(w *io.Writer, v interface{}) {
slice := v.([]map[string]interface{})
for i := range slice {
w.Serialize(slice[i])
}
}
并不会加快序列化速度,反而会比默认方式更慢。所以,在自定义编码器时,最好测试一下注册前后的性能对比。
RegisterSliceEncoder
函数的第一个参数是编码器所要序列化的数据类型的一个实例。它必须是一个 slice 类型的实例,并且跟后面注册的编码器中所序列化的类型相一致。
func RegisterMapEncoder(m interface{}, encoder func(*Writer, interface{}))
该函数用于注册一个 map 编码器实现对 map 的快速的 hprose 序列化。hprose 对如下 map 类型已经提供了内置的快速编码器:
- map[string]string
- map[string]interface{}
- map[string]int
- map[int]int
- map[int]string
- map[int]interface{}
- map[interface{}]interface{}
- map[interface{}]int
- map[interface{}]string
用户不需要为上述 map 类型注册自定义编码器。
对于没有提供快速编码器的类型,hprose 采用反射的方式进行序列化。如果用户想要加快某个其它 map 类型的序列化,可以使用该函数来注册自定义编码器,下面来看一个例子:
package main
import (
"fmt"
"math"
"time"
"github.com/hprose/hprose-golang/io"
)
func stringFloat64MapEncoder(w *io.Writer, v interface{}) {
m := v.(map[string]float64)
for key, val := range m {
w.WriteString(key)
w.WriteFloat(val, 64)
}
}
func test(m map[string]float64) {
start := time.Now()
for i := 0; i < 500000; i++ {
io.Marshal(m)
}
stop := time.Now()
fmt.Println((stop.UnixNano() - start.UnixNano()) / 1000000)
}
func main() {
m := make(map[string]float64)
m["e"] = math.E
m["pi"] = math.Pi
m["ln2"] = math.Ln2
test(m)
io.RegisterMapEncoder((map[string]float64)(nil), stringFloat64MapEncoder)
test(m)
}
该程序运行结果为:
680
429
我们看到,实现快速编码器之后,效率提高了大约 35% 左右。
RegisterMapEncoder
函数的第一个参数是编码器所要序列化的数据类型的一个实例。它必须是一个 map 类型的实例,并且跟后面注册的编码器中所序列化的类型相一致。
func Register(proto interface{}, alias string, tag ...string)
该函数用于注册一个可序列化和反序列化的自定义结构体。
第一个参数 proto
为注册的结构体对象,它也可以是结构体指针。
第二个参数 alias
为注册的结构体的别名,该名称可以跟结构体类型名称相同,也可以不同。它用于在不同语言的不同定义之间进行类型映射。
第三个参数 tag
是可选的(可以是 0 个或 1 个,但不支持多个),它用于指定序列化字段别名的 tag
标签名。
假设有如下结构体定义:
type User struct {
Name string `json:"name"`
Age int `json:"age"`
Password string `json:"-"`
}
那么可以这样注册:
func init() {
io.Register((*User)(nil), "User", "json")
}
注册之后的 User
类型的对象,在序列化时,字段名按照 json
标签对应的名称进行序列化,如果 json
标签对应的名称为 "-"
,那么该字段将不会被序列化。另外,私有字段也不会被序列化。当没有指定标签时,字段名会自动按照小写首字母的名称进行序列化。
func GetStructType(alias string) (structType reflect.Type)
获取用 Register
函数注册的类型。
func GetAlias(structType reflect.Type) string
获取用 Register
函数注册的类型对应的别名。
func GetTag(structType reflect.Type) string
获取用 Register
函数注册的类型对应的标记。