Foreword
There are many common data structures in golang, such as array, slice, map, etc. among them, the most commonly used are array, slice and map. Theoretically, array and slice are a kind of data structure, both of which are sequential structures. However, due to the fixed length characteristics of array, In some cases, the use of dynamic length is very unfriendly. At this time, slice needs to be used to replace the fixed length array.
What is Slice
The official explanation is as follows: Slices wrap arrays to give a more general, powerful, and convenient interface to sequences of data. Except for items with explicit dimension such as transformation matrices, most array programming in Go is done with slices rather than simple arrays. The general meaning is as follows: Slice It's a packaged one array,It can provide a more general, powerful and convenient interface for data sequence. In addition to terms with explicit dimensions, such as transformation matrices, Go Most array programming in is done using slices rather than simple arrays.
I think slice is a bit like the vector in the c + + standard library, but the underlying implementation method may be a little different (I don't know much about c + +, if someone knows the underlying implementation of vector, I can solve my doubts). Slice is a structure that wraps the go array, but this structure can only be seen at other levels such as compilation, In the process of using it, we only need to define it as an array, which can be converted into slice structure during compilation. Next, let me analyze the relevant structure source code and operation source code of slice.
Code parsing
slice structure
1 // slice Structure, which will be built during compilation 2 // If you want to use it during operation, you can use its corresponding reflect structural morphology 3 // Namely reflect.SliceHeader 4 type slice struct { 5 // A pointer to the underlying array 6 array unsafe.Pointer 7 // slice Current number of elements, i.e len()Number returned when 8 len int 9 // slice Capacity i.e cap()Number returned when 10 cap int 11 }
slice initialization
slice can be initialized in three ways:
- Subscript initialization method: a:=slice [:] / / this slice is a slice created by others. This creation method is closest to the underlying creation method. During compilation, the statement will be converted to the OpSliceMake operation of the compiler, which will call the SliceMake operation. The SliceMake operation will accept four parameters to create a new slice, including element type, array pointer, slice size and capacity, which is the same as the field composition of the slice structure shown in the previous chapter, In addition, the subscript initialization method does not copy the data in the original array, but directly refers to the pointer to the original array, which will affect the slice of the original slice when modifying slice a.
- Literal initialization method: ` ` ` a:=[]int{1,2,3} `, the process is as follows
- When this method is created during compilation, it will first generate an array with a length of 3 (the length is automatically inferred according to the literal quantity)
- Assign values to the elements of this array
- Then a new length of 3 will be displayed (the length depends on the literal quantity)
- Pointer to the array of (auto inferred)
- Assign this pointer to slice according to the most basic subscript initialization method
1 var arr [3]int 2 arr[0] = 1 3 arr[1] = 2 4 arr[2] = 3 5 var arrp *[3]int = new([3]int) 6 *arrp = arr 7 slice := arrp[:]
3. Create slice with the keyword make([]int,3,3): initialize the slice with the keyword make. First, verify the len and cap passed in during the compilation phase to verify whether they are negative and whether len > cap. Determine whether the slice will be initialized on the heap by judging the size of the slice and whether it escapes. If the slice is small enough and there is no escape, An array of cap values will be created, and then initialized as literal initialization. If cap is 0, an array of corresponding length will be created according to the value of len. If escape occurs or the slice is too large, the runtime.makeslice() function will be called to allocate the slice memory on the heap. The runtime.makeslice code is as follows:
1 // This function passes in the type, length and capacity of the slice to be initialized, and the returned pointer will be completed through the caller group slice structural morphology 2 func makeslice(et *_type, len, cap int) unsafe.Pointer { 3 // Judge whether the product of type and capacity will exceed the size of allocable memory, and whether the length is 0 and the capacity is less than the length 4 mem, overflow := math.MulUintptr(et.size, uintptr(cap)) 5 if overflow || mem > maxAlloc || len < 0 || len > cap { 6 mem, overflow := math.MulUintptr(et.size, uintptr(len)) 7 if overflow || mem > maxAlloc || len < 0 { 8 panicmakeslicelen() 9 } 10 panicmakeslicecap() 11 } 12 // If all are normal, call this function to request to return the element size in a continuous slice×Pointer to the memory space of the slice capacity length 13 return mallocgc(mem, et, true) 14 }
Access element
Slice access elements are accessed through the slice structure
Element address of corresponding index=Pointer to the underlying array+Number of bytes occupied by the corresponding element∗Indexes
The compiler will return the corresponding value through the element address of the corresponding index, that is, direct address access
Addition and expansion
A major advantage of slice over array is that it can be dynamically expanded according to the usage to adapt to the data added at any time. When adding, it calls the append function to append the tail of slice. If the cap value of slice is less than the current len plus the number of incoming values in append, the expansion operation will start, The append function has no explicit function body, but is converted during compilation. When append finds that it needs capacity expansion, it will call the runtime.growslice method. The source code of this method is as follows (to remove some useless code):
1 func growslice(et *_type, old slice, cap int) slice { 2 // If the required capacity is less than the required capacity, an error is reported 3 // Theoretically, this problem should not arise 4 if cap < old.cap { 5 panic(errorString("growslice: cap out of range")) 6 } 7 // append Can't create one nil Pointer but len Slice not 0 8 if et.size == 0 { 9 return slice{unsafe.Pointer(&zerobase), old.len, cap} 10 } 11 12 newcap := old.cap 13 doublecap := newcap + newcap 14 // If the demand capacity is greater than double the old capacity, use the demand capacity directly 15 if cap > doublecap { 16 newcap = cap 17 } else { 18 // If current len If it is less than 1024, the capacity will be doubled directly; otherwise, it will be 1.25 Multiply until the required capacity is met 19 if old.len < 1024 { 20 newcap = doublecap 21 } else { 22 for 0 < newcap && newcap < cap { 23 newcap += newcap / 4 24 } 25 if newcap <= 0 { 26 newcap = cap 27 } 28 } 29 } 30 31 var overflow bool 32 var lenmem, newlenmem, capmem uintptr 33 // During capacity expansion, you cannot simply len To determine the memory length required for capacity expansion 34 // Memory alignment is also performed according to the element type of the slice 35 // Memory alignment occurs when the number of bytes occupied by an element is a multiple of 1, 8, or 2 36 // The memory alignment strategy is rounded up 37 // When rounding the target go 67 in memory allocation policy class Round the size in the page 38 switch { 39 case et.size == 1: 40 lenmem = uintptr(old.len) 41 newlenmem = uintptr(cap) 42 capmem = roundupsize(uintptr(newcap)) 43 overflow = uintptr(newcap) > maxAlloc 44 newcap = int(capmem) 45 case et.size == sys.PtrSize: 46 lenmem = uintptr(old.len) * sys.PtrSize 47 newlenmem = uintptr(cap) * sys.PtrSize 48 capmem = roundupsize(uintptr(newcap) * sys.PtrSize) 49 overflow = uintptr(newcap) > maxAlloc/sys.PtrSize 50 newcap = int(capmem / sys.PtrSize) 51 case isPowerOfTwo(et.size): 52 var shift uintptr 53 if sys.PtrSize == 8 { 54 // Mask shift for better code generation. 55 shift = uintptr(sys.Ctz64(uint64(et.size))) & 63 56 } else { 57 shift = uintptr(sys.Ctz32(uint32(et.size))) & 31 58 } 59 lenmem = uintptr(old.len) << shift 60 newlenmem = uintptr(cap) << shift 61 capmem = roundupsize(uintptr(newcap) << shift) 62 overflow = uintptr(newcap) > (maxAlloc >> shift) 63 newcap = int(capmem >> shift) 64 default: 65 lenmem = uintptr(old.len) * et.size 66 newlenmem = uintptr(cap) * et.size 67 capmem, overflow = math.MulUintptr(et.size, uintptr(newcap)) 68 capmem = roundupsize(capmem) 69 newcap = int(capmem / et.size) 70 } 71 72 // If the required memory exceeds the maximum allocable memory, the panic 73 if overflow || capmem > maxAlloc { 74 panic(errorString("growslice: cap out of range")) 75 } 76 77 var p unsafe.Pointer 78 // If the current element type is not a pointer, the position beyond the current length of the slice is cleared 79 // At last, the contents of the original array memory are copied to the newly applied memory. 80 if et.ptrdata == 0 { 81 p = mallocgc(capmem, nil, false) 82 memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem) 83 } else { 84 // If it is a pointer, it will be processed according to gc Aspect protects it from null pointers being deleted during allocation gc recovery 85 p = mallocgc(capmem, et, true) 86 if lenmem > 0 && writeBarrier.enabled { 87 bulkBarrierPreWriteSrcOnly(uintptr(p), uintptr(old.array), lenmem-et.size+et.ptrdata) 88 } 89 } 90 memmove(p, old.array, lenmem) 91 //This function eventually returns a new slice 92 return slice{p, old.len, newcap} 93 }
Copy of slice
Slice copying is also an interface provided for slices. You can copy the values in src slices to dst slices by calling the copy() function. After slice copying through this function, the operations on dst slices will not have any impact on src. The copy length is calculated according to the minimum len length in src and dst slices. The source code of runtime.slicecopy is as follows:
func slicecopy(toPtr unsafe.Pointer, toLen int, fmPtr unsafe.Pointer, fmLen int, width uintptr) int { if fmLen == 0 || toLen == 0 { return 0 } n := fmLen if toLen < n { n = toLen } if width == 0 { return n } size := uintptr(n) * width if size == 1 { // If only one element is directly assigned *(*byte)(toPtr) = *(*byte)(fmPtr) } else { // Directly copy the memory, if slice Too much data will affect performance memmove(toPtr, fmPtr, size) } return n }