Golang中为避免网络传输拷贝应使用父级引用还是内部切片

Golang中为避免网络传输拷贝应使用父级引用还是内部切片 你好,

我正在尝试理解,当使用包含其他类型(如 Comment 或其他类型)切片的 User 类型时,最佳的处理方式是什么。以下每个示例都附有相应的问题。

谢谢

type User struct {
	Name     string
	Age      int
	Salary   float64
	Username string
	Password string
	Comments []Comment // 或者 *Comment
	// SomeOtherSlices []Other
}

type Comment struct {
	ID        string
	Message   string
	CreatedAt time.Time
	DeletedAt *time.Time
}

// 当我传递返回值时,内部的 `Comments` 每次都会被复制吗?
func storageV1() *User {
	return &User{
		// ...
		Comments: []Comment{{
			// ..
		}},
	}
}

// 假设返回值是一个副本,而内部的 `Comments` 是一个指针,那么当我传递返回值时,内部的 `Comments` 每次都会被复制吗?
func storageV2() User {
	return User{
		// ...
		Comments: []*Comment{{ // 如果字段是指针
			// ..
		}},
	}
}

// 我不确定是否两者都必须是指针?
func storageV3() *User {
	return &User{
		// ...
		Comments: []*Comment{{ // 如果字段是指针
			// ..
		}},
	}
}

更多关于Golang中为避免网络传输拷贝应使用父级引用还是内部切片的实战教程也可以访问 https://www.itying.com/category-94-b0.html

4 回复

正确

更多关于Golang中为避免网络传输拷贝应使用父级引用还是内部切片的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


切片的内容不会被复制,除非你通过调用 copy() 函数来手动实现。切片的值会被复制,但这基本上只是一个指针。通过传递指向切片的指针,在防止复制方面你并没有获得任何好处。

func main() {
    fmt.Println("hello world")
}

@mje

如果我理解正确的话,您想说的是:相比其他两种方案,采用 storageV1 更有意义,因为无论 *User 在网络上从一个地方传递到另一个地方多少次,User 的基本属性或 Comments 属性都不会在内存中被复制。

func main() {
    fmt.Println("hello world")
}

在Golang中,当涉及网络传输或函数传递时,理解数据复制行为很重要。以下是针对您问题的具体分析:

1. 切片本身的复制行为

切片在Go中是引用类型(包含指向底层数组的指针、长度和容量)。当复制切片时,只复制切片头(24字节),不会复制底层数组数据。

// 示例:切片复制行为
func demonstrateSliceCopy() {
    original := []int{1, 2, 3, 4, 5}
    copied := original  // 只复制切片头
    
    copied[0] = 100
    fmt.Println(original[0])  // 输出: 100,底层数组被共享
}

2. 针对您的具体问题

storageV1()

func storageV1() *User {
    return &User{
        Comments: []Comment{{ID: "1", Message: "test"}},
    }
}
  • 返回*User指针,避免复制整个User结构体
  • Comments切片头会被复制(24字节),但底层Comment数组会被共享
  • 每个Comment结构体会被完整复制到切片中

storageV2()

func storageV2() User {
    return User{
        Comments: []*Comment{{ID: "1", Message: "test"}},
    }
}
  • 返回User值,整个User结构体会被复制
  • Comments切片头被复制(24字节)
  • 切片包含的是*Comment指针,指针值被复制,但指向的Comment对象不会被复制

storageV3()

func storageV3() *User {
    return &User{
        Comments: []*Comment{{ID: "1", Message: "test"}},
    }
}
  • 返回*User指针,避免复制User结构体
  • Comments切片头被复制(24字节)
  • 切片包含*Comment指针,指针值被复制,Comment对象不会被复制

3. 网络传输场景示例

// 示例:JSON序列化时的数据复制
type User struct {
    Name     string
    Comments []Comment  // 或 []*Comment
}

func sendOverNetwork(user *User) {
    // JSON序列化会复制所有数据
    data, _ := json.Marshal(user)
    
    // 传输过程中:
    // 1. 如果Comments是[]Comment:每个Comment都会被复制到序列化数据中
    // 2. 如果Comments是[]*Comment:每个Comment也会被复制(JSON需要具体值)
}

// 性能对比示例
func benchmarkCopy() {
    // 创建测试数据
    comments := make([]Comment, 1000)
    for i := range comments {
        comments[i] = Comment{ID: fmt.Sprintf("%d", i), Message: "msg"}
    }
    
    user1 := &User{Comments: comments}           // 值切片
    user2 := &User{Comments: make([]*Comment, 1000)} // 指针切片
    
    // 指针切片在赋值时更快
    for i := range user2.Comments {
        user2.Comments[i] = &comments[i]
    }
}

4. 具体建议

对于网络传输场景:

  1. 使用[]*Comment(指针切片)

    • 减少大结构体的复制开销
    • 便于共享和修改Comment数据
    • 序列化时JSON库会自动解引用
  2. 返回*User指针

    • 避免复制整个User结构体
    • 配合指针切片实现最小化复制
// 推荐方案
func getBestPractice() *User {
    return &User{
        Name: "John",
        Comments: []*Comment{
            {ID: "1", Message: "Hello"},
            {ID: "2", Message: "World"},
        },
    }
}

// 使用示例
func processUser() {
    user := getBestPractice()
    
    // 修改comment不会触发结构体复制
    if len(user.Comments) > 0 {
        user.Comments[0].Message = "Updated"
    }
    
    // 网络传输时
    data, _ := json.Marshal(user)  // JSON库会正确处理指针切片
}

关键点:切片头复制开销很小(24字节),主要考虑因素是切片元素的大小和复制频率。对于大型结构体,使用指针切片可以显著减少内存复制。

回到顶部