> 백엔드 개발 > Golang > Go의 배열과 슬라이스: 시각적으로 작동하는 '내부' 이해

Go의 배열과 슬라이스: 시각적으로 작동하는 '내부' 이해

Patricia Arquette
풀어 주다: 2024-12-21 18:27:15
원래의
275명이 탐색했습니다.

Arrays vs Slices in Go: Understanding the

여행 기간이 얼마나 될지 모르고 여행을 위해 짐을 싸본 적이 있나요? Go에 데이터를 저장할 때 바로 이런 일이 발생합니다. 때로는 주말 여행을 위해 짐을 꾸릴 때와 같이 얼마나 많은 물건을 보관해야 하는지 정확히 알 수 있습니다. 예를 들어 여행을 위해 짐을 꾸릴 때 "준비되면 돌아올게요"라고 말하는 경우에는 그렇지 않습니다.

간단한 일러스트레이션을 통해 Go 배열과 슬라이스 내부의 세계에 대해 자세히 알아보세요. 다음 사항을 살펴보겠습니다.

  1. 메모리 레이아웃
  2. 성장 메커니즘
  3. 참조 의미
  4. 성능에 미치는 영향

이 글을 끝까지 읽으면 실제 사례와 메모리 다이어그램의 도움을 받아 언제 배열을 사용해야 하는지, 언제 슬라이스를 사용해야 하는지 이해할 수 있을 것입니다

배열: 고정 크기 컨테이너?

배열을 완벽하게 배열된 상자처럼 각 요소가 서로 나란히 배치된 단일 메모리 블록으로 생각하세요.

var number [5]int를 선언하면 Go는 그 이상도 그 이하도 아닌 5개의 정수를 담을 수 있는 충분한 연속 메모리를 예약합니다.

Arrays vs Slices in Go: Understanding the

연속된 고정 메모리가 있으므로 런타임 중에 크기를 조정할 수 없습니다.

func main() {
    // Zero-value initialization
    var nums [3]int    // Creates [0,0,0]

    // Fixed size
    nums[4] = 1       // Runtime panic: index out of range

    // Sized during compilation
    size := 5
    var dynamic [size]int  // Won't compile: non-constant array bound
}
로그인 후 복사
로그인 후 복사
로그인 후 복사

Arrays vs Slices in Go: Understanding the

크기는 배열 유형의 일부입니다. 이는 int와 string이 다른 것처럼 [5]int와 [6]int도 완전히 다른 유형임을 의미합니다.

func main() {
    // Different types!
    var a [5]int
    var b [6]int

    // This won't compile
    a = b // compile error: cannot use b (type [6]int) as type [5]int

    // But this works
    var c [5]int
    a = c // Same types, allowed
}
로그인 후 복사
로그인 후 복사
로그인 후 복사

어레이가 기본적으로 복사되는 이유는 무엇입니까?

Go에서 배열을 할당하거나 전달하면 기본적으로 복사본이 생성됩니다. 이는 데이터 격리를 보장하고 예상치 못한 돌연변이를 방지합니다.

Arrays vs Slices in Go: Understanding the

func modifyArrayCopy(arr [5]int) {
    arr[0] = 999    // Modifies the copy, not original
}

func modifyArray(arr *[5]int){
    arr[0] = 999  // Modifies the original, since reference is passed
}

func main() {
    numbers := [5]int{1, 2, 3, 4, 5}

    modifyArrayCopy(numbers)
    fmt.Println(numbers[0])  // prints 1, not 999

    modifyArray(&numbers)
    fmt.Println(numbers[0])  // prints 999
}
로그인 후 복사
로그인 후 복사
로그인 후 복사

조각

좋아요. var Dynamic [size]int를 사용하여 동적 크기를 설정할 수는 없습니다. 여기서 slice가 중요합니다.

후드 아래의 슬라이스

마법은 빠른 운영을 유지하면서 이러한 유연성을 유지하는 방법에 있습니다.

Go의 모든 슬라이스는 세 가지 중요한 구성 요소로 구성됩니다.

Arrays vs Slices in Go: Understanding the

type slice struct {
    array unsafe.Pointer // Points to the actual data
    len   int           // Current number of elements
    cap   int           // Total available space
}
로그인 후 복사
로그인 후 복사
로그인 후 복사

안전하지 않은 포인터란 무엇입니까??

unsafe.Pointer는 유형 안전성 제약 없이 원시 메모리 주소를 처리하는 Go의 방식입니다. Go의 유형 시스템을 우회하여 직접적인 메모리 조작을 허용하므로 "안전하지 않음"입니다.

Go의 C의 void 포인터와 동일하다고 생각하세요.

그 배열은 무엇인가요?

슬라이스를 생성할 때 Go는 배열과 달리 힙에 백업 배열이라는 연속적인 메모리 블록을 할당합니다. 이제 슬라이스 구조체의 배열은 해당 메모리 블록의 시작을 가리킵니다.

배열 필드는 다음과 같은 이유로 unsafe.Pointer를 사용합니다.

  1. 유형 정보 없이 원시 메모리를 가리켜야 합니다
  2. Go는 각 유형에 대해 별도의 코드를 생성하지 않고도 모든 유형 T에 대한 슬라이스를 구현할 수 있습니다.

슬라이스의 동적 메커니즘

실제 알고리즘에 대한 직관력을 키워 보겠습니다.

Arrays vs Slices in Go: Understanding the

직관을 따르면 두 가지 일을 할 수 있습니다.

  1. 넓은 공간을 확보하여 필요할 때마다 사용할 수 있습니다
    장점: 특정 시점까지 늘어나는 요구사항을 처리합니다.
    단점: 메모리 낭비, 사실상 한계에 도달할 수도 있음

  2. 처음에는 임의의 크기를 설정할 수 있으며 요소가 추가될 때마다 추가할 때마다 메모리를 재할당할 수 있습니다.
    장점: 이전 사례를 처리하고 필요에 따라 확장 가능
    단점: 재할당은 비용이 많이 들고 추가할 때마다 최악의 상황이 될 것입니다

용량이 한계에 도달하면 용량을 늘려야 하므로 재배치를 피할 수 없습니다. 후속 삽입/추가 비용이 일정하도록(O(1)) 재할당을 최소화할 수 있습니다. 이를 상각비용이라고 합니다.

어떻게 하면 되나요?

Go 버전 v1.17까지는 다음 공식이 사용되었습니다.

func main() {
    // Zero-value initialization
    var nums [3]int    // Creates [0,0,0]

    // Fixed size
    nums[4] = 1       // Runtime panic: index out of range

    // Sized during compilation
    size := 5
    var dynamic [size]int  // Won't compile: non-constant array bound
}
로그인 후 복사
로그인 후 복사
로그인 후 복사

Go 버전 v1.18:

func main() {
    // Different types!
    var a [5]int
    var b [6]int

    // This won't compile
    a = b // compile error: cannot use b (type [6]int) as type [5]int

    // But this works
    var c [5]int
    a = c // Same types, allowed
}
로그인 후 복사
로그인 후 복사
로그인 후 복사

큰 슬라이스를 두 배로 늘리는 것은 메모리 낭비이므로 슬라이스 크기가 증가할수록 성장 인자는 감소합니다.

사용법 측면에서 더 잘 이해해 봅시다.

Arrays vs Slices in Go: Understanding the

func modifyArrayCopy(arr [5]int) {
    arr[0] = 999    // Modifies the copy, not original
}

func modifyArray(arr *[5]int){
    arr[0] = 999  // Modifies the original, since reference is passed
}

func main() {
    numbers := [5]int{1, 2, 3, 4, 5}

    modifyArrayCopy(numbers)
    fmt.Println(numbers[0])  // prints 1, not 999

    modifyArray(&numbers)
    fmt.Println(numbers[0])  // prints 999
}
로그인 후 복사
로그인 후 복사
로그인 후 복사

슬라이스에 몇 가지 요소를 추가해 보겠습니다

type slice struct {
    array unsafe.Pointer // Points to the actual data
    len   int           // Current number of elements
    cap   int           // Total available space
}
로그인 후 복사
로그인 후 복사
로그인 후 복사

정원(5명)이 있으므로 > 길이(3), 이동:

기존 백업 어레이 사용
인덱스 3에 10위
길이를 1씩 늘립니다

// Old growth pattern
capacity = oldCapacity * 2  // Simple doubling
로그인 후 복사
로그인 후 복사

한계에 도전해보자

// New growth pattern
if capacity < 256 {
    capacity = capacity * 2
} else {
    capacity = capacity + capacity/4  // 25% growth
}
로그인 후 복사
로그인 후 복사

앗! 이제 우리는 능력에 도달했으니 성장해야 합니다. 일어나는 일은 다음과 같습니다.

  1. 새 용량을 계산합니다(oldCap < 256이므로 두 배인 10)
  2. 새 백업 어레이 할당(새 메모리 주소 300)
  3. 기존 요소를 새 백업 어레이에 복사
  4. 새 요소 추가
  5. 슬라이스 헤더 업데이트

Arrays vs Slices in Go: Understanding the

func main() {
    // Zero-value initialization
    var nums [3]int    // Creates [0,0,0]

    // Fixed size
    nums[4] = 1       // Runtime panic: index out of range

    // Sized during compilation
    size := 5
    var dynamic [size]int  // Won't compile: non-constant array bound
}
로그인 후 복사
로그인 후 복사
로그인 후 복사

큰 조각이면 어떻게 되나요?

func main() {
    // Different types!
    var a [5]int
    var b [6]int

    // This won't compile
    a = b // compile error: cannot use b (type [6]int) as type [5]int

    // But this works
    var c [5]int
    a = c // Same types, allowed
}
로그인 후 복사
로그인 후 복사
로그인 후 복사

용량이 256명이므로 Go는 1.18 이후 성장 공식을 사용합니다.

새 용량 = oldCap oldCap/4
256 256/4 = 256 64 = 320

func modifyArrayCopy(arr [5]int) {
    arr[0] = 999    // Modifies the copy, not original
}

func modifyArray(arr *[5]int){
    arr[0] = 999  // Modifies the original, since reference is passed
}

func main() {
    numbers := [5]int{1, 2, 3, 4, 5}

    modifyArrayCopy(numbers)
    fmt.Println(numbers[0])  // prints 1, not 999

    modifyArray(&numbers)
    fmt.Println(numbers[0])  // prints 999
}
로그인 후 복사
로그인 후 복사
로그인 후 복사

왜 참조 의미론인가?

  1. 성능: 대규모 데이터 구조를 복사하는 데 비용이 많이 듭니다
  2. 메모리 효율성: 불필요한 데이터 중복 방지
  3. 데이터 공유 보기 활성화: 여러 슬라이스가 동일한 백업 어레이를 참조할 수 있습니다.
type slice struct {
    array unsafe.Pointer // Points to the actual data
    len   int           // Current number of elements
    cap   int           // Total available space
}
로그인 후 복사
로그인 후 복사
로그인 후 복사

슬라이스 헤더의 모양은 다음과 같습니다.

// Old growth pattern
capacity = oldCapacity * 2  // Simple doubling
로그인 후 복사
로그인 후 복사

슬라이스 사용 패턴 및 주의사항

우발적인 업데이트

slice는 참조 의미 체계를 사용하므로 주의하지 않을 경우 원본 슬라이스에 대한 우발적인 변형이 발생할 수 있는 복사본을 생성하지 않습니다.

// New growth pattern
if capacity < 256 {
    capacity = capacity * 2
} else {
    capacity = capacity + capacity/4  // 25% growth
}
로그인 후 복사
로그인 후 복사

비용이 많이 드는 추가 작업

numbers := make([]int, 3, 5) // length=3 capacity

// Memory Layout after creation:
Slice Header:
{
    array: 0xc0000b2000    // Example memory address
    len:   3
    cap:   5
}

Backing Array at 0xc0000b2000:
[0|0|0|unused|unused]
로그인 후 복사

복사 vs 추가

numbers = append(numbers, 10)
로그인 후 복사

Arrays vs Slices in Go: Understanding the

명확한 선택 가이드로 마무리하겠습니다.

? 다음과 같은 경우 어레이를 선택하십시오:

  1. 정확한 사이즈는 미리 알아두세요
  2. 작은 고정 데이터(예: 좌표, RGB 값) 작업
  3. 성능이 중요하며 데이터가 스택에 적합합니다
  4. 크기에 따른 유형 안전성을 원합니다

? 슬라이스를 선택하는 경우:

  1. 사이즈는 변경될 수 있습니다
  2. 동적 데이터 작업
  3. 동일한 데이터에 대한 여러 보기가 필요함
  4. 스트림/컬렉션 처리

? notion-to-md 프로젝트를 확인해보세요! Notion 페이지를 Markdown으로 변환하는 도구로, 콘텐츠 제작자와 개발자에게 적합합니다. Discord 커뮤니티에 참여하세요.

위 내용은 Go의 배열과 슬라이스: 시각적으로 작동하는 '내부' 이해의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

원천:dev.to
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
저자별 최신 기사
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿