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

huangapple go评论90阅读模式
英文:

How do you clear a slice in Go?

问题

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

Go论坛中,我找到了以下内容:

// test.go
package main

import (
	"fmt"
)

func main() {
	letters := []string{"a", "b", "c", "d"}
	fmt.Println(cap(letters))
	fmt.Println(len(letters))
    // 清空切片
	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从缓冲区中丢弃除了前n个未读字节之外的所有内容。
60	// 如果n为负数或大于缓冲区的长度,则会引发panic。
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			// 重用缓冲区空间。
68			b.off = 0
69		}
70		b.buf = b.buf[0 : b.off+n]
71	}
72	
73	// Reset重置缓冲区,使其不包含任何内容。
74	// b.Reset()与b.Truncate(0)相同。
75	func (b *Buffer) Reset() { b.Truncate(0) }
英文:

What is the appropriate way to clear a slice in Go?

Here's what I've found in the go forums:

// test.go
package main

import (
	&quot;fmt&quot;
)

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

Is this correct?

To clarify, the buffer is cleared so it can be reused.

An example is Buffer.Truncate function in the bytes package.

Notice that Reset just calls Truncate(0). So it appears that in this case line 70 would evaluate:
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 &lt; 0 || n &gt; b.Len():
65			panic(&quot;bytes.Buffer: truncation out of range&quot;)
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

得分: 251

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

查看playground

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)
    // 清除切片
    letters = nil
    dump(letters)
    // 添加内容
    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将消除这种别名。

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

英文:

Setting the slice to nil is the best way to clear a slice. nil slices in go are perfectly well behaved and setting the slice to nil will release the underlying memory to the garbage collector.

See playground

package main

import (
	&quot;fmt&quot;
)

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

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

Prints

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

Note that slices can easily be aliased so that two slices point to the same underlying memory. The setting to nil will remove that aliasing.

This method changes the capacity to zero though.

答案2

得分: 152

这完全取决于你对“清空”的定义。其中一个有效的方法是:

slice = slice[:0]

但是有一个问题。如果切片元素的类型是T:

var slice []T

通过上述“技巧”强制len(slice)为零,并不会使得

slice[:cap(slice)]

中的任何元素有资格进行垃圾回收。这在某些情况下可能是最佳方法。但它也可能导致“内存泄漏”-内存没有被使用,但可能在重新切片'slice'后可达(并且因此无法进行垃圾“回收”)。

英文:

It all depends on what is your definition of 'clear'. One of the valid ones certainly is:

slice = slice[:0]

But there's a catch. If slice elements are of type T:

var slice []T

then enforcing len(slice) to be zero, by the above "trick", doesn't make any element of

slice[:cap(slice)]

eligible for garbage collection. This might be the optimal approach in some scenarios. But it might also be a cause of "memory leaks" - memory not used, but potentially reachable (after re-slicing of 'slice') and thus not garbage "collectable".

答案3

得分: 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

我认为这证实了该结构体被垃圾回收了。有趣的是,取消注释被注释的打印行,将为meows变量产生不同的内存地址:

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

我认为这可能是打印被延迟执行的原因(?),但这是一种有趣的内存管理行为的示例,并且对于以下代码的又一个支持:

[]MyStruct = nil
英文:

I was looking into this issue a bit for my own purposes; I had a slice of structs (including some pointers) and I wanted to make sure I got it right; ended up on this thread, and wanted to share my results.

To practice, I did a little go playground:
https://play.golang.org/p/9i4gPx3lnY

which evals to this:

package main

import &quot;fmt&quot;

type Blah struct {
    babyKitten int
    kittenSays *string
}

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

Running that code as-is will show the same memory address for both "meow" and "meow2" variables as being the same:

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

which I think confirms that the struct is garbage collected. Oddly enough, uncommenting the commented print line, will yield different memory addresses for the meows:

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

I think this may be due to the print being deferred in some way (?), but interesting illustration of some memory mgmt behavior, and one more vote for:

[]MyStruct = nil

答案4

得分: 5

Go 1.21 (Q3 2023)将提出一个新的内置关键字:clear()

issue 56351包含了以下文档:

内置函数clear接受一个map、slice或类型参数类型的参数,并删除或将所有元素清零。

调用 参数类型 结果
clear(m) map[K]T 删除所有条目,结果是一个空的map (len(m) == 0)
clear(s) []T s的长度之前的所有元素设置为T的零值
clear(t) 类型参数 参见下文

如果参数类型是类型参数,则其类型集中的所有类型必须是map或slice,并且clear执行与实际类型参数相对应的操作。

如果map或slice为nilclear不执行任何操作。

英文:

Go 1.21 (Q3 2023) will propose a new builtin keyword: clear()

issue 56351 includes the following documentation:

> The built-in function clear takes an argument of map, slice or type parameter type, and deletes or zeroes out all elements.
>
> | Call | Argument type | Result > |
> |------------|----------------|--------------------------------------------------------|
> | clear(m) | map[K]T | deletes all entries, resulting in an empty map (len(m) == 0) |
> | clear(s) | []T | sets all elements up to the length of s to the zero value of T |
> | clear(t) | type parameter | see below > |
>
> If the argument type is a type parameter, all types in its type set must be maps or slices, and clear performs the operation corresponding to the actual type argument.
>
> If the map or slice is nil, clear is a no-op.

答案5

得分: 0

2023更新

在Go 1.21中,您可以使用clear函数:

https://tip.golang.org/ref/spec#Clear

英文:

2023 Update

With Go 1.21 you can use clear function:

https://tip.golang.org/ref/spec#Clear

huangapple
  • 本文由 发表于 2013年6月7日 04:38:30
  • 转载请务必保留本文链接:https://go.coder-hub.com/16971741.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定