增长的切片和底层数组

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

Out growing Slice and Underlying array

问题

我有一个数组和一个指向它的切片,如下所示:

package main

import "fmt"

func main() {
    array_str := []string{"0a","1b","2c","3d","4e"}
    slice_str := array_str[1:4]
    fmt.Println("Initially :")
    fmt.Println("Printing 1 :Array :", array_str)
    fmt.Println("Printing 1 :Slice:", slice_str)

    //Step 1.修改切片,反映在数组中
    fmt.Println("\nAfter Alteration:")
    slice_str[0] = "alterd_1b"
    fmt.Println("Printing 2 :Array :", array_str)
    fmt.Println("Printing 2 :Slice:", slice_str)
    fmt.Println("len of slice_str:", len(slice_str), "cap of slice_str:", cap(slice_str), "len of array_str:", len(array_str))

    //Step 2.向切片追加元素,反映在数组中
    slice_str = append(slice_str, "apnded_elemnt")
    fmt.Println("\nAfter Appending:")
    fmt.Println("Printing 3 :Array :", array_str) //"4e"在数组中被替换为"apnded_elemnt"!!
    fmt.Println("Printing 3 :Slice:", slice_str)
    fmt.Println("len of slice_str:", len(slice_str), "cap of slice_str:", cap(slice_str), "len of array_str:", len(array_str))

    //Step 3.再次向切片追加元素,使切片的长度进一步增长到底层数组
    slice_str = append(slice_str, "outgrown_elemnt")
    fmt.Println("\nAfter OUT GROWING:")
    fmt.Println("Printing 4 :Array :", array_str) //显然,超出范围的元素没有添加到数组中,这是正常的
    fmt.Println("Printing 4 :Slice:", slice_str)
    fmt.Println("len of slice_str:", len(slice_str), "cap of slice_str:", cap(slice_str), "len of array_str:", len(array_str))
    //为什么容量在这里变成了8?

    //Step 4.现在更改切片元素,该元素在数组范围内,以验证它是否反映在数组中:
    fmt.Println("\nAfter Post out grown Alteration:")
    slice_str[0] = "again_alterd_1b"
    fmt.Println("Printing 2 :Array :", array_str) //切片的更改不会反映在数组中。为什么?
    fmt.Println("Printing 2 :Slice:", slice_str)
}

问题:

  1. 在第3步中,为什么切片的cap从4跳到8?

  2. 在第4步中,当切片超出范围后,对于数组范围内的元素进行的更改不会反映在数组中,反之亦然。为什么在切片超出范围后不会发生这种情况?切片超出范围时实际发生了什么?

英文:

I have an array and a slice pointing to it, like shown as follows:

package main
import "fmt"
func main() {
array_str := []string{"0a","1b","2c","3d","4e"}
slice_str:=array_str[1:4]
fmt.Println("Initially :")
fmt.Println("Printing 1 :Array :",array_str)
fmt.Println("Printing 1 :Slice:",slice_str)
//Step 1.Changing Slice and it get reflected in array
fmt.Println("\nAfter Alteration:")
slice_str[0]="alterd_1b"
fmt.Println("Printing 2 :Array :",array_str)
fmt.Println("Printing 2 :Slice:",slice_str)
fmt.Println("len of  slice_str:",len(slice_str)," cap of         slice_str:",cap(slice_str),"len of  array_str:",len(array_str))
//Step 2.appending to slice and it get reflected
slice_str = append(slice_str,"apnded_elemnt")
fmt.Println("\nAfter Apending:")
fmt.Println("Printing 3 :Array :",array_str)//"4e" is replaced with "apnded_elemnt" in array !!
fmt.Println("Printing 3 :Slice:",slice_str)
fmt.Println("len of  slice_str:",len(slice_str)," cap of   slice_str:",cap(slice_str),"len of  array_str:",len(array_str))
//Step 3.Again appending to slice so that lentght of slice is growing further to  underlaying array
slice_str = append(slice_str,"outgrown_elemnt")
fmt.Println("\nAfter OUT GROWING:")
fmt.Println("Printing 4 :Array :",array_str)//obeviously out grown elemnt not added to array that is fine
fmt.Println("Printing 4 :Slice:",slice_str)
fmt.Println("len of  slice_str:",len(slice_str)," cap of slice_str:",cap(slice_str),"len of  array_str:",len(array_str))
//How Capacity Become 8 here ???
//Step 4 .Now Changing Slice element which is in Range of array to verify it reflect on array:
fmt.Println("\nAfter Post out grown Alteration:")
slice_str[0]="again_alterd_1b"
fmt.Println("Printing 2 :Array :",array_str)//Change in slice is not reflectd in array .Why ?
fmt.Println("Printing 2 :Slice:",slice_str)
}

Playground: http://play.golang.org/p/3z52HXHQ7s

Questions:

  1. In Step 3: why does cap of the slice jumped from 4 to 8?

  2. In Step 4: after the slice is out grown, changes to the element of the slice, which is in the range of the array, is not reflected to the array and vice versa. Why is it not happening after it is grown out? What actually happens when the slice grows out?

答案1

得分: 5

请看这里:http://blog.golang.org/slices

简短回答:1)切片在长度较短时通过倍增来增长。如果你追加一次,可能还会追加第二次,这样可以避免分配内存。2)这就是切片增长的工作原理。数组无法增长,所以会分配一个新的更大的数组,将旧数组复制过去,然后将一个指向更大数组的切片返回给你。

(golang.org网站上的文档非常有帮助,易读、简短而准确。我建议先在golang.org上查看,然后再在这里提问。)

英文:

See here: http://blog.golang.org/slices

Short answers: 1) it grows by doubling (while short). If you append once you might append a second time too and this avoids allocations. 2) That's how slice growing works. An array cannot grow, so a new, larger array is allocated, the old one copied and you are handed a slice pointing to the larger copy.

(The documentation on the golang.org website is really helpful, readable, short and precise. I'd like to recommend to look at golang.org first before asking here.)

答案2

得分: 2

容量乘以2是因为它更节省资源。事实上,内存分配非常消耗资源,最好一次性分配大量内存,而不是每次都分配所需的内存。

让我们先用一个简单的示例来比较,使用字符串连接的方式:每次,Go 只分配所需的内存。

var n = time.Now()
var s = ""
for i := 0; i < 1000000; i++ {
s += "a"
}
fmt.Println(time.Now().Sub(n))
// 47.7秒

现在,我们做同样的事情,但这次使用 bytes.Buffer 类型:

var n = time.Now()
var b = bytes.NewBufferString("")
for i := 0; i < 1000000; i++ {
b.WriteString("a")
}
fmt.Println(time.Now().Sub(n))
// 18.5毫秒

区别在于 Buffer 分配内存的方式:当容量不足时,它会分配当前容量的两倍:

buf = makeSlice(2*cap(b.buf) + n)

这对切片也是一样的(我只是找不到证明的源代码...)。所以是的,你可能会损失一些空间,但这是为了更高的效率!


对于你的第二个问题,对我来说有点棘手,所以我希望 @Volker 的回答对你足够清晰!

英文:

The capacity is multiplied by 2 because it is less consuming. Indeed, memory allocation is very consuming, and it's better to allocate a lot of memory a single time than exactly what is needed every time.

Let's just compare, first with a simple example using the concatenation: every time, Go allocates just what is needed.

var n = time.Now()
var s = &quot;&quot;
for i := 0; i &lt; 1000000; i++ {
s += &quot;a&quot;
}
fmt.Println(time.Now().Sub(n))
// 47.7s

Now, let's do the same but this time using the bytes.Buffer type:

var n = time.Now()
var b = bytes.NewBufferString(&quot;&quot;)
for i := 0; i &lt; 1000000; i++ {
b.WriteString(&quot;a&quot;)
}
fmt.Println(time.Now().Sub(n))
// 18.5ms

The difference is the way Buffer allocates memory: when there is not enough capacity, it allocates twice the current capacity:

buf = makeSlice(2*cap(b.buf) + n)

Source

This works the same with slices (I was just not able to find the source code to prove it...). So yes, you may be losing some space, but this is for a much better efficiency!


You're second question is a bit more tricky for me, so I hope @Volker's answer will be clear enough for you !

huangapple
  • 本文由 发表于 2014年8月9日 05:08:03
  • 转载请务必保留本文链接:https://go.coder-hub.com/25212260.html
匿名

发表评论

匿名网友

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

确定