When reading the Go language Bible, I have been curious about the use of arrays and slices, and I don’t understand why it is recommended to use slices instead of arrays. I hope that through some sorting, I can better understand slices and arrays and find their suitable usage scenarios.
Slices and arrays
Regarding how to choose slices and arrays, let’s discuss this issue.
In Go, arrays are value types, and assignments and function argument transfers will copy the entire array data.
func main() { a := [2]int{100, 200} // Assignment var b = a ("a : %p , %v\n", &a, a) ("b : %p , %v\n", &b, b) // Pass the function parameter f(a) f(b) } func f(array [2]int) { ("array : %p , %v\n", &array, array) }
Output result:
a : 0xc0000180a0 , [100 200]
b : 0xc0000180b0 , [100 200]
array : 0xc0000180f0 , [100 200]
array : 0xc000018110 , [100 200]
It can be seen that the four memory addresses are different, which confirms the previous statement. When the array data volume reaches the million level, copying the array will put huge pressure on the memory. Can it be solved by passing pointers?
func main() { a := [1]int{100} f1(&a) ("array : %p , %v\n", &a, a) } func f1(p *[1]int) { ("f1 array : %p , %v\n", p, *p) (*p)[0] += 100 }
Output result:
f1 array : 0xc0000b0008 , [100]
array : 0xc0000b0008 , [200]
It can be seen that array pointers can achieve the effect we want, solving the memory problem caused by copying arrays. However, the pointers received by the function come from the value copy, which is relatively less flexible than slices.
func main() { a := [1]int{100} f1(&a) // Slice b := a[:] f2(&b) ("array : %p , %v\n", &a, a) } func f1(p *[1]int) { ("f1 array : %p , %v\n", p, *p) (*p)[0] += 100 } func f2(p *[]int) { ("f2 array : %p , %v\n", p, *p) (*p)[0] += 100 } //Output resultf1 array : 0xc000018098 , [100] f2 array : 0xc00000c030 , [200] array : 0xc000018098 , [300]
As you can see, the pointer of the slice is different from the pointer of the original array.
Summarize
Generally speaking, using arrays for parameter passing will consume a lot of memory, and using slicing can avoid this problem. Slices are reference passes, which will not occupy more memory and are more efficient.
The data structure of slices
The slice is during the compilation periodcmd/compile/internal/types/The Slice type under the package, and its runtime data structure is located in
type SliceHeader struct { Data uintptr // Pointer to the array Len int // The length of the current slice Cap int // The current capacity of the slice, cap always >= len} // Takes up 24 bytes(({}))
A slice is a reference to a continuous fragment of an array, which can be an entire array or a part of an array. The length of the slice can be modified at runtime, with a minimum of 0 and a maximum of the length of the associative array.Slices are dynamic windows with variable length。
Create slices
Using make
slice := make([]int, 4, 6)
Memory space has applied for 6 int type memory sizes. Since len=4, the next two spaces cannot be accessed for the time being, but the capacity exists. At this time, each variable in the array is = 0.
Literal
slice := []int{0, 1, 2}
nil slices and empty slices
// nil slicevar s []int // Empty slicess2 := make([]int, 0) s3 := []int{}
The difference between an empty slice and a nil slice is that the address pointed to by the empty slice is not nil, it points to a memory address, but it does not allocate any memory space, that is, the underlying element contains 0 elements.
func main() { var s []int s2 := []int{} s3 := make([]int, 0) (s == nil) (s2 == nil) (s3 == nil) } // Output resulttrue false false
Simply put, the nil slice pointer value is nil; while the pointer value of the empty slice is not nil.
One thing to note is that whether you use nil slices or empty slices, the effect of calling the built-in functions append, len and cap is the same.
But when using append, please pay attention to:
- If the data length added by append is less than or equal to cap-len, only once the data copy is made.
- If the append data length is greater than cap-len, a larger piece of memory will be allocated, and the original data will be copied over and then added.
Especially when we need to build a slice that is copied from the original slice, we should pay attention to the value overlay issue.
func main() { s1 := []int{0, 1, 2, 3} // Define an existing slice first s2 := s1[0:1] // Copy existing slices s2 = append(s2, 100) (s1) }
Output result
[0 100 2 3]
The reason is that slicing is essentially a dynamic window of the array. When the cap is sufficient, no new memory space will be opened for copying. Any modification to the array at this time will have a joint impact on the slice of other agents of this array.
This is the end of this article about the use of Slice in Golang data structure. For more related Go Slice content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!