如何在golang中将数组编组为二进制并将二进制文件解组为数组?

时间:2017-05-31 16:56:36

标签: go encoder decoder gob

我想将gob用于encodedecode对象,我这样做:

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)
}

但是,这个报告错误如下:

decode:can't scan type: *[]int

1 个答案:

答案 0 :(得分:4)

如果您可以将transProp字段的公开范围更改为公开字段,例如

type transProp struct {
    A []int
    B []float64
}

那么您不需要实现自定义二进制编组器/ unmarshaller。您可以使用默认的gob解码器/编码器。

但是,如果你不能,有很多选择。

  1. 最简单的一个,定义导出相关字段的另一个wrapper struct,换行transProp,然后使用默认的gob编码器/解码器,例如

    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
    }
    
  2. 具有自定义数据布局的自定义marshaller / unmarshaller。有许多实现可能性。几点考虑因素:

    • 字节顺序,小/大端?
    • 数据包/流布局?

    使用流格式的big-endian的示例实现:

    // 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
    

    挑战在于如何表示int,因为它依赖于架构。示例实现将是:

    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
    }