자료구조 입문 (배열)
문서 업데이트: 2024-08-27
목차
공부하며 채워가는 포스트입니다. 이해한 내용이 추가됩니다.
자료구조의 목표
자료구조는 한정된 공간인 ‘메모리’에 데이터를 효율적으로 적재하는 것을 목표로 합니다.
자료구조에는 데이터를 삽입, 조회, 삭제, 변경(제한된 경우 삭제후 삽입)할 수 있습니다.니다.
자료구조를 구조 기준으로 나누면 다음과 같이 나눠집니다.
- 선형(Linear)
- 비선형(Non-Linear)
이번 포스트에서는 대표적인 선형 자료구조인 배열에 대해 살펴봅니다.
배열(Array)
고정된 크기를 가지는 자료구조로, 인덱스 값(메모리 주소)를 통해 저장된 값을 찾습니다.
배열의 생성
자바 코드로 배열을 선언, 할당하는 방법은 아래와 같습니다.
// int[] arrayDeclare;
// arrayDeclare[0] = 1; // java: variable arrayDeclare might not have been initialized
int arrayLength = 5;
int[] arrayInitWithLength = new int[arrayLength]; // Contents of array 'arrayInitWithLength' are read, but never written to
int[] arrayInit = {0, 1, 2, 3, 4};
배열 타입의 변수를 선언 후 값을 할당하려 하면, 컴파일 에러가 발생합니다.
왜 배열은 초기화할 때 그 크기(길이)를 정해줘야할까요
배열은 초기화할 때 그 크기(length)를 명시한다는 점이 특징이라 생각합니다.
이것이 특징이 이유는 배열의 구조와 관련 있습니다.
배열은 하나로 이어진 덩어리입니다.
이것은 메모리에 배열 크기의 연속된 공간이 있어야 적재가 가능함을 뜻합니다.
예를 들어 메모리의 크기가 10 이고, 저장하고자 하는 배열의 크기가 5 일 때,
다음의 두 예시를 살펴보겠습니다.
- 메모리에 남은 공간의 크기가 5 이고, 이 공간이 연속할 때,
메모리에 배열을 적재할 수 있습니다.
- 메모리에 남은 공간의 크기가 5 이고, 이 공간이 연속하지 않을 때,
메모리에 적재할 수 없습니다. 이유는 배열의 구조가 하나로 이어진 덩어리로, 나눌 수 없기 때문입니다.
배열의 인덱스와 배열 요소 조회
인덱스는 배열이 가지는 하나의 값을 가리키는 무언가입니다.
배열의 인덱스는 [0]
부터 시작해 [배열의 크기 - 1]
만큼 존재합니다.
아래의 예시를 보며 설명을 이어보겠습니다.
배열의 크기 = 5 이고,
배열 요소의 크기 = 1 이라고 가정하겠습니다.
배열을 메모리에 적재 후, array[0]
의 형태로 접근하면, 배열이 시작하는 메모리 주소를 가리키게 됩니다.
이후 이어지는 인덱스는 배열 요소의 크기 만큼 메모리 공간을 건너 뛰어 메모리 주소를 가리킵니다. (위 예시는 메모리 한 칸씩)
배열의 크기 = 2 이고,
배열 요소의 크기 = 2 라고 가정하면,
배열 요소 ‘사’와 ‘자’ 를 저장한다면 다음과 같이 참조하게 됩니다.
이렇게 배열의 인덱스를 이용해, 배열이 저장된 메모리 주소를 찾고, 인덱스의 증가를 통해 연속된 배열 요소의 값을 조회할 수 있습니다.
연속된 구조와 인덱스를 통해 순서가 정해져있다는 것은 배열의 또다른 특징입니다.
ㄴ>순서가 보장됨
배열 요소에 값 할당 (수정)
앞서 살펴봤듯, 배열은 인덱스를 통해 배열 요소의 값이 저장된 메모리 주소를 가리킬 수 있습니다.
이 특성을 이용해 인덱스로 배열 요소를 참조하고, 해당 메모리 주소에 값을 할당하면 해당 주소에 새로운 값이 할당됩니다.
이것은 특정 메모리 위치에 있는 값을 수정하는 것으로 볼 수도 있겠습니다.
번외: JVM 과 배열의 초기화
앞서 작성한 코드에서 arrayInitWithLength
와 arrayInit
내부의 값을 출력해봅니다.
// prints `0 0 0 0 0`
Arrays.stream(arrayInitWithLength).forEach(each -> System.out.printf("%d ", each));
// new line
System.out.println();
// prints `0 1 2 3 4`
Arrays.stream(arrayInit).forEach(each -> System.out.printf("%d ", each));
출력하면 배열의 크기만 할당한 경우(arrayInitWithLength
) 모든 값이 0으로 초기화된 것을 확인할 수 있습니다.
기본적으로 모든 인덱스에 값이 할당 되어있는 것을 알 수 있습니다.
만약 객체의 배열이라면 어떻게 될까요?
String[] objectArray = new String[arrayLength];
// prints `null null null null null`
Arrays.stream(objectArray).forEach(each -> System.out.printf("%s ", each));
배열 모든 인덱스가 가리키는 값이 null 로 초기화된 것을 확인할 수 있습니다.
boolean[]
의 경우false
로 초기화됩니다.
이렇게 초기화가 되는 이유는 JVM이 메모리의 특정 위치에 값들을 저장할 때, 해당 위치에 이전에 사용한 값이 있다면 그 값을 사용하지 않도록 관리하기 때문입니다.
배열 중 특정 인덱스의 값이 기본값이라면 개발자가 따로 값을 할당하지 않았거나, 기본값으로 할당한 것임을 알 수 있게 도와줍니다.
배열 요소의 값 삭제?
배열 요소에 값을 할당 했으니 삭제도 가능할까 궁금합니다.
초기화할 때 고정된 길이를 갖고, 메모리에 연속된 공간을 차지하는 배열의 특성으로 메모리상에서 값을 삭제한다는 것은 불가합니다.
삭제라는 개념보다는, 이용하지 않음을 나타내는 값을 할당 하는 방식으로 사용하게 됩니다.
배열의 한계
앞서 살펴본 배열의 특징과 원리를 통해 배열의 가지는 한계를 몇 가지 알 수 있습니다.
- 값으로 가득찬 배열에 추가 데이터를 더 넣고 싶은 경우에는? -> 고정된 길이로 생성 되었기에 크기를 키운 새 배열의 선언이 필요합니다.
- 특정 길이의 배열을 저장하려는데, 메모리에 연속된 공간을 찾을 수 없다면? -> 메모리에 적재된 데이터의 주소를 정리가 필요합니다.
- 배열 중 어딘가에 특정 값을 저장한 상태, 그 값이 어딨는지 찾고 싶다면? -> 배열을 순회하여 하나씩 값을 확인해야 됩니다.
요약
- 배열은 연속된 선형 자료구조.
- 배열이 필요한 크기만큼 연속된 메모리 공간에 할당.
- 배열을 사용하려면 배열의 길이를 먼저 설정해야함.
- 인덱스를 통해 개별 요소에 접근 및 값을 할당.
- JVM 에서는 배열이 저장될 메모리 주소의 이전 값이 그대로 배열 요소에 할당되는 것을 막고자 자료형 별 기본값으로 초기화.
- 한번 설정한 배열의 크기를 줄이거나 늘일 수 없으며, 필요하면 적정 크기로 새로운 배열을 생성해야 함.
다음으로
다음에는 배열이 갖는 한계를 극복할 수 있는 자료구조에 대해 공부해 포스팅 하겠습니다.