1

Slice, also known as dynamic array, is implemented based on arrays, but is more flexible than arrays. Generally, slices are used instead of arrays in the Go language.

features

In addition to directly cutting from the array, there are mainly the following ways to declare and initialize slices: variable declarations, literal declarations, and the use of built-in functions new() and make(). It is generally recommended to use the make() function to initialize.

realization principle

Data structure

The source code src/runtime/slice.go:slice defines the data structure of slice:

type slice struct {
    array unsafe.Pointer // 底层数组指针
    len   int            // 长度
    cap   int            // 容量
}

related operations

created

When we use make() to create a slice, we can specify the length and capacity at the same time. The bottom layer will allocate an array, and the length of the array is the capacity.

For example, slice := make([]int, 5, 10) has the following structure:

image.png

capacity expansion

When adding elements to a slice, if the capacity of the slice is not enough to accommodate the added elements, expansion will be triggered. Expansion is actually reallocating a larger memory, copying the original slice into the new slice, and then appending the data. This is why we often see the wording s := append(s, 1)

The changes in capacity during expansion follow the following basic rules:

  • The capacity of the original slice is less than 1024, and the capacity of the new slice is doubled;
  • The capacity of the original slice is greater than or equal to 1024, and the capacity of the new slice is expanded to 1.25 times of the original.

However, in the actual process, the type of element and its memory allocation strategy will be considered comprehensively, and some fine-tuning (such as memory alignment) will be made on the basis of this rule.

Below is a code example. Observe the output of the function. Combining the content of this part, you can understand the internal mechanism of expansion well:

package main

import "fmt"

func SliceRise(s []int) {
    s = append(s, 0)
    for i := range s {
        s[i]++
    }
}

func main() {
    s1 := []int{1, 2}
    s2 := s1
    s2 = append(s2, 3)
    SliceRise(s1)
    SliceRise(s2)
    fmt.Println(s1, s2)
}
copy

When copying two slices, the elements of the source slice will be copied one by one into the underlying array pointed to by the destination slice. The number of copies depends on the smaller value of the length of the two slices. For example, when a slice of length 10 is copied to a slice of length 5, only 5 elements will be copied, and expansion will not be triggered in the process.

Some tips

We often say that slice is a so-called reference type, mainly because its structure contains pointers to the underlying array, so if you modify the elements in the slice inside the function, the external variables will also be affected.

We can use the a[low:high] expression to cut the string, and a new string will be generated at this time. This method can quickly intercept the string.

If we cut slices from strings or arrays, the expression a[low:high] needs to satisfy: 0 <= low <= high <= len(a), if not, panic will be triggered. But if the cut object is another slice, the maximum value of low and high is not the length of a, but the capacity of a. In actual use, we need to pay attention to this.


与昊
225 声望636 粉丝

IT民工,主要从事web方向,喜欢研究技术和投资之道