Home System Tutorial LINUX Tips for working with slice capacity and length in Go

Tips for working with slice capacity and length in Go

Mar 20, 2024 pm 02:36 PM
linux linux tutorial Red Hat linux system linux command linux certification red hat linux linux video

在 Go 中使用切片的容量和长度的技巧

Quick Test - What does the following code output?

vals := make([]int, 5)
for i := 0; i < 5; i {
  vals = append(vals, i)
}
fmt.Println(vals)
Copy after login
Copy after login

If you guessed [0 0 0 0 0 0 1 2 3 4], you are right.

If you make a mistake in the test, you don't have to worry. This is a fairly common mistake when transitioning to the Go language, and in this article we'll explain why the output isn't what you expected, and how to take advantage of Go's nuances to make your code more efficient.

Slice vs Array

There are both arrays and slices in Go. It can be confusing, but once you get used to it, you'll love it. Please trust me.

There are many differences between slices and arrays, but the one we are going to focus on in this article is that the size of an array is part of its type, whereas slices can have dynamic sizes because they are a wrapper around an array.

What does this mean in practice? So let's say we have the array val a[10]int. The array has a fixed size and cannot be changed. If we call len(a), it always returns 10 because this size is part of the type. So if you suddenly need more than 10 items in the array, you have to create a new object of a completely different type, say val b[11]int, and then copy all the values ​​from a to b.

There are specific situations where collection-sized arrays can be valuable, but generally speaking, this is not what developers want. Instead, they wanted to use something similar to arrays in Go, but with the ability to grow over time. A crude way is to create an array much larger than it needs to be, and then treat a subset of the array as an array. The code below is an example.

var vals [20]int
for i := 0; i < 5; i {
  vals[i] = i * i
}
subsetLen := 5

fmt.Println("The subset of our array has a length of:", subsetLen)

//Add a new item to our array
vals[subsetLen] = 123
subsetLen
fmt.Println("The subset of our array has a length of:", subsetLen)
Copy after login

In the code, we have an array of length 20, but since we are only using a subset, in the code we can assume that the length of the array is 5, and then 6 after we add a new item to the array .

This is (very roughly) how slicing works. They contain an array with a set size, like the array in our previous example, which had size 20.

They also keep track of the subset of the array used in the program - this is the append attribute, which is similar to the subsetLen variable in the previous example.

Finally, a slice also has a capacity, which is similar to the total length of our array in the previous example (20). This is useful because it tells the size your subset can grow before it no longer fits the slice array. When this happens, a new array needs to be allocated, but all this logic is hidden behind the append function.

In short, combining slices using the append function gives us a very array-like type, but over time it can handle many more elements.

Let’s look at the previous example again, but this time we’ll use slices instead of arrays.

var vals []int
for i := 0; i < 5; i {
  vals = append(vals, i)
  fmt.Println("The length of our slice is:", len(vals))
  fmt.Println("The capacity of our slice is:", cap(vals))
}

//Add a new item to our array
vals = append(vals, 123)
fmt.Println("The length of our slice is:", len(vals))
fmt.Println("The capacity of our slice is:", cap(vals))

// Accessing items is the same as an array
fmt.Println(vals[5])
fmt.Println(vals[2])
Copy after login

We can still access the elements in our slice just like an array, but by using slices and the append function, we no longer need to consider the size of the array behind it. We can still figure this stuff out by using the len and cap functions, but we don't have to worry about it as much. Simple, right?

Back to testing

With this in mind, let’s review the previous test and see what went wrong.

vals := make([]int, 5)
for i := 0; i < 5; i {
  vals = append(vals, i)
}
fmt.Println(vals)
Copy after login
Copy after login

When calling make, we allow up to 3 parameters to be passed in. The first is the type we allocated, the second is the "length" of the type, and the third is the "capacity" of the type (this parameter is optional).

By passing the argument make([]int, 5), we tell the program that we want to create a slice of length 5, in which case the default capacity is the same as the length - 5 in this case.

While this may look like what we want, the important difference here is that we tell our slice that we want to set the "length" and "capacity" to 5, assuming you want 5 elements in the initial After adding new elements, we then call the append function, which will increase the size of the capacity and add new elements at the end of the slice.

If you add a Println() statement to the code, you can see the capacity change.

vals := make([]int, 5)
fmt.Println("Capacity was:", cap(vals))
for i := 0; i < 5; i {
  vals = append(vals, i)
  fmt.Println("Capacity is now:", cap(vals))
}

fmt.Println(vals)
Copy after login

In the end, we end up with the output of [0 0 0 0 0 0 1 2 3 4] instead of the desired [0 1 2 3 4].

How to fix it? Okay, there are a few ways to do this, we're going to cover two, and you can pick whichever method is most useful in your scenario.

Use index writing directly instead of append

The first fix is ​​to leave the make call unchanged and explicitly set each element using the index. In this way, we get the following code:

vals := make([]int, 5)
for i := 0; i < 5; i {
  vals[i] = i
}
fmt.Println(vals)
Copy after login

In this case, we set the value to exactly the same index we want to use, but you can also track the index independently.

For example, if you want to get the key of the map, you can use the following code.

package main

import "fmt"

func main() {
  fmt.Println(keys(map[string]struct{}{
    "dog": struct{}{},
    "cat": struct{}{},
  }))
}

func keys(m map[string]struct{}) []string {
  ret := make([]string, len(m))
  i := 0
  for key := range m {
    ret[i] = key
    i
  }
  return ret
}
Copy after login

This is good because we know that the length of the slice we return will be the same as the length of the map, so we can initialize our slice with that length and then assign each element to the appropriate index. The disadvantage of this approach is that we have to keep track of i in order to know what value to set for each index.

This brings us to the second method...

Use 0 as your length and specify the capacity

Instead of keeping track of the index of the value we want to add, we can update our make call and provide two arguments after the slice type. First, the length of our new slice will be set to 0 because we haven't added any new elements to the slice yet. Second, the capacity of our new slice will be set to the length of the map parameter, since we know our slice will end up adding many strings.

This will still build the same array behind the scenes as the previous example, but now when we call append it will place them at the beginning of the slice since the length of the slice is 0.

package main

import "fmt"

func main() {
  fmt.Println(keys(map[string]struct{}{
    "dog": struct{}{},
    "cat": struct{}{},
  }))
}

func keys(m map[string]struct{}) []string {
  ret := make([]string, 0, len(m))
  for key := range m {
    ret = append(ret, key)
  }
  return ret
}
Copy after login
If append handles it, why do we have to worry about capacity?

Next you may ask: "If the append function can increase the capacity of the slice for me, then why do we need to tell the program the capacity?"

The truth is, in most cases, you don't have to worry about this too much. If it makes your code more complex, just initialize your slice with var vals []int and let the append function handle the rest.

But this situation is different. It's not an example of the difficulty of declaring capacity, in fact it's easy to determine the final capacity of our slice since we know it will map directly into the provided map. Therefore, when we initialize it, we can declare the capacity of the slice and save our program from performing unnecessary memory allocations.

If you want to see additional memory allocations, run the following code on the Go Playground. Every time the capacity is increased, the program needs to allocate memory.

package main

import "fmt"

func main() {
  fmt.Println(keys(map[string]struct{}{
    "dog": struct{}{},
    "cat": struct{}{},
    "mouse": struct{}{},
    "wolf": struct{}{},
    "alligator": struct{}{},
  }))
}

func keys(m map[string]struct{}) []string {
  var ret[]string
  fmt.Println(cap(ret))
  for key := range m {
    ret = append(ret, key)
    fmt.Println(cap(ret))
  }
  return ret
}
Copy after login

Now compare this to the same code but with a predefined capacity.

package main

import "fmt"

func main() {
  fmt.Println(keys(map[string]struct{}{
    "dog": struct{}{},
    "cat": struct{}{},
    "mouse": struct{}{},
    "wolf": struct{}{},
    "alligator": struct{}{},
  }))
}

func keys(m map[string]struct{}) []string {
  ret := make([]string, 0, len(m))
  fmt.Println(cap(ret))
  for key := range m {
    ret = append(ret, key)
    fmt.Println(cap(ret))
  }
  return ret
}
Copy after login

In the first code example, our capacity started at 0, then increased to 1, 2, 4, and finally 8, which meant that we had to allocate the array 5 times, the last one holding the array we sliced The capacity is 8, which is larger than we ultimately need.

On the other hand, our second example starts and ends with the same capacity (5), it only needs to be allocated once at the beginning of the keys() function. We also avoid wasting any extra memory and return a perfectly sized slice that can fit this array.

Don’t over-optimize

As mentioned before, I usually discourage anyone from doing small optimizations like this, but if the effect of the final size is really noticeable, then I strongly recommend that you try setting an appropriate capacity or length for the slice.

Not only does this help improve the performance of your program, it also helps clarify your code by explicitly stating the relationship between the size of the input and the size of the output.

Summarize

This article is not a detailed discussion of the differences between slices or arrays, but a brief introduction to how capacity and length affect slices, and their use in scenarios.


The above is the detailed content of Tips for working with slice capacity and length in Go. For more information, please follow other related articles on the PHP Chinese website!

Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn

Hot AI Tools

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Undress AI Tool

Undress AI Tool

Undress images for free

Clothoff.io

Clothoff.io

AI clothes remover

Video Face Swap

Video Face Swap

Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Tools

Notepad++7.3.1

Notepad++7.3.1

Easy-to-use and free code editor

SublimeText3 Chinese version

SublimeText3 Chinese version

Chinese version, very easy to use

Zend Studio 13.0.1

Zend Studio 13.0.1

Powerful PHP integrated development environment

Dreamweaver CS6

Dreamweaver CS6

Visual web development tools

SublimeText3 Mac version

SublimeText3 Mac version

God-level code editing software (SublimeText3)

What computer configuration is required for vscode What computer configuration is required for vscode Apr 15, 2025 pm 09:48 PM

VS Code system requirements: Operating system: Windows 10 and above, macOS 10.12 and above, Linux distribution processor: minimum 1.6 GHz, recommended 2.0 GHz and above memory: minimum 512 MB, recommended 4 GB and above storage space: minimum 250 MB, recommended 1 GB and above other requirements: stable network connection, Xorg/Wayland (Linux)

Linux Architecture: Unveiling the 5 Basic Components Linux Architecture: Unveiling the 5 Basic Components Apr 20, 2025 am 12:04 AM

The five basic components of the Linux system are: 1. Kernel, 2. System library, 3. System utilities, 4. Graphical user interface, 5. Applications. The kernel manages hardware resources, the system library provides precompiled functions, system utilities are used for system management, the GUI provides visual interaction, and applications use these components to implement functions.

How to run java code in notepad How to run java code in notepad Apr 16, 2025 pm 07:39 PM

Although Notepad cannot run Java code directly, it can be achieved by using other tools: using the command line compiler (javac) to generate a bytecode file (filename.class). Use the Java interpreter (java) to interpret bytecode, execute the code, and output the result.

vscode terminal usage tutorial vscode terminal usage tutorial Apr 15, 2025 pm 10:09 PM

vscode built-in terminal is a development tool that allows running commands and scripts within the editor to simplify the development process. How to use vscode terminal: Open the terminal with the shortcut key (Ctrl/Cmd). Enter a command or run the script. Use hotkeys (such as Ctrl L to clear the terminal). Change the working directory (such as the cd command). Advanced features include debug mode, automatic code snippet completion, and interactive command history.

How to check the warehouse address of git How to check the warehouse address of git Apr 17, 2025 pm 01:54 PM

To view the Git repository address, perform the following steps: 1. Open the command line and navigate to the repository directory; 2. Run the "git remote -v" command; 3. View the repository name in the output and its corresponding address.

Where to write code in vscode Where to write code in vscode Apr 15, 2025 pm 09:54 PM

Writing code in Visual Studio Code (VSCode) is simple and easy to use. Just install VSCode, create a project, select a language, create a file, write code, save and run it. The advantages of VSCode include cross-platform, free and open source, powerful features, rich extensions, and lightweight and fast.

What is the main purpose of Linux? What is the main purpose of Linux? Apr 16, 2025 am 12:19 AM

The main uses of Linux include: 1. Server operating system, 2. Embedded system, 3. Desktop operating system, 4. Development and testing environment. Linux excels in these areas, providing stability, security and efficient development tools.

vscode terminal command cannot be used vscode terminal command cannot be used Apr 15, 2025 pm 10:03 PM

Causes and solutions for the VS Code terminal commands not available: The necessary tools are not installed (Windows: WSL; macOS: Xcode command line tools) Path configuration is wrong (add executable files to PATH environment variables) Permission issues (run VS Code as administrator) Firewall or proxy restrictions (check settings, unrestrictions) Terminal settings are incorrect (enable use of external terminals) VS Code installation is corrupt (reinstall or update) Terminal configuration is incompatible (try different terminal types or commands) Specific environment variables are missing (set necessary environment variables)

See all articles