如何在Go语言中清空一个切片?

166

在 Go 语言中,清空一个切片的适当方式是什么?

以下是我在 Go 论坛 中找到的:

// test.go
package main

import (
    "fmt"
)

func main() {
    letters := []string{"a", "b", "c", "d"}
    fmt.Println(cap(letters))
    fmt.Println(len(letters))
    // clear the slice
    letters = letters[:0]
    fmt.Println(cap(letters))
    fmt.Println(len(letters))
}

这是否正确?

澄清一下,缓冲区被清除以便重复使用。

一个例子是 bytes 包中的 Buffer.Truncate 函数。

注意,Reset 只是调用 Truncate(0)。 因此,在这种情况下,第70行似乎会评估为:b.buf = b.buf[0 : 0]

http://golang.org/src/pkg/bytes/buffer.go

// Truncate discards all but the first n unread bytes from the buffer.
60  // It panics if n is negative or greater than the length of the buffer.
61  func (b *Buffer) Truncate(n int) {
62      b.lastRead = opInvalid
63      switch {
64      case n < 0 || n > b.Len():
65          panic("bytes.Buffer: truncation out of range")
66      case n == 0:
67          // Reuse buffer space.
68          b.off = 0
69      }
70      b.buf = b.buf[0 : b.off+n]
71  }
72  
73  // Reset resets the buffer so it has no content.
74  // b.Reset() is the same as b.Truncate(0).
75  func (b *Buffer) Reset() { b.Truncate(0) }

1
在 http://play.golang.org/p/6Z-qDQtpbg 上进行的一个快速测试表明它将会奏效(不会改变容量,但会截断长度)。 - Jason Sperske
5个回答

254

将切片设置为nil是清除切片的最佳方法。在Go中,nil切片的行为良好,将切片设置为nil将释放底层内存给垃圾收集器。

查看示例

package main

import (
    "fmt"
)

func dump(letters []string) {
    fmt.Println("letters = ", letters)
    fmt.Println(cap(letters))
    fmt.Println(len(letters))
    for i := range letters {
        fmt.Println(i, letters[i])
    }
}

func main() {
    letters := []string{"a", "b", "c", "d"}
    dump(letters)
    // clear the slice
    letters = nil
    dump(letters)
    // add stuff back to it
    letters = append(letters, "e")
    dump(letters)
}

打印

letters =  [a b c d]
4
4
0 a
1 b
2 c
3 d
letters =  []
0
0
letters =  [e]
1
1
0 e

请注意,切片可以轻松地被别名化,使得两个切片指向同一底层内存。将设置为nil会删除该别名。

但是,此方法会将容量更改为零。


尼克感谢回复。如果可以,请查看我的更新。我正在清除切片以便重复使用。因此,我不希望底层内存被释放到垃圾回收器,因为我需要重新分配它。 - Chris Weber
6
基于标题“如何在Go中清除一个切片?”,这个回答远远是更安全的,并且应该被接受。一个完美的答案将是原始接受的答案和这个答案的结合,这样人们可以自己决定。 - Shadoninja
2
在 Go 中,向一个 nil 切片添加元素一直都是有效的吗? - alediaferia
@alediaferia 从Go 1.0开始,确实如此。 - Nick Craig-Wood
注意:在JSON编组中,空切片和nil切片的结果是不同的。 - Amin Shojaei
显示剩余3条评论

155
这完全取决于你对“清晰”的定义。其中一个有效的定义肯定是:
slice = slice[:0]

但有一个注意点。如果切片元素的类型是T:
var slice []T

通过上述“技巧”,强制执行len(slice)为零,并不会使任何元素变为空。

slice[:cap(slice)]

有资格进行垃圾回收。这在某些情况下可能是最佳方法。但这也可能导致"内存泄漏"——未使用的内存,但潜在地可达(在重新切片'slice'后),因此无法进行垃圾"收集"。


2
有趣。除了保持底层数组容量不变的情况下,是否还有其他方法可以从切片的底层数组中删除所有元素? - Chris Weber
4
只需迭代底层数组并将所有元素设置为新值。 - newacct
3
@jnml,我确实希望重用该切片(以及底层数组的存储空间),以便我不必不断地分配新的切片(包括数组)。我已经编辑了我的问题以澄清,并展示了一些来自标准库的示例代码。 - Chris Weber
1
我对Go不熟悉。您能否详细解释一下为什么这可能是最佳方法?先谢谢了。 - satoru
你确定是分片大小重置导致了内存泄漏吗?我无法复制它。 - Tommaso Barbugli
1
为了澄清一些基本点,1.与分配给nil不同,这不需要垃圾回收/重新分配内存,因此根据用例,它可能会更快;2.从实质上讲,这不是真正的内存泄漏,因为您仍然可以通过将其分配给nil来释放它,但如果您不正确使用它,则程序将在您不真正需要它时消耗内存。 - user202729

15
Go 1.21 引入了一个新的内置关键字:new builtin keyword: clear() 问题 56351 包含了以下文档:following documentation 内置函数`clear`接受一个映射、切片或类型参数类型的参数,并删除或将所有元素清零。
调用 | 参数类型 | 结果 ---|---|--- `clear(m)` | `map[K]T` | 删除所有条目,使得映射为空(`len(m) == 0`) `clear(s)` | `[]T` | 将所有元素设置为类型`T`的零值,直到切片的长度为止 `clear(t)` | 类型参数 | 见下文
如果参数类型是类型参数,则其类型集中的所有类型必须是映射或切片,并且`clear`执行与实际类型参数相对应的操作。
如果映射或切片为`nil`,则`clear`不执行任何操作。

6
我在为自己的目的研究这个问题;我有一个结构体片段(包括一些指针),我想确保我得到了正确的结果;最终进入了这个线程,并想分享我的结果。

为了练习,我做了一个小型的go playground: https://play.golang.org/p/9i4gPx3lnY

它评估为这个:

package main

import "fmt"

type Blah struct {
    babyKitten int
    kittenSays *string
}

func main() {
    meow := "meow"
    Blahs := []Blah{}
    fmt.Printf("Blahs: %v\n", Blahs)
    Blahs = append(Blahs, Blah{1, &meow})
    fmt.Printf("Blahs: %v\n", Blahs)
    Blahs = append(Blahs, Blah{2, &meow})
    fmt.Printf("Blahs: %v\n", Blahs)
    //fmt.Printf("kittenSays: %v\n", *Blahs[0].kittenSays)
    Blahs = nil
    meow2 := "nyan"
    fmt.Printf("Blahs: %v\n", Blahs)
    Blahs = append(Blahs, Blah{1, &meow2})
    fmt.Printf("Blahs: %v\n", Blahs)
    fmt.Printf("kittenSays: %v\n", *Blahs[0].kittenSays)
}

在原样运行该代码时,"meow"和"meow2"变量的内存地址相同。
Blahs: []
Blahs: [{1 0x1030e0c0}]
Blahs: [{1 0x1030e0c0} {2 0x1030e0c0}]
Blahs: []
Blahs: [{1 0x1030e0f0}]
kittenSays: nyan

我认为这证实了struct是可垃圾回收的。奇怪的是,取消对print行的注释会导致meows具有不同的内存地址:

Blahs: []
Blahs: [{1 0x1030e0c0}]
Blahs: [{1 0x1030e0c0} {2 0x1030e0c0}]
kittenSays: meow
Blahs: []
Blahs: [{1 0x1030e0f8}]
kittenSays: nyan

我认为这可能是由于打印方式被推迟了(?),但这是一种有趣的内存管理行为的说明,再次证明了这一点:
[]MyStruct = nil

很好的详细示例。谢谢! - Dolanor
3
这并不意味着 meo1 和 meow2 的内存地址相同:0x1030e0c0 不等于 0x1030e0f0(前者以 c0 结尾,后者以 f0 结尾)。 - carbocation
我要跟 @carbocation 意见一致,那些内存地址不一样。虽然我不能更好地解释这里发生了什么,但对我来说,这并不能作为证据。我确实在每次运行中看到了 meow2 的地址存在相同的 8 字节差异... - rbrtl

0

为什么要重复我2个月前报告的内容? - VonC

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接