[Go] 배열과 슬라이스

Jiwoo Kim·2021년 11월 1일
0

Go

목록 보기
3/11
post-thumbnail

배열과 슬라이스

  • 슬라이스는 배열을 바탕으로 좀 더 유연한 처리를 할 수 있는 자료구조로, 자바의 ArrayList와 비슷하다.
  • Go에서는 굳이 배열을 쓰기 보다는 슬라이스를 주로 활용한다.
arr1 := [...]int{1, 2, 3}	// 배열
arr2 := [3]int{1, 2, 3}		// 배열
slice1 := []int{1, 2, 3}		// 슬라이스
slice2 := make([]int, len(arr1))	// 슬라이스

배열

There are major differences between the ways arrays work in Go and C. In Go,

  • Arrays are values. Assigning one array to another copies all the elements.
  • In particular, if you pass an array to a function, it will receive a copy of the array, not a pointer to it.
  • The size of an array is part of its type. The types [10]int and [20]int are distinct.

슬라이스

  • 슬라이스는 배열 포인터, 길이, 용량 세 필드로 이루어진 구조체다.
  • 슬라이스는 크기가 cap인 배열을 가리키고 있고, 복사를 위한 배열 이동이 일어나면 슬라이스는 다른 배열을 가리키게 된다.
  • if you assign one slice to another, both refer to the same array.

append

nums := make([]int, 3, 5)
nums = append(nums, 1)
  • 용량을 초과하지 않는 경우, 값을 추가하고 길이를 증가시켜 이 슬라이스를 다시 할당한다.
  • 용량을 초과하는 경우, 더 큰 배열을 만들어 값을 채우고 반환하며, 이 슬라이스를 다시 할당한다.

copy

src := []int{1, 2, 3}
dst := src
  • dstsrc 슬라이스 자체의 주소값만 복사된다.
  • src가 변경되면 dst도 함께 변경된다.
src := []int{1, 2, 3}
if n := copy(dest, src); n != len(src) {
    fmt.Println("복사가 덜 됐습니다.")
}
  • len(src)len(dst) 중 더 작은 값만큼만 복사된다.
  • 아래와 같이 해야 슬라이스 전체 복사를 수행한다.
// 1번
src := []int{1, 2, 3}
dst := make([]int, len(src))
copy(dst, src)
// 2번
src := []int{1, 2, 3}
var dst []int
dst := append(dst, src...)

insert

func Example_sliceInsert1() {
	a := []int{0, 1, 2, 3, 4}
	var res []int
	n := 2
	if n <= len(a) {
		res = append(a[:n+1], a[n:]...)
		res[n] = 100
	} else {
		res = append(a, 100)
	}
	fmt.Println(res)

	// Output:
	// [0 1 100 2 3 4]
}

func Example_sliceInsert2() {
	a := []int{0, 1, 2, 3, 4}
	n := 2

	a = append(a, 1)
	copy(a[n+1:], a[n:])
	a[n] = 100

	fmt.Println(a)

	// Output:
	// [0 1 100 2 3 4]
}

delete

순서가 변하면 안 되는 경우 O(N)

func Example_sliceDelete1() {
	a := []int{0, 1, 2, 3, 4}
	n := 2
	a = append(a[:n], a[n+1:]...)
	fmt.Println(a)

	// Output:
	// [0 1 3 4]
}

func Example_sliceDelete2() {
	a := []int{0, 1, 2, 3, 4}
	n := 2
	k := 2	// 연속된 k개의 원소 삭제
	a = append(a[:n], a[n+k:]...)
	fmt.Println(a)

	// Output:
	// [0 1 4]
}

순서가 변해도 되는 경우 O(1)

func Example_sliceDelete3() {
	a := []int{0, 1, 2, 3, 4}
	n := 2
	a[n] = a[len(a)-1]
	a = a[:len(a)-1]
	fmt.Println(a)

	// Output:
	// [0 1 4 3]
}

func Example_sliceDelete4() {	// 이 경우 O(N)
	a := []int{0, 1, 2, 3, 4}
	n := 1
	k := 4
	split := len(a)-k
	if n+k > split {
		split = n+k
	}
	copy(a[n:n+k], a[split:])
	a = a[:len(a)-k]
	fmt.Println(a)

	// Output:
	// [0]
}

삭제 시 메모리 누수

삭제 대상 슬라이스 내부에 포인터가 있는 경우 메모리 누수가 발생한다.

슬라이스를 잘라내도 사용하는 배열(메모리)은 그대로라 GC 대상에서 제외된다. 이 때 배열 원소가 포인터인 경우 그 포인터가 가리키는 객체도 유효하다고 판단되어 메모리 공간을 차지하고 GC되지 않기 때문에 누수가 발생한다.

따라서 아래와 같이 nil로 지워줘야 한다.

copy(a[i:], a[i+k:])
for i := 0; i < k; i++ {
    a[len(a)-1-i] = nil
}
a = a[:len(a)-k]

0개의 댓글