Go slice full resolution

Time:2022-5-18

Go slice full resolution

Directory structure:

array

section

  • Bottom structure
  • establish
    • General statement
    • Make mode
  • intercept
    • Boundary problem
  • Add
  • Extended expression
  • Capacity expansion mechanism
  • Slice transfer pit
  • Copy of slice
    • Shallow copy
    • Deep copy

array

var n [4]int
fmt. Println (n) // output: [0]
n[0] = 1
n[3] = 2
fmt. Println (len (n)) // output: 4
fmt. Println (cap (n)) // output: 4
fmt. Println (n) // output: [1 0 0 2]

b := n
n[0] = 2
fmt. Println (b) // output: [1 0 0 2]
b[0] = 3
fmt. Println (n) // output: [2 0 0 2]

explain:

  • stayvar n [4]intThe initialization of the array has been completed, and all the values are assigned to 0, and the length and capacity are 4
  • Assigning n to B is equivalent to assigning ncopyOperation, and thencopyThe result is assigned to B, so n and B belong to two arrays respectively and do not affect each other

section

Bottom structure

type slice struct {
 array unsafe. Pointer // pointer to the underlying array
 Len int // slice length
 Cap int // capacity of underlying array
}

establish

Declaration method

The default value is nil, and the initial length and capacity are 0

var s []int
fmt.Println(cap(s)) // 0
fmt.Println(len(s)) // 0
fmt.Println(s == nil) // true

Make create

make([]interface{}, len, cap)

adoptmakeThe default value is not nil, and the initial length and capacity can be specified, which is not disturbed by the automatic capacity expansion mechanismlenWhen the parameter is not 0, it will be automatically assigned as an array

a := make([]int, 0, 10)
fmt.Println(len(a)) // 0
fmt.Println(cap(a)) // 10
fmt.Println(a == nil) // false
b := make([]int, 1000)
fmt.Println(len(b)) // 1000
fmt.Println(cap(b)) // 1000
c := make([]int, 5, 10)
fmt.Println(len(c)) // 5
fmt.Println(cap(c)) // 10
fmt.Println(c) // [0 0 0 0 0]

intercept

Slices can be created based on arrays and slices, and the interception rule is to close left and open right

n := [5]int{1, 2, 3, 4, 5}
 N1: = n [1:] // intercept from n array
 fmt.Println(n1) // [2 3 4 5]
 N2: = N1 [1:] // cut from N1 slice
 fmt.Println(n2) // [3 4 5]

The slice and the original array or slice share the underlying space. Then, in the above example, modifying the element of N2 will affect the original slice and array:

N2 [1] = 6 // modifying N2 will affect the original slice and array
 fmt.Println(n1) // [2 3 6 5]
 fmt.Println(n2) // [3 6 5]
 fmt.Println(n)  // [1 2 3 6 5]

Boundary problem

  • 1. When n is an array or string expressionn[low:high]Value relationship between low and high in:

0 <= low <=high <= len(n)

  • 2. When n is slice, the expressionn[low:high]The maximum value of high becomes cap (n), and the value relationship between low and high:

0 <= low <=high <= cap(n)

If the above conditions are not met, an out of bounds panic will be sent.

The difference is that the bounded array islen(n), slice iscap(n)

Add

Built in functionappend()Used to append elements to the slice.

n := make([]int, 0)
 N = append (n, 1) // add an element
 N = append (n, 2, 3, 4) // add multiple elements
 n = append(n, []int{5, 6, 7}...) //  Add a slice
 fmt.Println(n)                   // [1 2 3 4 5 6 7]

During the append operation, if the slice capacity is insufficient, the expansion will be triggered. Follow the example above:

fmt. Println (cap (n)) // capacity equals 8
 n = append(n, []int{8, 9, 10}...)
 fmt. Println (cap (n)) // the capacity is equal to 16, and the capacity has been expanded

When the capacity is 8 at the beginning and slices [] int {8, 9, 10} are added later, the capacity becomes 16.

If append exceeds the length of the slice, a new slice will be produced and the original one will not be overwritten:

N2: = n [1:4:5] // length equals 3 and capacity equals 4
 fmt.Printf("%p\n", n2) // 0xc0000ac068
 n2 = append(n2, 5)
 fmt.Printf("%p\n", n2) // 0xc0000ac068
 n2 = append(n2, 6)
 fmt. Printf ("% P \ n", N2) // address changed, 0xc0000b8000

Extended expression

The new slice produced by simple expression will share the underlying array with the original array or slice. Although copy is avoided, it will bring some risks. In the following example, when the new N1 slice append adds an element, it overwrites the value of index position 4 of the original n, which may cause your program to be unexpected, resulting in adverse consequences

n := []int{1, 2, 3, 4, 5, 6}
n1 := n[1:4]
fmt.Println(n)       // [1 2 3 4 5 6]
fmt.Println(n1)      // [2 3 4]
N1 = append (N1, 100) // change the value of index position 4 of N from 5 to 100
fmt.Println(n)       // [1 2 3 4 100 6]
fmt.Println(n1) // [2 3 4 100]
fmt.Println(len(n[1:4])) // 3
fmt.Println(cap(n[1:4])) // 5
About capacity

The length of n [1:4] is 3. It’s easy to understand (4-1). Why is the capacity 5?

Because slice n [1:4] and slice n share the underlying spaceTherefore, its capacity is not equal to its length 3. According to the position where 1 is equal to index 1 (equal to value 2), there are five elements from value 2 to end element 6, son[1:4]The capacity is 5.

Go 1.2[3] An expression that limits the capacity of new slices is provided in:

n[low:high:max]

Max represents the capacity of the newly generated slice, and the capacity of the new slice is equal tomax-low, the relationship between low, high and Max in the expression:

0 <= low <= high <= max <= cap(n)

Continuing with the previous example, we will recalculate the capacity with the value of Max instead of sharing the capacity of N, but N2 and N still share the same underlying array

n2 := n[1:4:5]
fmt.Println(cap(n2)) // 4
fmt. Println (N2) // output [2 3 4]
n[3] = 111 
fmt. Println (N2) // output [2 3 111]

Capacity expansion mechanism

There are many online articles on the capacity expansion mechanism of go slice, and many conclusions are as follows:

Conclusion 1:

  • 1. When the required capacity exceeds twice the capacity of the original slice, the required capacity is used as the new capacity.
  • 2. When the length of the original slice is less than 1024, the capacity of the new slice will be doubled directly. When the capacity of the original slice is greater than or equal to 1024, it will be increased by 25% repeatedly until the new capacity exceeds the required capacity.

Conclusion 2:

  • Based on the estimated capacity of slice 1memory alignment , after the capacity calculation, the efficient utilization of memory should be considered for memory alignment.
example
package main

func main() {
    s := []int{1,2}
    s = append(s, 3,4,5)
    Println (cap (s)) // output 6
}

Due to initialsThe capacity of is 2. Now three elements need to be added, so throughappendIt will trigger capacity expansion and callgrowsliceFunction, whose input parametercapThe size is 2 + 3 = 5. By doubling the original capacitydoublecap = 2+2,doublecapless thancapValue, so the expected capacity value calculated in the first stagenewcap=5。 In the second stage, the element type sizeintandsys.PtrSizeEqual, passroundupsizeRound up the size of memory tocapmem=48 bytes, so the capacity of the new slicenewcap48 / 8 = 6, successful interpretation!

In sliceappendDuring operation, if the underlying array has no space to accommodate additional elements, it needs to be expanded. Capacity expansion is not to increase memory space on the basis of the original underlying array, but to allocate a new memory space as the underlying array of slices, and copy the original data and additional data to the new memory space.

It is relatively complex to determine the capacity of expansionAnd CPU bits, element size, whether to include pointers, and the number of additionsAnd so on. After reading the expansion source code logic, we find it unnecessary to tangle with the exact expansion value.

In actual use, if the capacity range of the slice can be determined, it is more appropriate to allocate sufficient capacity space during slice initialization, and the performance loss caused by capacity expansion will not be considered during append operation.

Slice transfer pit

Example 1

There are the following examples

func modifySlice(innerSlice []string) {
    innerSlice[0] = "b"
    innerSlice[1] = "b"
    fmt.Println(innerSlice)
}
 
func main() {
    outerSlice := []string{"a", "a"}
    modifySlice(outerSlice)
    fmt.Print(outerSlice)
}

//The output is as follows
[b b]
[b b]

In the above example, the slice content has been modified.

Example 2

func modifySlice(innerSlice []string) {
	innerSlice = append(innerSlice, "a")
    innerSlice[0] = "b"
    innerSlice[1] = "b"
    fmt.Println(innerSlice)
}
 
func main() {
    outerSlice := []string{"a", "a"}
    modifySlice(outerSlice)
    fmt.Print(outerSlice)
}

//The output is as follows
[b b a]
[a a]

explain:

  • In the modifyslice method, innerslice is a copy of outerslice, but it refers to the same underlying array. Therefore, in example 1, the slice contents have been modified.
  • Innerslice is a slice with the same len and cap. When the append method occurs, the capacity expansion operation will be carried out. The capacity expansion operation will generate a new slice, which is to make a deep copy in the original array and expand the capacity.

Print the details of the code and look at the output again

func modifySlice(innerSlice []string) {
	fmt.Println("begin modify")
	innerSlice = append(innerSlice, "a")
	fmt.Printf("%p, %v\n", innerSlice, &innerSlice[0])
	fmt.Println("innerSlice  len:", len(innerSlice), "cap:", cap(innerSlice))
	innerSlice[0] = "b"
	innerSlice[1] = "b"
	fmt.Println(innerSlice)
	fmt.Println("end modify")
}

func main() {
	outerSlice := []string{"a", "a"}
	fmt.Printf("%p, %v\n", outerSlice, &outerSlice[0])
	fmt.Println("outerSlice  len:", len(outerSlice), "cap:", cap(outerSlice))
	modifySlice(outerSlice)
	fmt.Println("outerSlice  len:", len(outerSlice), "cap:", cap(outerSlice))
	fmt.Printf("%p, %v\n", outerSlice, &outerSlice[0])
	fmt.Print(outerSlice)
}
//Output
0xc0000464e0, 0xc0000464e0
outerSlice  len: 2 cap: 2
begin modify
0xc000022240, 0xc000022240 // address translation
Innerslice len: 3 cap: 4 // capacity change
[b b a]
end modify
outerSlice  len: 2 cap: 2
0xc0000464e0, 0xc0000464e0
[a a]

Proved our conjecture.

Example 3

func modifySlice(innerSlice []string) {
  innerSlice = append(innerSlice, "a")
  innerSlice[0] = "b"
  innerSlice[1] = "b"
  fmt.Println(innerSlice)
}

func main() {
  outerSlice := make([]string, 0, 3)
  outerSlice = append(outerSlice, "a", "a")
  modifySlice(outerSlice)
  fmt.Println(outerSlice)
}

//Output
[b b a]
[b b]

explain:

  • The capacity of the initialization slice is 3, so ininnerSliceThe capacity expansion operation will not occur, but because it is value transfer,innerSlicejustouterSliceWhen the append operation is performed, a copy of the array is also inserted into the same array and changed at the same timeinnerSliceLength, butouterSliceLength of(lenField) has not changed, so it still prints out [b]

Supplement the print details and do some processing

func modifySlice(innerSlice []string) {
	innerSlice = append(innerSlice, "a")
	innerSlice[0] = "b"
	innerSlice[1] = "b"
	For {// continuously print the memory address and value of outerslice
		time.Sleep(time.Second / 10)
		fmt.Printf("%p\n", innerSlice)
		fmt.Println(innerSlice)
	}
}

func main() {
	Outerslice: = make ([] string, 0, 3) // initialize slices with capacity of 3 and length of 0
	outerSlice = append(outerSlice, "a", "a")
	fmt. Printf ("outerslice% P \ n", outerslice) // print the initial memory address of innerslice
	Go modifyslice (outerslice) // execute modifyslice
	time. Sleep (time. Second / 5) // wait for the end of modifyslice
	fmt. Println (outerslice) // print the innerslice value again
	fmt. Println ("outerslice", len (outerslice), cap (outerslice)) // print the length and capacity of innerslice
	outerSlice = append(outerSlice, "b")
	fmt. Println (outerslice) // / print the innerslice value again
	fmt. Printf ("outerslice% P \ n", outerslice) // print the memory address of innerslice again
	time. Sleep (time. Second) // wait for the output of the modify method
}
//Output
Outerslice 0xc00000c4c60 // initial memory address of outerslice
0xc00000c4c60 // memory address of innerslice
[b a] // value after modify
[b b]
outerSlice 2 3
[b b b]
Outerslice 0xc00000c4c60 // the memory address of outerslice has not changed
0xc00000c4c60 // memory address of innerslice的值没有发生改变
The value of [b] // innerslice is overwritten
0xc0000c4c60
[b b b]
0xc0000c4c60
[b b b]
0xc0000c4c60
[b b b]
0xc0000c4c60
[b b b]
0xc0000c4c60
[b b b]
0xc0000c4c60
[b b b]
0xc0000c4c60
[b b b]
0xc0000c4c60
[b b b]
0xc0000c4c60
[b b b]

It can be explained that whenappend()During execution, the same array is still shared without capacity expansion, but because it is value transfer,innerSliceIt’s a copy. What’s changed is the of the copylenouterSliceoflenThere is no actual change, so the output value will be higher thaninnerSliceless

Copy of slice

Shallow copy

adopt=Operator copy slice, this is a shallow copy.

func main() {
 a := []int{1, 2, 3}
 b := a
 fmt.Println(unsafe.Pointer(&a))  // 0xc00000c030
 fmt.Println(a, &a[0])            // [100 2 3] 0xc00001a078
 fmt.Println(unsafe.Pointer(&b))  // 0xc00000c048
 fmt.Println(b, &b[0])            // [100 2 3] 0xc00001a078
}

adopt[:]Copy the slice in the same way, which is also a shallow copy.

func main() {
 a := []int{1, 2, 3}
 b := a[:]
 fmt.Println(unsafe.Pointer(&a)) *// 0xc0000a4018*
 fmt.Println(a, &a[0])      *// [1 2 3] 0xc0000b4000*
 fmt.Println(unsafe.Pointer(&b)) *// 0xc0000a4030*
 fmt.Println(b, &b[0])      *// [1 2 3] 0xc0000b4000*
}

Deep copy

Deep copy, need to usecopy()Built in function

func copy(dst, src []Type) int

Its return value represents the number of copied elements in the slice

func main() {
 a := []int{1, 2, 3}
 b := make([]int, len(a), len(a))
 copy(b, a)
 fmt.Println(unsafe.Pointer(&a)) *// 0xc00000c030*
 fmt.Println(a, &a[0])      *// [1 2 3] 0xc00001a078*
 fmt.Println(unsafe.Pointer(&b)) *// 0xc00000c048*
 fmt.Println(b, &b[0])      *// [1 2 3] 0xc00001a090*
}

The number of copy elements is related to the size and capacity of the original slice and the target slice, and it only replaces the data from the original slice without generating a new slice

func main() {
	a := []int{1, 2, 3}
	b := []int{-1, -2, -3, -4}
	c := []int{-1, -2}

	fmt.Println(unsafe.Pointer(&b)) //0xc0000040f0
	copy(b, a)
	fmt.Println(unsafe.Pointer(&a)) // 0xc0000040d8
	fmt.Println(a, &a[0])           // [1 2 3] 0xc0000145e8
	fmt.Println(unsafe.Pointer(&b)) // 0xc0000040f0
	fmt.Println(b, &b[0])           // [1 2 3 -4] 0xc0000101e0
	fmt.Println(unsafe.Pointer(&c)) //0xc000004108
	copy(c, a)
	fmt.Println(unsafe.Pointer(&a)) // 0xc0000040d8
	fmt.Println(a, &a[0])           // [1 2 3] 0xc0000145e8
	fmt.Println(unsafe.Pointer(&c)) // 0xc000004108
	fmt.Println(c, &c[0])           // [1 2] 0xc0000129a0
}