array는 고정된 크기를 갖는다. 반면 slice는 내부적으로 len, cap, 그리고 array에 대한 포인터로 구성된 구조체로, 편리한 사용을 위한 array 래퍼 구조체로 보면 좋다. Java의 Vector와 매우 유사하다.slice는 append 함수로 아이템을 추가하는데, len과 cap이 같아지면, cap을 2배로 키우고, 새로운 array를 만든다.package main
import (
"fmt"
)
func main() {
slice := make([]int, 0)
for i := 0; i < 10; i++ {
slice = append(slice, i)
fmt.Printf("pointer: %p, cap: %d\n", slice, cap(slice))
}
}
예를 들어, 위의 코드는 아래와 같은 결과를 준다.
pointer: 0xc0000b4020, cap: 1
pointer: 0xc0000b4040, cap: 2 // New array object
pointer: 0xc0000be020, cap: 4 // New array object
pointer: 0xc0000be020, cap: 4
pointer: 0xc0000c0000, cap: 8 // New array object
pointer: 0xc0000c0000, cap: 8
pointer: 0xc0000c0000, cap: 8
pointer: 0xc0000c0000, cap: 8
pointer: 0xc0000b2080, cap: 16 // New array object
pointer: 0xc0000b2080, cap: 16
그러므로, 미리 크기를 알고 있는 경우, array로 선언하는게 slice로 해서 append 함수를 쓰는 것 보다 성능 상 이점이 있다고 할 수 있다.
아니면, cap 값을 미리 지정해서 slice를 생성하면, 당연하겠지만, 새로운 array를 만들지 않는다. 그러니 전체 길이를 아는 배열을 만든다면, cap 값을 미리 지정해주는 것이 좋다.
package main
import (
"fmt"
)
func main() {
slice := make([]int, 0, 10)
for i := 0; i < 10; i++ {
slice = append(slice, i)
fmt.Printf("pointer: %p, cap: %d\n", slice, cap(slice))
}
}
pointer: 0xc00009e000, cap: 10
pointer: 0xc00009e000, cap: 10
pointer: 0xc00009e000, cap: 10
pointer: 0xc00009e000, cap: 10
pointer: 0xc00009e000, cap: 10
pointer: 0xc00009e000, cap: 10
pointer: 0xc00009e000, cap: 10
pointer: 0xc00009e000, cap: 10
pointer: 0xc00009e000, cap: 10
pointer: 0xc00009e000, cap: 10 // Same array object
slice가 cap을 키우며 새로운 array 객체를 만들때는 shallow-copy를 수행한다. 즉, 객체를 통째로 복사하지 않고 포인터 값만 가져온다. immutability를 고려한다면 주의하길.
slice를 만들 때, len 값을 0보다 큰 값을 넣으면, 일단 Zero value로 넣으며, 그 상태에서 append를 쓰면 당연히 그 이후의 index 값부터 채워넣는다.
package main
import (
"fmt"
)
func main() {
slice := make([]int, 5)
for i := 0; i < 5; i++ {
slice = append(slice, i)
fmt.Printf("%v\n", slice)
}
}
[0 0 0 0 0 0]
[0 0 0 0 0 0 1]
[0 0 0 0 0 0 1 2]
[0 0 0 0 0 0 1 2 3]
[0 0 0 0 0 0 1 2 3 4]