使用Golang的GOB序列化处理包含接口的循环结构
使用Golang的GOB序列化处理包含接口的循环结构
我正在尝试序列化一个对我来说相当复杂的结构。
过去我曾使用过 gob,但仅限于处理相当简单的情况。我已经发现了几个难点,不确定该如何继续。
- (非)导出字段
- 指针 - nil 和循环引用
- 接口和泛型
我的领域相当复杂,所以我创建了一个玩具示例,保留了代码的重要部分。我正在尝试为一个现有库添加序列化功能,虽然我可能可以修改代码,但我想看看我能用现有代码走多远。
我最终想要一个更密集的图,但出于学习目的,我简化了它,使得朋友只能有一个朋友,但最终会是一个 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)
}
}
关键修改点:
- 接口注册:使用
gob.Register(&BasicFriend{})在init()函数中注册具体类型 - 接口编码:直接使用
enc.Encode(p.user)而不是传递指针到接口 - 循环引用处理:在序列化前临时断开循环引用,反序列化后重新建立
- 方法接收器:将
BasicFriend的方法改为指针接收器以保持一致 - 错误处理:移除
log.Fatal,改为返回错误
这个解决方案正确处理了接口类型的序列化和循环引用问题。对于更复杂的图结构,可以考虑使用序列化ID来跟踪节点,避免深度递归。

