An array of
In Go, an array type consists of two parts: the size of the array and the types of elements inside the array.
a1 := [1]string("A little bird's nest.")
a2 := [2]string("A little bird's nest.")
Copy the code
In the example, the variable A1 is of type [1]string, and the variable A2 is of type [2] String, and because they are of different sizes, they are not of the same type.
Array limitations
- Once an array is declared, its size and the types of its inner elements cannot be changed
- Because in Go, parameters are passed between functions by value, and when an array is taken as a parameter, it is copied. If it is very large, it will cause a lot of memory waste
Because of these limitations of arrays, Go designed Slice!
Slice slice
Slice is an abstraction and encapsulation of an array. It can dynamically add elements and automatically expand when capacity is insufficient.
Dynamic capacity
With the built-in append method, you can append any number of elements to a slice:
func main(a) {
s := []string{"A little bird's nest."."Clean"}
s = append(s,"wucs")
fmt.Println(s) //[Dust-free wucs]
}
Copy the code
When the append method appends an element, it will automatically expand the slice if the size of the slice is insufficient:
func main(a) {
s := []string{"A little bird's nest."."Clean"}
fmt.Println(Section length:.len(s),"; Section size:".cap(s))
s = append(s,"wucs")
fmt.Println(Section length:.len(s),"; Section size:".cap(s))
fmt.Println(s) //
}
Copy the code
Running result:
Section length:2; Section capacity:2Section length:3; Section capacity:4[Micro guest bird nest dust-free wucs]Copy the code
We found that the capacity was 2 before the call to Append and 4 after the call, indicating that the capacity was expanded automatically. Expansion works by creating a new underlying array, copying the elements from the original slice into the new array, and returning a slice that points to the new array.
Slice structure
A slice is actually a structure, which is defined as follows:
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
Copy the code
- Data is used to point to an array that stores slice elements.
- Len is the length of the slice.
- Cap stands for volume of slices.
With these three fields, you can abstract an array into a slice, so the underlying Data for different slices might point to the same array. Example:
func main(a) {
a1 := [2]string{"A little bird's nest."."Clean"}
s1 := a1[0:1]
s2 := a1[:]
fmt.Println((*reflect.SliceHeader)(unsafe.Pointer(&s1)).Data)
fmt.Println((*reflect.SliceHeader)(unsafe.Pointer(&s2)).Data)
}
Copy the code
Running result:
824634892120
824634892120
Copy the code
We find that the Data values printed for S1 and S2 are the same, indicating that the two slices share an array. Therefore, in the operation of slicing, the same array is used instead of copying the original elements, which reduces memory usage and improves efficiency. Multiple slices sharing the underlying array can reduce memory footprint, but if one slice modifies an internal element, the other slices will also be affected, so be careful not to modify elements in distant slices when passing slices as arguments. The essence of slicing is SliceHeader, and since the function’s arguments are value passing, you pass a copy of SliceHeader instead of a copy of the underlying array, which greatly reduces memory usage. Get the values of the three fields in the result of the slice array. In addition to using SliceHeader, you can also customize a structure.
func main(a) {
s := []string{"A little bird's nest."."Clean"."wucs"}
s1 := (*any)(unsafe.Pointer(&s))
fmt.Println(s1.Data,s1.Len,s1.Cap) 3 3 / / 824634892104
}
type any struct {
Data uintptr
Len int
Cap int
}
Copy the code
efficient
For the collection types in Go: Array, slice, map, the value and assignment operations for arrays and slices are more efficient than for map because they are sequential memory operations and can be quickly indexed to find the address of the element store. In function passes, slicing is more efficient than array, because as a parameter, you don’t copy all the elements, you just copy the three fields of the SliceHeader, which still share the same underlying array. Example:
func main(a) {
a := [2]string{"A little bird's nest."."Clean"}
fmt.Printf("Function main array pointer: %p\n", &a)
arrayData(a)
s := a[0:1]
fmt.Println((*reflect.SliceHeader)(unsafe.Pointer(&s)).Data)
sliceData(s)
}
func arrayData(a [2]string) {
fmt.Printf("Function arrayData array pointer: %p\n", &a)
}
func sliceData(s []string) {
fmt.Println("Function sliceData array pointer:", (*reflect.SliceHeader)(unsafe.Pointer(&s)).Data)
}
Copy the code
Running result:
Function main array pointer:0xc0000503c0Function arrayData (array pointer) :0xc000050400
824634049472Function sliceData array pointer:824634049472
Copy the code
It can be found:
- The pointer changes when the same array is passed to the arrayData function, indicating that the array was copied during the pass, resulting in a new array.
- The slice is passed as a parameter to the sliceData function, and the pointer does not change because the underlying Data of the slice is the same. The slice shares an underlying array, which is not copied.
The string and []byte are transferred to each other
StringHeader:
// StringHeader is the runtime representation of a string.
type StringHeader struct {
Data uintptr
Len int
}
Copy the code
The StringHeader, like the SliceHeader, represents the actual structure of the string at runtime, and you can see that the field is only one less Cap than the slice. Byte (s) and string(b) cast:
func main(a) {
s := "A little bird's nest."
fmt.Printf("S memory address: %d\n", (*reflect.StringHeader)(unsafe.Pointer(&s)).Data)
b := []byte(s)
fmt.Printf("Memory address of b: %d\n", (*reflect.SliceHeader)(unsafe.Pointer(&b)).Data)
c := string(b)
fmt.Printf("C memory address: %d\n", (*reflect.StringHeader)(unsafe.Pointer(&c)).Data)
}
Copy the code
Running result:
S memory address:8125426Memory address of b:824634892016C memory address:824634891984
Copy the code
From the example above, we can see that the cast of []byte(s) and string(b) will copy the string again. If the string is very large, this way of copying can affect performance.
To optimize the
Unsafe. Pointer means turning *SliceHeader into *StringHeader. In other words, turning *[]byte into *string. Zero copy example:
func main(a) {
s := "A little bird's nest."
fmt.Printf("S memory address: %d\n", (*reflect.StringHeader)(unsafe.Pointer(&s)).Data)
b := []byte(s)
fmt.Printf("Memory address of b: %d\n", (*reflect.SliceHeader)(unsafe.Pointer(&b)).Data)
//c1 :=string(b)
c2 := *(*string)(unsafe.Pointer(&b))
fmt.Printf("C2 memory address: %d\n", (*reflect.StringHeader)(unsafe.Pointer(&c2)).Data)
}
Copy the code
Running result:
S memory address:1899506Memory address of b:824634597104C2 memory address:824634597104
Copy the code
In this example, the contents of C1 and c2 are the same, but c2 does not apply for new memory (zero copy), c2 and b use the same memory, because their underlying Data field values are the same, thus saving memory, and also achieve the purpose of []byte to string. The SliceHeader has Data, Len, and Cap fields, and the StringHeader has Data and Len fields, so it doesn’t matter if SliceHeader is converted from unsafe.Pointer to a StringHeader. But the other way around doesn’t work, because *StringHeader lacks the Cap field required for *SliceHeader, so we need to add a default value:
func main(a) {
s := "A little bird's nest."
fmt.Printf("S memory address: %d\n", (*reflect.StringHeader)(unsafe.Pointer(&s)).Data)
b := []byte(s)
fmt.Printf("Memory address of b: %d\n". (*reflect.SliceHeader)(unsafe.Pointer(&b)).Data) sh := (*reflect.SliceHeader)(unsafe.Pointer(&s)) sh.Cap = sh.Len b1 := * (* []byte)(unsafe.Pointer(sh))
fmt.Printf("Memory address of b1: %d\n", (*reflect.StringHeader)(unsafe.Pointer(&b1)).Data)
}
Copy the code
Running result:
S memory address:1309682Memory address of b:824634892008Memory address of B1:1309682
Copy the code
- The contents of B1 and B are the same. The difference is that B1 does not apply for new memory, but uses the same memory as the variable s, because their underlying Data fields are the same, so memory is also saved.
- After the string is converted to []byte using the unsafe.Pointer command, the []byte cannot be modified. For example, the operation b1[0]=10 cannot be performed. In Go, string memory is read-only.