Understanding of slicing

The array in GO is a fixed-length data structure, while slicing can be understood as a dynamic array concept.

It is array-based and provides an API for dynamic expansion. In use, it can be understood as an ArrayList in Java, but its underlying level is very different.

Section composition

Section consists of three parts

  1. Pointer to the underlying array
  2. Capacity
  3. Length (length)

As you can see from the composition, the slice itself does not contain an array but has a pointer to the underlying array, which is different from the ArrayList in Java.

Because each ArrayList has a “pointer” to its own array, it is possible for a slice of GO to have multiple slices corresponding to the same underlying array.

Basic Principles of Slicing

The code to define a slice is as follows:


slice := make([]string.2)
fmt.Println("Capacity:".cap(slice), "Length:".len(slice))
Output: Capacity: 2 Length: 2
slice = make([]string.2.3)
fmt.Println("Capacity:".cap(slice), "Length:".len(slice))
Output: Capacity: 3 Length: 2
Copy the code

As you can see, if the capacity is not specified, the capacity and length are the same by default. So what is the internal data structure after making? Slice = make([]string, 2, 3)

As shown in the figure, the bottom layer creates an array with the length of the slice capacity and points the pointer of the slice to the first element of the array. In this case, the access to the shear is restricted according to the length of the cut edge.

The length of the slice is 2. If the third element of the slice is accessed, an error will be generated:

println(slice[2])
Panic: Runtime error: index out of range [2] with length 2
Copy the code

The English word for slice is slice, and the name makes sense because we can “cut” a new slice from a slice. This operation is as follows:

newSlice := slice[1:2:3]
fmt.Println("Capacity:".cap(newSlice), "Length:".len(newSlice))
Output: Capacity: 2 Length: 1
Copy the code

The underlying structure is as follows:

[1:2:3] Slice [1:2:3]

  1. The first subscript, 1 here, means that the new slice starts from the position of the array index 1 to which the original slice points, and this is the starting subscript to specify the new slice.
  2. The second subscript, in this case 2, refers to the length of the new slice at the original array location, which is specified as 2 (not the array index) and starts at 1, so the length of the new slice is 2-1=1;
  3. The third subscript: here is 3: refers to the capacity of the new slice in the original array, specified here as 3 (not the array index), the starting position is 1, so the capacity of the new slice is 3-1=2;

Note that an out-of-bounds error is reported if the specified three subscripts exceed the length of the underlying array (not the index).

At this point, the two slices share an underlying array, and modification of elements in either slice will affect each other.

slice := make([]string.2)
slice = make([]string.2.3)
newSlice := slice[1:2:3]
newSlice[0] = "Zhang"
printSlice(slice, "slice")
printSlice(newSlice, "newSlice")

func printSlice(slice []string, name string) {
   fmt.Println("----- start printing" + name + "Slice element of")
   for index, item := range slice {
      fmt.Println(name+"Index as", index, "The location data is:"+item)
   }
   fmt.Println(name, "Length:".len(slice), "Capacity is:".cap(slice))
   fmt.Println("----- End of printing" + name + "Slice element of")}// ----- starts printing slice elements
// Slice index 0 is:
// Slice index 1 is
// ----- End Print slice elements
// ----- starts printing the slice elements of newSlice
// The newSlice index is 0
// ----- End Print the slice elements of newSlice

Copy the code

Slice with append function

So once we’ve defined the slice and we need to add elements we need to use the append method.

slice := []string{"Eggplant"."Potato"."Cucumber"."Watermelon"}
newSlice := slice[1:3:4]
printSlice(slice, "slice")
printSlice(newSlice, "newSlice")

//----- starts printing slice elements
// Slice 0 is: eggplant
// Slice index 1 is: potato
// Slice index 2 is: cucumber
// Slice index 3 is: melons
// The slice length is 4. The capacity is 4
//----- End Print slice elements
//----- starts printing the slice elements of newSlice
//newSlice index 0 is: potato
//newSlice index 1 is: cucumber
//newSlice The length is 2. The capacity is 3
//----- End Print the slice elements of newSlice

Copy the code

In this case, the structure of the underlying array is shown in the figure, where the length and capacity of slice are both 4, that is, the entire underlying array, and newSlice specifies slice[1:3:4], that is, its own capacity is 3, but the slice length is 2

At this point we perform the first add data:

newSlice = append(newSlice, "Banana")
printSlice(slice, "After first data addition: slice")
printSlice(newSlice, "After first addition of data: newSlice")
//----- starts printing the slice element of slice after the first addition of data
// Add data for the first time: slice 0 is: eggplant
// After adding data for the first time: slice index 1 is: potato
// After adding data for the first time: slice index 2: cucumber
// After adding data for the first time: slice index 3 is: banana
// After adding data for the first time: Slice length: 4 Capacity: 4
//----- End Print the slice element of slice after the first data is added
//----- starts printing the slice element of newSlice after the first addition of data
// After adding data for the first time: newSlice 0 is: potato
// After adding data for the first time: newSlice index 1 is: cucumber
// After adding data for the first time: newSlice index 2: bananas
// After data is added for the first time: newSlice length: 3 Capacity: 3
//----- End Print the slice element of newSlice after the first data is added
Copy the code

As you can see, the append to newSlice also modifies slice’s data, since the underlying data is shared. The structure of the end of append is as follows

Here, when adding data to newSlice, it overwrites the original ‘watermelon’ because its size is 3 and its current length is 2, because the element in its’ watermelon ‘position is unused to newSlice.

At this point we add data to newSlice a second time

newSlice = append(newSlice, The word "apple")
printSlice(slice, "After second data addition: slice")
printSlice(newSlice, "After the second addition of data: newSlice")
//----- starts printing the slice element of slice after the second addition of data
// Add data for the second time: slice 0 is: eggplant
// Add data for the second time: slice index 1 is: potato
// After the second addition of data: slice index 2 is: cucumber
// After adding data for the second time: slice index 3 is: banana
// After the second data addition, the slice length is 4 and the capacity is 4
//----- End Print the slice element of slice after the second data addition
//----- starts printing the slice element of newSlice after the second addition of data
// After the second addition of data: newSlice 0 is: potato
// After the second addition of data: newSlice index 1 is: cucumber
// After the second addition of data: newSlice index 2 is: banana
// After the second addition of data: newSlice index 3 is: apple
// After data is added for the second time: newSlice length: 4 Capacity: 6
//----- End Print the slice element of newSlice after the second data addition
Copy the code

As you can see, the size of newSlice has increased by twice, but the size of slice has not changed! Here’s how it works:

When newSlice appends its element, it runs out of space to add elements to the underlying array because its length is the same as its capacity. In this case, it creates an array with double the size and copies the original data into the new array. And then you put the apple in the newly added array, and the new slice will put the pointer to the first element of the newly created array.

summary

  1. For slicing, it is an immutable data structure, and assignments to arrays are the underlying arrays of operations
  2. To sliceappendOperation, each time a new Slice object is created, so each timeappendAfter that, local variables are reassigned
  3. When the capacity is less than 1000, the capacity will be doubled each time. When the capacity exceeds 1000, the capacity will be expanded 1.25 times each time