💻 프로그래밍

Array

배열

동일 타입 요소들의 연속적 저장 구조. O(1) 인덱스 접근. 기본 자료구조.

📖 상세 설명

배열(Array)은 동일한 데이터 타입의 요소들을 연속된 메모리 공간에 저장하는 가장 기본적인 자료구조입니다. 각 요소는 0부터 시작하는 인덱스로 접근할 수 있으며, 메모리 주소 계산을 통해 O(1) 시간 복잡도로 임의 접근(Random Access)이 가능합니다.

배열의 핵심 특징은 연속적인 메모리 할당입니다. 이로 인해 CPU 캐시 히트율이 높아져 순차 접근 시 뛰어난 성능을 발휘합니다. 반면, 크기가 고정되어 있어 중간 삽입/삭제 시 O(n) 시간이 소요되고, 동적 크기 조정이 필요하면 새로운 배열을 할당해야 하는 단점이 있습니다.

정적 배열과 동적 배열로 구분됩니다. C/C++의 int arr[10]은 정적 배열로 컴파일 시점에 크기가 결정되고, Python의 list나 JavaScript의 Array는 동적 배열로 런타임에 크기가 자동 조정됩니다. 동적 배열은 내부적으로 용량이 부족하면 보통 2배로 확장하는 amortized O(1) 삽입을 구현합니다.

실무에서 배열은 모든 프로그래밍의 기초입니다. 데이터베이스 쿼리 결과 저장, API 응답의 리스트 데이터, 이미지의 픽셀 값, 머신러닝의 텐서 연산까지 배열 없이는 불가능합니다. 특히 AI/ML에서 NumPy 배열은 벡터화 연산으로 순수 Python 대비 100배 이상 빠른 수치 계산을 가능하게 합니다.

💻 코드 예제

// 1. 배열 생성 방법
const arr1 = [1, 2, 3, 4, 5];              // 리터럴 방식 (권장)
const arr2 = new Array(5);                  // 길이 5인 빈 배열
const arr3 = Array.from({length: 5}, (_, i) => i * 2); // [0, 2, 4, 6, 8]
const arr4 = [...Array(5).keys()];          // [0, 1, 2, 3, 4]

// 2. 배열 조작 (CRUD)
const fruits = ['apple', 'banana', 'cherry'];

// 추가
fruits.push('date');           // 끝에 추가: ['apple', 'banana', 'cherry', 'date']
fruits.unshift('avocado');     // 앞에 추가: ['avocado', 'apple', ...]
fruits.splice(2, 0, 'blueberry'); // 인덱스 2에 삽입

// 삭제
fruits.pop();                  // 마지막 요소 제거
fruits.shift();                // 첫 요소 제거
fruits.splice(1, 1);           // 인덱스 1의 요소 1개 제거

// 수정
fruits[0] = 'apricot';         // 인덱스로 직접 수정

// 3. 배열 순회 (Iteration)
const numbers = [1, 2, 3, 4, 5];

// for...of (값 순회, 권장)
for (const num of numbers) {
    console.log(num);
}

// forEach (콜백 함수)
numbers.forEach((num, index) => {
    console.log(`인덱스 ${index}: ${num}`);
});

// map (새 배열 반환)
const doubled = numbers.map(n => n * 2);  // [2, 4, 6, 8, 10]

// filter (조건 필터링)
const evens = numbers.filter(n => n % 2 === 0);  // [2, 4]

// reduce (누적 계산)
const sum = numbers.reduce((acc, n) => acc + n, 0);  // 15

// 4. 실무 패턴: 배열 검색과 정렬
const users = [
    { id: 1, name: 'Alice', age: 25 },
    { id: 2, name: 'Bob', age: 30 },
    { id: 3, name: 'Charlie', age: 35 }
];

// 찾기
const bob = users.find(u => u.name === 'Bob');      // { id: 2, ... }
const index = users.findIndex(u => u.id === 3);     // 2
const hasAdult = users.some(u => u.age >= 18);      // true
const allAdults = users.every(u => u.age >= 18);    // true

// 정렬 (원본 배열 변경 주의!)
const sorted = [...users].sort((a, b) => a.age - b.age);  // 나이순
const desc = [...users].sort((a, b) => b.age - a.age);    // 내림차순

🗣️ 실무에서 이렇게 말하세요

💬 회의에서
"이 API 응답이 최대 10만 건까지 올 수 있는데, 전체를 배열로 메모리에 올리면 문제가 될 수 있어요. 페이지네이션을 적용하거나 스트리밍 방식으로 처리하는 게 좋겠습니다."
💬 면접에서
"배열은 인덱스 접근이 O(1)이고 연결 리스트는 O(n)입니다. 하지만 중간 삽입/삭제는 배열이 O(n)이고 연결 리스트가 O(1)이죠. 읽기가 많으면 배열, 삽입/삭제가 많으면 연결 리스트를 선택합니다. JavaScript의 Array는 내부적으로 해시맵 기반이라 희소 배열도 효율적으로 처리합니다."
💬 코드 리뷰에서
"여기서 for 루프로 배열을 순회하면서 splice로 요소를 삭제하고 있는데, 인덱스가 꼬일 수 있어요. filter로 새 배열을 만들거나, 역순으로 순회하면서 삭제하는 방식으로 바꿔주세요."

⚠️ 흔한 실수 & 주의사항

sort()는 원본 배열을 변경합니다

JavaScript의 sort(), reverse(), splice() 등은 원본 배열을 직접 수정합니다. 원본 유지가 필요하면 [...arr].sort()나 arr.slice().sort()로 복사본을 만들어 정렬하세요.

빈 배열 체크 시 length 사용하기

if(arr)는 빈 배열도 true입니다. 빈 배열 체크는 반드시 if(arr.length === 0) 또는 if(!arr.length)를 사용하세요. arr?.length로 null/undefined까지 안전하게 체크할 수 있습니다.

적절한 메서드 선택으로 성능 최적화

단순 순회는 for...of, 변환은 map, 필터링은 filter, 누적 계산은 reduce를 사용하세요. 조기 종료가 필요하면 find나 some을 활용하면 전체 순회 없이 결과를 얻을 수 있습니다.

🔗 관련 용어

📚 더 배우기