Slice in Go로 작업할 때 주의하세요.

26308 단어 programminggo
Go에는 arrayslice 데이터 구조가 있습니다. 둘 다 동일한 유형의 시퀀스 요소를 저장하는 데 사용됩니다. 차이점은 배열은 길이가 고정되어 있고 슬라이스는 길이가 가변적이라는 것입니다.

어레이와 슬라이스 모두 길이와 용량이 있습니다. 길이는 len, 용량은 cap 키워드로 확인할 수 있습니다.

배열에서는 길이가 고정되어 있으므로 새 요소를 추가(길이 변경)할 수 없습니다. 반면 최대 슬라이스 용량에서도 슬라이스에 요소를 추가할 수 있습니다.

package main

import "fmt"

func main() {
    length := 0
    capacity := 3
    x := make([]int, length, capacity)

    // slice: [], length: 0, capacity: 3
    fmt.Printf("slice: %v, length: %d, capacity: %d\n", x, len(x), cap(x))
}


위의 코드에서 길이가 0이고 용량이 3인 슬라이스를 만듭니다. 세 개의 요소를 추가하고 길이와 용량을 살펴보겠습니다.

package main

import "fmt"

func main() {
    length := 0
    capacity := 3
    x := make([]int, length, capacity)

    // slice: [], length: 0, capacity: 3
    fmt.Printf("slice: %v, length: %d, capacity: %d\n", x, len(x), cap(x))

    x = append(x, 1, 2, 3)

    // slice: [1 2 3], length: 3, capacity: 3
    fmt.Printf("slice: %v, length: %d, capacity: %d\n", x, len(x), cap(x))
}


여태까지는 그런대로 잘됐다. 이제 x에 새 요소를 추가하고 어떻게 되는지 살펴보겠습니다.

package main

import "fmt"

func main() {
    length := 0
    capacity := 3
    x := make([]int, length, capacity)

    // slice: [], length: 0, capacity: 3
    fmt.Printf("slice: %v, length: %d, capacity: %d\n", x, len(x), cap(x))

    x = append(x, 1, 2, 3)

    // slice: [1 2 3], length: 3, capacity: 3
    fmt.Printf("slice: %v, length: %d, capacity: %d\n", x, len(x), cap(x))

    x = append(x, 4)

    // slice: [1 2 3 4], length: 4, capacity: 6
    fmt.Printf("slice: %v, length: %d, capacity: %d\n", x, len(x), cap(x))
}


보시다시피 길이는 4가 되고 용량은 6이 됩니다. 슬라이스가 최대 용량에 도달했을 때 새 요소를 추가하면 용량이 두 배가 됩니다.

그러나 더 흥미로운 점은 슬라이스가 용량을 초과하면 슬라이스가 새 메모리를 할당한다는 것입니다. 따라서 메모리에는 용량이 3인 슬라이스[1 2 3]와 용량이 6인 슬라이스[1 2 3 4]가 있습니다. 변수x는 새 어레이를 가리킵니다.

이것은 우리가 할 때 메모리에서 일어나는 일입니다x = append(x, 1, 2, 3).



속성 길이와 용량 값이 각각 3과 3인 변수x가 있습니다. 새 요소( x = append(x, 4) )를 추가하면 Go는 요소의 새 시퀀스를 만들고 이전 배열의 모든 값을 복사합니다.



이전 배열이 더 이상 참조되지 않으며 나중에 Go Garbage Collector에서 해제됨을 알 수 있습니다.

슬라이스가 Go에서 작동하는 방식을 이해했습니다. 아래 문제를 봅시다.

package main

import "fmt"

func main() {
    x := make([]int, 3, 5)

    x = append(x, 1)
    y := append(x, 2)
    z := append(x, 3)

    fmt.Printf("slice x: %v, length: %d, capacity: %d\n", x, len(x), cap(x))
    fmt.Printf("slice y: %v, length: %d, capacity: %d\n", y, len(y), cap(y))
    fmt.Printf("slice z: %v, length: %d, capacity: %d\n", z, len(z), cap(z))
}


당신은 출력을 추측할 수 있습니까?

코드를 실행하면 다음과 같은 결과를 얻을 수 있습니다.

# The first three element is initialized with 0 because we set the slice to have length 3.
slice x: [0 0 0 1], length: 4, capacity: 5
slice y: [0 0 0 1 3], length: 5, capacity: 5
slice z: [0 0 0 1 3], length: 5, capacity: 5

y[0 0 0 1 2]와 같지 않아야 합니까?
이런 식으로 생각하고 있다면 실제로 무슨 일이 일어나고 있는지 이해하기 위해 메모리 표현을 단계별로 살펴보겠습니다.

package main

import "fmt"

func main() {
    x := make([]int, 3, 5)

    x = append(x, 1)

    fmt.Printf("slice x: %v, length: %d, capacity: %d\n", x, len(x), cap(x))
}


이때 메모리 표현은 아래와 같습니다.



그런 다음 x에 2를 추가하고 변수y에 할당합니다.


xy가 동일한 배열을 가리키는 것을 볼 수 있습니다. 차이점은 y의 길이가 5이고 x의 길이가 4라는 것입니다. 현재 배열이 용량을 초과하지 않았기 때문에 Go는 새 배열을 만들지 않습니다.

다음으로 x에 3을 추가하고 z에 할당합니다.


x를 추가하기 전에 x의 길이는 4이고 용량은 5입니다. 따라서 z := append(x, 3)를 수행할 때 Go는 2를 3으로 덮어씁니다. 아직 초과했습니다. 따라서 새 어레이가 할당되지 않습니다. 따라서 xzx와 동일한 헤더를 가리킵니다.

이를 증명하려면 이 코드를 실행해 보십시오.

package main

import "fmt"

func main() {
    x := make([]int, 3, 5)

    x = append(x, 1)
    y := append(x, 2)
    z := append(x, 3)

    fmt.Printf("slice x: %v, length: %d, capacity: %d\n", x, len(x), cap(x))
    fmt.Printf("slice y: %v, length: %d, capacity: %d\n", y, len(y), cap(y))
    fmt.Printf("slice z: %v, length: %d, capacity: %d\n", z, len(z), cap(z))

    // change the value of third element of slice x
    x[2] = 100
    fmt.Printf("slice x: %v, length: %d, capacity: %d\n", x, len(x), cap(x))
    fmt.Printf("slice y: %v, length: %d, capacity: %d\n", y, len(y), cap(y))
    fmt.Printf("slice z: %v, length: %d, capacity: %d\n", z, len(z), cap(z))
}

y , xy 의 세 번째 요소가 100인 것을 볼 수 있습니다. 이것은 이 세 포인터가 배열의 동일한 헤더를 가리키고 있음을 증명합니다.

slice: [0 0 100 1], length: 4, capacity: 5
slice: [0 0 100 1 3], length: 5, capacity: 5
slice: [0 0 100 1 3], length: 5, capacity: 5


이제 아래와 같이 새 요소를 추가하면z 어떻게 됩니까?

package main

import "fmt"

func main() {
    x := make([]int, 3, 5)

    x = append(x, 1)
    y := append(x, 2)
    z := append(x, 3)
    w := append(z, 4)

    fmt.Printf("slice x: %v, length: %d, capacity: %d\n", x, len(x), cap(x))
    fmt.Printf("slice y: %v, length: %d, capacity: %d\n", y, len(y), cap(y))
    fmt.Printf("slice z: %v, length: %d, capacity: %d\n", z, len(z), cap(z))
    fmt.Printf("slice w: %v, length: %d, capacity: %d\n", w, len(w), cap(w))
}


이전에 논의한 것처럼 z가 최대 용량에 도달했기 때문에 새 요소를 추가하면 Go가 새 배열을 만들고 z가 해당 배열을 가리킵니다. w의 용량은 w보다 두 배 더 큽니다. 아래는 출력입니다.

slice x: [0 0 0 1], length: 4, capacity: 5
slice y: [0 0 0 1 3], length: 5, capacity: 5
slice z: [0 0 0 1 3], length: 5, capacity: 5
slice w: [0 0 0 1 3 4], length: 6, capacity: 10



z가 다른 배열을 가리키고 있음을 증명하기 위해 세 번째 요소의 값을 다시 변경해 보겠습니다.

package main

import "fmt"

func main() {
    x := make([]int, 3, 5)

    x = append(x, 1)
    y := append(x, 2)
    z := append(x, 3)
    w := append(z, 4)

    x[2] = 100

    fmt.Printf("slice: %v, length: %d, capacity: %d\n", x, len(x), cap(x))
    fmt.Printf("slice: %v, length: %d, capacity: %d\n", y, len(y), cap(y))
    fmt.Printf("slice: %v, length: %d, capacity: %d\n", z, len(z), cap(z))
    fmt.Printf("slice: %v, length: %d, capacity: %d\n", w, len(w), cap(w))
}


아래는 출력입니다.

slice: [0 0 100 1], length: 4, capacity: 5
slice: [0 0 100 1 3], length: 5, capacity: 5
slice: [0 0 100 1 3], length: 5, capacity: 5
slice: [0 0 0 1 3 4], length: 6, capacity: 10

w의 세 번째 요소는 여전히 100이 아니라 1임을 알 수 있습니다. 이것은 w가 다른 배열을 가리키고 있음을 증명합니다.

따라서 다음에는 Go 슬라이스로 작업할 때 주의하십시오.

좋은 웹페이지 즐겨찾기