英文:
How to marshal array to binary and unmarshal binary to array in golang?
问题
我想使用gob
来对对象进行编码和解码,我是这样做的:
type transProp struct {
a []int
b []float64
}
func (p transProp) MarshalBinary() ([]byte, error) {
// 简单的编码:纯文本。
var b bytes.Buffer
fmt.Fprintln(&b, p.a, p.b)
return b.Bytes(), nil
}
// UnmarshalBinary 修改接收器,因此必须使用指针接收器。
func (p *transProp) UnmarshalBinary(data []byte) error {
// 简单的编码:纯文本。
b := bytes.NewBuffer(data)
_, err := fmt.Fscanln(b, &p.a, &p.b)
return err
}
func TestGobEncode(t *testing.T) {
p := transProp{
a: []int{3, 4, 5},
b: []float64{1.0, 2.0},
}
var network bytes.Buffer
enc := gob.NewEncoder(&network)
err := enc.Encode(p)
if err != nil {
log.Fatal("encode:", err)
}
dec := gob.NewDecoder(&network)
var p1 transProp
err = dec.Decode(&p1)
if err != nil {
log.Fatal("decode:", err)
}
fmt.Println(p1)
}
但是,报告了以下错误:
decode:can't scan type: *[]int
英文:
I want to use gob
to encode
and decode
object, I do it like this:
type transProp struct {
a []int
b []float64
}
func (p transProp) MarshalBinary() ([]byte, error) {
// A simple encoding: plain text.
var b bytes.Buffer
fmt.Fprintln(&b, p.a, p.b)
return b.Bytes(), nil
}
// UnmarshalBinary modifies the receiver so it must take a pointer receiver.
func (p *transProp) UnmarshalBinary(data []byte) error {
// A simple encoding: plain text.
b := bytes.NewBuffer(data)
_, err := fmt.Fscanln(b, &p.a, &p.b)
return err
}
func TestGobEncode(t *testing.T) {
p := transProp{
a: []int{3, 4, 5},
b: []float64{1.0, 2.0},
}
var network bytes.Buffer
enc := gob.NewEncoder(&network)
err := enc.Encode(p)
if err != nil {
log.Fatal("encode:", err)
}
dec := gob.NewDecoder(&network)
var p1 transProp
err = dec.Decode(&p1)
if err != nil {
log.Fatal("decode:", err)
}
fmt.Println(p1)
}
But, this report error like this:
decode:can't scan type: *[]int
答案1
得分: 8
如果您可以将transProp
字段的可见性更改为public,例如:
type transProp struct {
A []int
B []float64
}
那么您就不需要实现自定义的二进制编码/解码器。您可以使用默认的gob
编码器/解码器。
然而,如果您不能更改可见性,有很多选项。
- 最简单的方法是定义另一个导出相关字段的
包装结构体
,将transProp
包装起来,然后使用默认的gob
编码器/解码器,例如:
type wrapTransProp struct {
A []int
B []float64
}
func (p transProp) MarshalBinary() ([]byte, error) {
// 包装结构体
w := wrapTransProp{p.A, p.B}
// 使用默认的gob编码器
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
if err := enc.Encode(w); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func (p *transProp) UnmarshalBinary(data []byte) error {
w := wrapTransProp{}
// 使用默认的gob解码器
reader := bytes.NewReader(data)
dec := gob.NewDecoder(reader)
if err := dec.Decode(&w); err != nil {
return err
}
p.A = w.A
p.B = w.B
return nil
}
- 使用自定义的编码器/解码器和自定义的数据布局。有很多实现可能性。以下是一些考虑因素:
- 字节顺序,小端/大端?
- 数据包/流的布局?
以下是使用大端
字节顺序和流格式的示例实现:
// 大端字节顺序
// 大小:4, 4, 1, n, 4, n
// 类型:uint32, uint32, uint8, []int, uint32, []float64
// 数据:#numbytes, #nInt, #intSize, p.A, #nFloat, p.B
挑战在于如何表示`int`,因为它取决于架构。以下是一个示例实现:
func (p transProp) MarshalBinary() ([]byte, error) {
// 从strconv包获取int的大小(以位为单位)
szInt := strconv.IntSize / 8
nInt := len(p.A)
nFloat := len(p.B)
nStream := 4 + 4 + 1 + nInt*szInt + 4 + nFloat*8
stream := make([]byte, nStream)
pos := 0
// 总字节数
binary.BigEndian.PutUint32(stream, uint32(nStream))
pos += 4
// p.A中的项目数
binary.BigEndian.PutUint32(stream[pos:], uint32(nInt))
pos += 4
// int大小
stream[pos] = uint8(szInt)
pos++
// p.A中的项目
switch szInt {
case 1:
for _, v := range p.A {
stream[pos] = uint8(v)
pos++
}
case 2: // 16位
for _, v := range p.A {
binary.BigEndian.PutUint16(stream[pos:], uint16(v))
pos += 2
}
case 4: // 32位
for _, v := range p.A {
binary.BigEndian.PutUint32(stream[pos:], uint32(v))
pos += 4
}
case 8: // 64位
for _, v := range p.A {
binary.BigEndian.PutUint64(stream[pos:], uint64(v))
pos += 8
}
}
// p.B中的项目数
binary.BigEndian.PutUint32(stream[pos:], uint32(nFloat))
pos += 4
// p.B中的项目
s := stream[pos:pos] // 切片长度为0,容量为nFloat
buf := bytes.NewBuffer(s)
if err := binary.Write(buf, binary.BigEndian, p.B); err != nil {
return nil, err
}
return stream, nil
}
func (p *transProp) UnmarshalBinary(data []byte) error {
buf := bytes.NewBuffer(data)
var intSize uint8
var k, nBytes, nInt, nFloat uint32
// 字节数
if err := binary.Read(buf, binary.BigEndian, &nBytes); err != nil {
return err
}
if len(data) < int(nBytes) {
return errors.New("len(data) < #Bytes")
}
// int项目数
if err := binary.Read(buf, binary.BigEndian, &nInt); err != nil {
return err
}
// int大小
if err := binary.Read(buf, binary.BigEndian, &intSize); err != nil {
return err
}
// 读取int到p.A
pos := 0
stream := buf.Bytes()
p.A = make([]int, nInt)
switch intSize {
case 1:
for pos = 0; pos < int(nInt); pos++ {
p.A[pos] = int(stream[pos])
}
case 2:
for k = 0; k < nInt; k++ {
p.A[k] = int(binary.BigEndian.Uint16(stream[pos:]))
pos += 2
}
case 4:
for k = 0; k < nInt; k++ {
p.A[k] = int(binary.BigEndian.Uint32(stream[pos:]))
pos += 4
}
case 8:
for k = 0; k < nInt; k++ {
p.A[k] = int(binary.BigEndian.Uint64(stream[pos:]))
pos += 8
}
}
// 推进缓冲区
buf.Next(pos)
// float64项目数
if err := binary.Read(buf, binary.BigEndian, &nFloat); err != nil {
return err
}
// p.B中的项目
p.B = make([]float64, nFloat)
if err := binary.Read(buf, binary.BigEndian, p.B); err != nil {
return err
}
return nil
}
希望这可以帮助到您!
英文:
If you can change the visibility of transProp
fields to public, e.g.
type transProp struct {
A []int
B []float64
}
then you don't need to implement custom binary marshaller/unmarshaller. You can use default gob
decoder/encoder.
However, if you can't, there are many options.
-
The simplest one, define another
wrapper struct
that export related fields, wraptransProp
, then use defaultgob
encoder/decoder, e.g.type wrapTransProp struct { A []int B []float64 } func (p transProp) MarshalBinary() ([]byte, error) { //Wrap struct w := wrapTransProp{p.a, p.b} //use default gob encoder var buf bytes.Buffer enc := gob.NewEncoder(&buf) if err := enc.Encode(w); err != nil { return nil, err } return buf.Bytes(), nil } func (p *transProp) UnmarshalBinary(data []byte) error { w := wrapTransProp{} //Use default gob decoder reader := bytes.NewReader(data) dec := gob.NewDecoder(reader) if err := dec.Decode(&w); err != nil { return err } p.a = w.A p.b = w.B return nil }
-
Custom marshaller/unmarshaller with custom data layout. There are many implementation possibilities. Several considerations:
- Byte order, little/big endian?
- Packet/stream layout?
An example implementation of big-endian
with stream format:
// Big-Endian
// Size : 4, 4, 1, n, 4, n
// Types : uint32, uint32, uint8, []int, uint32, []float64
// Data : #numbytes, #nInt, #intSize, p.a, #nFloat, p.b
The challenge is in how to represent int
since it's architecture dependent. Sample implementation will be:
func (p transProp) MarshalBinary() ([]byte, error) {
//Get sizeof int (in bits) from strconv package
szInt := strconv.IntSize / 8
nInt := len(p.a)
nFloat := len(p.b)
nStream := 4 + 4 + 1 + nInt*szInt + 4 + nFloat*8
stream := make([]byte, nStream)
pos := 0
//total number of bytes
binary.BigEndian.PutUint32(stream, uint32(nStream))
pos += 4
//num of items in p.a
binary.BigEndian.PutUint32(stream[pos:], uint32(nInt))
pos += 4
//int size
stream[pos] = uint8(szInt)
pos++
//items in a
switch szInt {
case 1:
for _, v := range p.a {
stream[pos] = uint8(v)
pos++
}
case 2: //16-bit
for _, v := range p.a {
binary.BigEndian.PutUint16(stream[pos:], uint16(v))
pos += 2
}
case 4: //32-bit
for _, v := range p.a {
binary.BigEndian.PutUint32(stream[pos:], uint32(v))
pos += 4
}
case 8: //64-bit
for _, v := range p.a {
binary.BigEndian.PutUint64(stream[pos:], uint64(v))
pos += 8
}
}
//number of items in p.b
binary.BigEndian.PutUint32(stream[pos:], uint32(nFloat))
pos += 4
//items in b
s := stream[pos:pos] //slice len=0, capacity=nFloat
buf := bytes.NewBuffer(s)
if err := binary.Write(buf, binary.BigEndian, p.b); err != nil {
return nil, err
}
return stream, nil
}
func (p *transProp) UnmarshalBinary(data []byte) error {
buf := bytes.NewBuffer(data)
var intSize uint8
var k, nBytes, nInt, nFloat uint32
//num bytes
if err := binary.Read(buf, binary.BigEndian, &nBytes); err != nil {
return err
}
if len(data) < int(nBytes) {
return errors.New("len(data) < #Bytes")
}
//num of int items
if err := binary.Read(buf, binary.BigEndian, &nInt); err != nil {
return err
}
//int size
if err := binary.Read(buf, binary.BigEndian, &intSize); err != nil {
return err
}
//read int into p.a
pos := 0
stream := buf.Bytes()
p.a = make([]int, nInt)
switch intSize {
case 1:
for pos = 0; pos < int(nInt); pos++ {
p.a[pos] = int(stream[pos])
}
case 2:
for k = 0; k < nInt; k++ {
p.a[k] = int(binary.BigEndian.Uint16(stream[pos:]))
pos += 2
}
case 4:
for k = 0; k < nInt; k++ {
p.a[k] = int(binary.BigEndian.Uint32(stream[pos:]))
pos += 4
}
case 8:
for k = 0; k < nInt; k++ {
p.a[k] = int(binary.BigEndian.Uint64(stream[pos:]))
pos += 8
}
}
//advance buffer
buf.Next(pos)
//num of float64 items
if err := binary.Read(buf, binary.BigEndian, &nFloat); err != nil {
return err
}
//items in b
p.b = make([]float64, nFloat)
if err := binary.Read(buf, binary.BigEndian, p.b); err != nil {
return err
}
return nil
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论