- Hands-On Full Stack Development with Go
- Mina Andrawos
- 1162字
- 2021-07-02 12:33:31
Slices
There is a very obvious limitation in Go's array data structure—you must specify the size whenever you declare a new array. In real life, there are numerous scenarios where we will not know the number of elements to expect beforehand. Almost every modern programming language comes with its own data structure to address this requirement. In Go, this special data structure is called a slice.
From a practical point of view, you can think of slices as simply dynamic arrays. From a syntax point of view, slices look very similar to arrays, except that you don't need to specify the size. Here is an example:
var mySlice []int
As you can see, slice declarations are very similar to array declarations, except for the fact that you don't need to specify the number of elements on a slice.
Here is us initializing the preceding slide with some initial values:
mySlice = []int{1,2,3,4,5}
Let's declare and then initialize this with some initial values in one go:
var mySlice = []int{1,2,3,4,5}
Since slices can grow in size, we are also allowed to initialize an empty slice:
var mySlice = []int{}
If you would like to set an initial number of elements in your slice without having to write the initial values by hand, you can utilize a built-in function called make:
var mySlice = make([]int,5)
The preceding code will declare and initialize an int slice with an initial length of 5 elements.
To write efficient Go code that can benefit from slices, you need to first understand how slices work internally.
A slice can simply be considered as a pointer to a part of an array. A slice holds three main pieces of information:
- A pointer to the first element of the subarray that the slice points to.
- The length of the subarray that's exposed to the slice.
- The capacity, which is the remaining number of items available in the original array. The capacity is always either equal to the length or greater.
This sounds too theoretical, so let's utilize the power of code and some visualization to provide a practical explanation about how slices really work.
Let's assume we created a new slice:
var mySlice = []int{1,2,3,4,5}
Internally, the new slice we created points to an array with the 5 initial values that we set:

As you can see from the preceding diagram, mySlice held three pieces of information:
- The first is a pointer to the array underneath, which holds the data
- The second is the length of the slice, which is 5 in this case
- The third is the full capacity of the slice, which is also 5 in this case
The preceding diagram, however, doesn't really clarify how the capacity of the slice can be different from its length. To uncover the practical differences between length and capacity, we'll need to dig a bit deeper.
Let's say we decided to extract a subslice from the original slice:
var subSlice = mySlice[2:4]
Reslicing mySlice will not produce a new, smaller copy of the array underneath. Instead, the preceding line of code will produce the following slice:

Since subSlice includes the elements at index two and index three of mySlice, the length of subSlice is two (remember that an array index starts at zero, which is why index two is the third element and not the second). The capacity is different, however, and that is because the original array has three elements left, starting from index two, so the capacity is three and not two, even though the length is two.
So, in other words, the length of subSlice is two because subSlice only cares about two elements. However, the capacity is three because there are three elements left in the original array, starting from index two, which is the index that the subSlice array pointer points to.
There is a built-in function called cap, which we can use to get the capacity of a slice:
cap(subSlice) //this will return 3
The built-in function called len that we use for arrays works with slices as well, since it will give you the length of the slice:
len(subSlice) //this will return 2
You might be wondering by now, why should I care about the differences between length and capacity? I can just use the length and ignore the capacity altogether, since the capacity only gives you information about a hidden internal array.
The answer is very simple—memory utilization. What if mySlice had 100,000 elements instead of just five? This means that the internal array would have had 100,000 elements as well. This huge internal array will exist in our program's memory as long as we use any sub-slices extracted from mySlice, even if the sub-slices we use only care about two elements.
To avoid that kind of memory bloat, we need to explicitly copy the fewer elements we care about into a new slice. By doing this, once we stop using the original large slice, Go's garbage collector will realize that the huge array is not needed anymore and will clean it up.
So, how do we achieve that? This can be done through a built-in function called copy:
//let's assume this is a huge slice
var myBigSlice = []int{1,2,3,4,5,6}
//now here is a new slice that is smaller
var mySubSlice = make([]int,2)
//we copy two elements from myBigSlice to mySubSlice
copy(mySubSlice,myBigSlice[2:4])
Perfect! With this, you should have a fairly practical understanding about slice internals and how to avoid memory bloats in slices.
We keep saying that slices are like dynamic arrays, but we haven't seen how to actually grow the slice yet. Go offers a simple built-in function called append, which is used to add values to a slice. If you reach the end of your slice capacity, the append function will create a new slice with a bigger internal array to hold your expanding data. append is a variadic function, so it can take any number of arguments. Here is what this looks like:
var mySlice = []int{1,2} //our slice holds 1 and 2
mySlice = append(mySlice,3,4,5) //now our slice holds 1,2,3,4,5
One last important thing to mention is the built-in function called make. We already covered the make function earlier and how it can be used to initialize a slice:
//Initialize a slice with length of 3
var mySlice = make([]int,3)
The argument 3 in the preceding code represents the slice's length. What we haven't mentioned yet though is the fact that make can also be used to specify the capacity of the slice. This can be achieved by using the following code:
//initialize a slice with length of 3 and capacity of 5
var mySlice = make([]int,3,5)
If we don't provide the capacity to the make() function, the length argument value becomes the capacity as well, so in other words, we get the following:
//Initialize a slice with length of 3, and capacity of 3
var mySlice = make([]int,3)
Now, it's time to talk about maps.
- HornetQ Messaging Developer’s Guide
- Dependency Injection in .NET Core 2.0
- Eclipse Plug-in Development:Beginner's Guide(Second Edition)
- Apex Design Patterns
- Python貝葉斯分析(第2版)
- Big Data Analytics
- Hands-On Functional Programming with TypeScript
- 組態軟件技術與應用
- 編程與類型系統
- 移動互聯網軟件開發實驗指導
- Xcode 6 Essentials
- 零基礎學Python編程(少兒趣味版)
- Arduino可穿戴設備開發
- IoT Projects with Bluetooth Low Energy
- Clojure High Performance Programming(Second Edition)