Slice and array of go

This time we will mainly discuss the array (array) type and slice (slice) type of Go language. They sometimes confuse beginners. They all belong to the type of collection class, and their values can also be used to store values (or elements) of a certain type.
 
The most important difference, however, is that the values of the array type (hereinafter referred to as arrays) are fixed in length, and the values of the slice type (hereinafter referred to as slices) are variable in length.
 
The length of an array must be given when it is declared, and it will not change after that. It can be said that the length of the array is part of its type.
 
For example, [1] string and [2] string are two different array types; the type literal of a slice has only the type of its element, not its length. The length of a slice can automatically increase with the number of elements in it, but not decrease with the number of elements decreasing.
 
We can actually think of slices as a simple encapsulation of arrays, because the underlying data structure of each slice must contain an array. The latter can be called the underlying array of the former, and the former can also be seen as a reference to a continuous fragment of the latter.
 
Because of this, the slice type of Go language belongs to the reference type, the dictionary type, the channel type, the function type and so on, and the array type of Go language belongs to the value type, the basic data type and the structure type of the value type.
 
Note that there is no confusing “pass-by or reference” problem in Go languages like Java. In Go, we judge what is called a “pass value” or “pass reference” simply by the type of value being passed.
 
If the passed value is a reference type, it is called “reference”. If the value passed is a value type, then the value is passed. From the perspective of delivery cost, the value of reference type often has a much lower value than the ratio type.
 
Let’s talk about the theme again. We can apply index expressions on arrays and slices, and we get an element. We can also apply slice expressions over them, and we will get a new slice.
 
By calling the built in function len, we can get their length. By calling the built in function cap, we can get their capacity.
 
But note that the capacity of an array is always equal to its length, which is immutable. The capacity of slices is not the same, and its variation is regular. Now let’s learn about it through a question. Our question today is: how to correctly estimate the length and volume of slices?
 
 
package main
 
import “fmt”
 
func main() {
// Example 1.
s1 := make([]int, 5)
fmt.Printf(“The length of s1: %d\n”, len(s1))
fmt.Printf(“The capacity of s1: %d\n”, cap(s1))
fmt.Printf(“The value of s1: %d\n”, s1)
s2 := make([]int, 5, 8)
fmt.Printf(“The length of s2: %d\n”, len(s2))
fmt.Printf(“The capacity of s2: %d\n”, cap(s2))
fmt.Printf(“The value of s2: %d\n”, s2)
}
Let me describe what it has done. First, I declare an []int type variable S1 using the built-in function make. The second parameter I passed to the make function is 5, which indicates the length of the slice. I declare the slice S2 in almost the same way, but I just introduced one more parameter.Number 8 to indicate the capacity of the slice.
 
Now, the specific question is: what is the capacity of slice S1 and S2?
 
Typical answer to this question: the capacity of slices S1 and S2 is 5 and 8 respectively.
 
Problem analysis
Parse the problem. Why is the capacity of S1 5? Because when I declare S1, I set it to 5.
 
When we initialize a slice with the make function, if its capacity is not specified, it will be the same length. If the capacity is specified in initialization, the actual capacity of the slice is also it. That’s why the capacity of S2 is 8.
 
Let’s go through S2 to clarify the length, capacity and their relationship. When I initialize the slice represented by S2, I specify its length and capacity.
 
In this case, its capacity actually represents the length of its underlying array, which is 8. Note that the bottom array of slices is equivalent to the array we mentioned earlier, and its length is immutable.
 
Now you need to imagine with me: there’s a window through which you can see an array, but you don’t necessarily see all the elements in the array, sometimes only a continuous part of the elements.
 
Now, this array is the bottom array of slice S2, and this window is the slice S2 itself. The length of S2 actually specifies the width of the window, which determines which contiguous elements you can see in the underlying array through s2.
 
Since the length of S2 is 5, you can see the first element to the fifth element in the underlying array, and the index range of the underlying array is [0, 4].
 
The windows represented by the slices are also divided into small lattices, just like the windows in our house. Each small lattice corresponds to an element in its underlying array.
 
Let’s continue with s2, for example, where the leftmost lattice of the window corresponds exactly to the first element in its underlying array, the element with an index of 0. So it’s fair to say that the element in S2 that the index from 0 to 4 points to is exactly the five elements represented by the index from 0 to 4 in its underlying arrayPrime.
 
Remember, when we initialize a slice with a make function or slice-valued literal, such as [] int {1, 2, 3}, the small lattice on the left-most side of the window always corresponds to the first element in its underlying array.
 
But when we generate a new slice based on an array or slice using a slice expression, the situation becomes complicated. Let’s look at another example:
 
s3 := []int{1, 2, 3, 4, 5, 6, 7, 8}
s4 := s3[3:6]
fmt.Printf(“The length of s4: %d\n”, len(s4))
fmt.Printf(“The capacity of s4: %d\n”, cap(s4))
fmt.Printf(“The value of s4: %d\n”, s4)
There are 8 elements in slice S3, which are integers from 1 to 8. The length and capacity of S3 are 8. Then I used slice expression s3[3:6] to initialize slice S4. The question is, what is the length and capacity of the S4?
 
This is not difficult. It can be done with subtraction. First of all, you need to know what the two integers in square brackets represent. If I change my expression, you may be clear about it, that is, [3, 6.
 
This is the interval representation in mathematics, often used to represent the range of values, and I’ve actually used it several times in this column. As you can see, [3:6] is meant to be seen through the new window, and the index range of elements in S 3 is from 3 to 5 (note, not including 6).
 
Here, 3 can be called the initial index, and 6 can be called the end index. The length of S4 is 6 minus 3, or 3. So it can be said that the elements in S4 that are indexed from 0 to 2 correspond to the three elements in S3 and its underlying array that are indexed from 3 to 5.
 
Look at the capacity again. As I said earlier, the capacity of a slice represents the length of its underlying array, but this is limited to initializing a slice using make functions or slice-valued literals. A more general rule is that the capacity of a slice can be viewed as the most visible of the underlying array through this windowThe number of elements.
 
Since S4 is obtained by slicing on s3, the underlying array of S3 is the underlying array of s4. Because, with the underlying array unchanged, the window represented by the slice can be extended to the right until the end of the underlying array. Therefore, the capacity of S4 is 8 of the length of its underlying array.Subtract the initial index 3 in the above slice expression, that is, 5.
 
Note that the window represented by the slice can not be extended to the left. That is to say, we can never see the 3 leftmost elements in S3 through S4.
 
Finally, by the way, expand the slice window to the right. For S4, slice expression s4[0:cap (S4)] can be done. I think you should be able to read it. The result value of the expression (that is, a new slice) will be []int{4, 5, 6, 7.8}, its length and capacity are 5.
 
Knowledge expansion
1. Question: how to estimate the growth of slice capacity?
 
Once a slice is unable to accommodate more elements, the Go language tries to expand its capacity. Instead of changing the original slice, it produces a larger slice and copies the original and new elements together into the new slice.
 
In general, you can simply assume that the capacity of the new chip (hereinafter referred to as the new capacity) will be twice the capacity of the original chip (hereinafter referred to as the original capacity).
 
However, when the length of the original slice (hereinafter referred to as the original length) is greater than or equal to 1024, the Go language will take 1.25 times the original capacity as the benchmark for the new capacity (the following new capacity benchmark).
 
The new capacity benchmark will be adjusted (multiplied by 1.25) until the result is not less than the sum of the original length and the number of elements to be added (hereinafter referred to as the new length). In the end, the new capacity tends to be larger than the new length. Of course, equality is also possible.
 
In addition, if we add too many elements at a time to make the new length larger than twice the original capacity, the new capacity will be based on the new length.
 
Note that, as in the previous case, the final new capacity is in many cases larger than the new capacity benchmark. For more details, see the specific implementation of growslice and related functions in the slice.go file of the runtime package.
 
 
2. Question: when will the bottom array of slices be replaced?
 
To be exact, the bottom array of a slice will never be replaced. Why? Although the Go language is bound to generate new underlying arrays when it expands, it also generates new slices. It uses the new slice as a window to the new underlying array, without doing anything to the original slice and its underlying arrayWhat to change.
 
Remember, the append function returns a new slice to the original underlying array without scaling, and the append function returns a new slice to the new underlying array when scaling is needed.
 
So, strictly speaking, the word “expansion” is used here, though not the right image. However, given that this kind of address has been used widely, there is no need for us to find another new word.
 
By the way, as long as the new length does not exceed the original capacity of the slice, there is no expansion when the append function is used to append elements to it. This will only replace the elements on the right side of the slice window (the underlying array) with new elements.
 
summary
 
To sum up, we discussed the relationship between arrays and slices together today. Slicing is based on array, variable length, and very light. The capacity of a slice is always fixed, and a slice is bound to only one underlying array.
 
In addition, the capacity of a slice is always a value between the length of the slice and the length of the underlying array, and it is also related to the position of the leftmost element of the slice window in the underlying array. Those two methods of calculating the length and volume of slices by subtraction must be remembered.
 
In addition, the append function always returns a new slice, and if the new slice has a larger capacity than the original slice, it means that the underlying array is also new. Also, you don’t really have to worry too much about the details of the slice expansion strategy, as long as you understand the basic rules and get close to them.A similar estimate is fine.

Leave a Reply

Your email address will not be published. Required fields are marked *