使用Golang的GOB序列化处理包含接口的循环结构

使用Golang的GOB序列化处理包含接口的循环结构 我正在尝试序列化一个对我来说相当复杂的结构。 过去我曾使用过 gob,但仅限于处理相当简单的情况。我已经发现了几个难点,不确定该如何继续。

  1. (非)导出字段
  2. 指针 - nil 和循环引用
  3. 接口和泛型

我的领域相当复杂,所以我创建了一个玩具示例,保留了代码的重要部分。我正在尝试为一个现有库添加序列化功能,虽然我可能可以修改代码,但我想看看我能用现有代码走多远。 我最终想要一个更密集的图,但出于学习目的,我简化了它,使得朋友只能有一个朋友,但最终会是一个 map

package friends

import (
	"bytes"
	"encoding/gob"
	"log"
	"strconv"
)

type Events = []float32

type Friend interface {
	ID() string
	Events() Events
}

type BasicFriend struct {
	point float32
}

func (n BasicFriend) ID() string {
	return strconv.FormatFloat(float64(n.point), 'f', -1, 32)
}

func (n BasicFriend) Events() []float32 {
	return []float32{float32(n.point)}
}

// what I want eventually
type socialNetwork[T Friend] struct {
	user    Friend
	friends map[string]*socialNetwork[T]
}

type lonelyNetwork[T Friend] struct {
	user    Friend
	friends *lonelyNetwork[T]
}

func interfaceEncode(enc *gob.Encoder, p Friend) {
	err := enc.Encode(&p)
	if err != nil {
		log.Fatal("encode:", err)
	}
}

func interfaceDecode(dec *gob.Decoder) Friend {
	var p Friend
	err := dec.Decode(&p)
	if err != nil {
		log.Fatal("decode:", err)
	}
	return p
}

func (p *lonelyNetwork[T]) MarshalBinary() (_ []byte, err error) {
	var buf bytes.Buffer
	enc := gob.NewEncoder(&buf)
	// enc.Encode(p.user)
	interfaceEncode(enc, p.user)
	if p.friends == nil {
		return buf.Bytes(), nil
	}
	isCyclic := p.friends != nil && p.friends.friends == p
	enc.Encode(isCyclic)
	if isCyclic {
		p.friends.friends = nil
		err = enc.Encode(p.friends)
		p.friends.friends = p
	} else {
		err = enc.Encode(p.friends)
	}
	return buf.Bytes(), err
}

func (p *lonelyNetwork[T]) UnmarshalBinary(data []byte) (err error) {
	dec := gob.NewDecoder(bytes.NewReader(data))
	if err = dec.Decode(&p.user); err != nil {
		return
	}
	var isCyclic bool
	if err = dec.Decode(&isCyclic); err != nil {
		return
	}
	// err = dec.Decode(&p.friends)
	interfaceDecode(dec)
	if isCyclic {
		p.friends.friends = p
	}
	return
}

// These are required to encode BasicFriend as it exports no fields
func (d *BasicFriend) GobEncode() ([]byte, error) {
	var buf bytes.Buffer
	encoder := gob.NewEncoder(&buf)
	if err := encoder.Encode(d.point); err != nil {
		return nil, err
	}

	return buf.Bytes(), nil
}

func (d *BasicFriend) GobDecode(b []byte) error {
	buf := bytes.NewBuffer(b)
	decoder := gob.NewDecoder(buf)
	if err := decoder.Decode(&d.point); err != nil {
		return err
	}
	return nil
}

我目前卡在 Friend 接口的编码上。当前形式的代码返回以下错误:

 encode:gob: unaddressable value of type *hnsw.BasicFriend

如果我不传递给 interfaceEncode/Decode 函数,我会得到:

gob: local interface type *hnsw.Friend can only be decoded from remote interface type; received concrete type bool

我不确定它是获取到了 isCyclic 标志还是发生了其他情况。


更多关于使用Golang的GOB序列化处理包含接口的循环结构的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

如果对你有帮助,我这里有一些测试代码。

package friends

import (
	"bytes"
	"encoding/gob"
	"testing"

	"github.com/stretchr/testify/require"
)

func TestBasicFriend(t *testing.T) {
	var disk bytes.Buffer
	enc := gob.NewEncoder(&disk)
	friend := BasicFriend{1.1}
	err := enc.Encode(&friend)
	require.NoError(t, err)
	dec := gob.NewDecoder(&disk)
	var decodedFriend BasicFriend
	err = dec.Decode(&decodedFriend)
	require.NoError(t, err)
	require.Equal(t, friend.point, decodedFriend.point)
}

func TestLonelyNetwork(t *testing.T) {
	gob.Register(BasicFriend{})
	var disk bytes.Buffer
	enc := gob.NewEncoder(&disk)
	friend := BasicFriend{1.1}
	bestFriend := BasicFriend{2.1}
	bestFriendNetwork := lonelyNetwork[BasicFriend]{user: bestFriend}
	network := lonelyNetwork[BasicFriend]{user: friend, friends: &bestFriendNetwork}
	err := enc.Encode(&network)
	require.NoError(t, err)
	dec := gob.NewDecoder(&disk)
	var decodedNetwork lonelyNetwork[BasicFriend]
	err = dec.Decode(&decodedNetwork)
	require.NoError(t, err)
}

更多关于使用Golang的GOB序列化处理包含接口的循环结构的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在处理包含接口的循环结构序列化时,需要特别注意几个关键点。以下是针对你当前问题的解决方案:

package main

import (
    "bytes"
    "encoding/gob"
    "fmt"
    "strconv"
)

// 1. 首先注册接口类型
func init() {
    gob.Register(&BasicFriend{})
}

type Events = []float32

type Friend interface {
    ID() string
    Events() Events
}

type BasicFriend struct {
    point float32
}

func (n *BasicFriend) ID() string {
    return strconv.FormatFloat(float64(n.point), 'f', -1, 32)
}

func (n *BasicFriend) Events() []float32 {
    return []float32{n.point}
}

type lonelyNetwork[T Friend] struct {
    user    Friend
    friends *lonelyNetwork[T]
}

// 2. 修复 MarshalBinary 方法
func (p *lonelyNetwork[T]) MarshalBinary() ([]byte, error) {
    var buf bytes.Buffer
    enc := gob.NewEncoder(&buf)
    
    // 直接编码接口值,不需要传递指针
    if err := enc.Encode(p.user); err != nil {
        return nil, err
    }
    
    // 处理循环引用
    if p.friends == nil {
        enc.Encode(false) // 不是循环引用
        return buf.Bytes(), nil
    }
    
    // 检查是否是循环引用
    isCyclic := p.friends != nil && p.friends.friends == p
    if err := enc.Encode(isCyclic); err != nil {
        return nil, err
    }
    
    if isCyclic {
        // 临时断开循环引用
        temp := p.friends
        p.friends = nil
        err := enc.Encode(temp)
        p.friends = temp
        return buf.Bytes(), err
    }
    
    return buf.Bytes(), enc.Encode(p.friends)
}

// 3. 修复 UnmarshalBinary 方法
func (p *lonelyNetwork[T]) UnmarshalBinary(data []byte) error {
    dec := gob.NewDecoder(bytes.NewReader(data))
    
    // 解码接口值
    var user Friend
    if err := dec.Decode(&user); err != nil {
        return err
    }
    p.user = user
    
    // 解码循环标志
    var isCyclic bool
    if err := dec.Decode(&isCyclic); err != nil {
        return err
    }
    
    if !isCyclic {
        // 解码普通引用
        var friends *lonelyNetwork[T]
        if err := dec.Decode(&friends); err != nil {
            return err
        }
        p.friends = friends
    } else {
        // 解码并重建循环引用
        var temp *lonelyNetwork[T]
        if err := dec.Decode(&temp); err != nil {
            return err
        }
        p.friends = temp
        if temp != nil {
            temp.friends = p
        }
    }
    
    return nil
}

// 4. 简化 BasicFriend 的 GobEncode/GobDecode
func (d *BasicFriend) GobEncode() ([]byte, error) {
    var buf bytes.Buffer
    enc := gob.NewEncoder(&buf)
    if err := enc.Encode(d.point); err != nil {
        return nil, err
    }
    return buf.Bytes(), nil
}

func (d *BasicFriend) GobDecode(b []byte) error {
    dec := gob.NewDecoder(bytes.NewBuffer(b))
    return dec.Decode(&d.point)
}

// 5. 使用示例
func main() {
    // 创建循环结构
    network1 := &lonelyNetwork[Friend]{
        user: &BasicFriend{point: 1.0},
    }
    
    network2 := &lonelyNetwork[Friend]{
        user:    &BasicFriend{point: 2.0},
        friends: network1,
    }
    network1.friends = network2 // 创建循环引用
    
    // 序列化
    data, err := network1.MarshalBinary()
    if err != nil {
        panic(err)
    }
    
    // 反序列化
    var network3 lonelyNetwork[Friend]
    if err := network3.UnmarshalBinary(data); err != nil {
        panic(err)
    }
    
    // 验证
    fmt.Printf("User ID: %s\n", network3.user.ID())
    if network3.friends != nil {
        fmt.Printf("Friend ID: %s\n", network3.friends.user.ID())
        fmt.Printf("Is cyclic: %v\n", network3.friends.friends == &network3)
    }
}

关键修改点:

  1. 接口注册:使用 gob.Register(&BasicFriend{})init() 函数中注册具体类型
  2. 接口编码:直接使用 enc.Encode(p.user) 而不是传递指针到接口
  3. 循环引用处理:在序列化前临时断开循环引用,反序列化后重新建立
  4. 方法接收器:将 BasicFriend 的方法改为指针接收器以保持一致
  5. 错误处理:移除 log.Fatal,改为返回错误

这个解决方案正确处理了接口类型的序列化和循环引用问题。对于更复杂的图结构,可以考虑使用序列化ID来跟踪节点,避免深度递归。

回到顶部