Java 컬렉션 최적화 기법

 

Java 애플리케이션의 성능을 최적화하는 데 있어 컬렉션 프레임워크의 효율적인 사용은 매우 중요합니다.

대용량 데이터를 처리할 때 적절한 컬렉션 선택과 최적화 기법은 애플리케이션의 성능을 크게 향상시킬 수 있습니다.

적절한 컬렉션 타입 선택

컬렉션 타입 최적 사용 사례 성능 특성
ArrayList 빈번한 인덱스 접근 필요 시 접근: O(1), 삽입/삭제: O(n)
LinkedList 빈번한 삽입/삭제 필요 시 삽입/삭제: O(1), 접근: O(n)
HashMap 키 기반 빠른 검색 필요 시 검색/삽입/삭제: 평균 O(1)
HashSet 중복 없는 고유 요소 저장 시 검색/삽입/삭제: 평균 O(1)
TreeMap 정렬된 키-값 쌍 필요 시 검색/삽입/삭제: O(log n)
TreeSet 정렬된 고유 요소 필요 시 검색/삽입/삭제: O(log n)

초기 용량 설정 최적화

컬렉션의 초기 용량을 적절히 설정하면 동적 확장으로 인한 성능 저하를 방지할 수 있습니다.

1
2
3
4
// 초기 용량 설정 예시
ArrayList<String> list = new ArrayList<>(10000); // 예상 요소 수에 맞게 초기화
HashMap<String, Integer> map = new HashMap<>(100000.75f); // 초기 용량과 로드 팩터 설정
cs

반복 처리 최적화

컬렉션 순회 방식에 따라 성능이 크게 달라질 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 비효율적인 방식
for (int i = 0; i < list.size(); i++) { // 매 반복마다 size() 호출
    process(list.get(i));
}
// 최적화된 방식
int size = list.size(); // size() 한 번만 호출
for (int i = 0; i < size; i++) {
    process(list.get(i));
}
// 향상된 for문 (가독성 좋음)
for (String item : list) {
    process(item);
}
// 스트림 API 활용 (함수형 접근)
list.stream()
    .filter(item > item.length() > 3)
    .forEach(this::process);
cs

함수형 프로그래밍과 스트림 활용

Java 8부터 도입된 스트림 API는 컬렉션 처리를 간결하고 효율적으로 만들어 줍니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 명령형 방식
List<String> filtered = new ArrayList<>();
for (String s : strings) {
    if (s.length() > 3) {
        filtered.add(s.toUpperCase());
    }
}
// 스트림 API 활용
List<String> filtered = strings.stream()
    .filter(s > s.length() > 3)
    .map(String::toUpperCase)
    .collect(Collectors.toList());
// 병렬 스트림 활용 (멀티코어 활용)
List<String> filtered = strings.parallelStream()
    .filter(s > s.length() > 3)
    .map(String::toUpperCase)
    .collect(Collectors.toList());
cs

메모리 효율성 향상

메모리 사용량을 최적화하여 가비지 컬렉션 부하를 줄일 수 있습니다.

1
2
3
4
5
6
7
8
9
// 원시 타입 사용
int[] primitiveArray = new int[1000]; // 더 효율적인 메모리 사용
// 래퍼 클래스 사용
Integer[] wrapperArray = new Integer[1000]; // 추가 오버헤드 발생
// 전문화된 컬렉션 라이브러리 활용 (예: Trove, FastUtil)
TIntArrayList tList = new TIntArrayList(1000); // 원시 타입 전용 컬렉션
cs

객체 풀링 활용

자주 생성되고 삭제되는 객체의 경우, 객체 풀링을 통해 성능을 향상시킬 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class ObjectPool<T> {
    private List<T> available = new ArrayList<>();
    private List<T> inUse = new ArrayList<>();
    private Supplier<T> supplier;
    public ObjectPool(Supplier<T> supplier, int initialSize) {
        this.supplier = supplier;
        for (int i = 0; i < initialSize; i++) {
            available.add(supplier.get());
        }
    }
    public synchronized T acquire() {
        if (available.isEmpty()) {
            available.add(supplier.get());
        }
        T instance = available.remove(available.size()  1);
        inUse.add(instance);
        return instance;
    }
    public synchronized void release(T instance) {
        inUse.remove(instance);
        available.add(instance);
    }
}
// 사용 예시
ObjectPool<StringBuilder> pool = new ObjectPool<>(StringBuilder::new10);
StringBuilder sb = pool.acquire();
try {
    sb.append(“Hello”).append(” World”);
    // 작업 수행
finally {
    pool.release(sb);
}
cs

불변 컬렉션 활용

스레드 안전성이 필요한 경우 불변 컬렉션을 사용하면 동기화 오버헤드 없이 안전하게 데이터를 공유할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
// Java 9 이상
List<String> immutableList = List.of(“A”“B”“C”);
Map<String, Integer> immutableMap = Map.of(“A”1“B”2“C”3);
// Java 8 이하
List<String> immutableList = Collections.unmodifiableList(Arrays.asList(“A”“B”“C”));
Map<String, Integer> map = new HashMap<>();
map.put(“A”1);
map.put(“B”2);
map.put(“C”3);
Map<String, Integer> immutableMap = Collections.unmodifiableMap(map);
cs

동시성 컬렉션 활용

멀티스레드 환경에서는 동시성 컬렉션을 사용하여 성능을 향상시킬 수 있습니다.

1
2
3
4
5
6
7
// 동기화된 컬렉션 (성능 저하 가능성)
List<String> synchronizedList = Collections.synchronizedList(new ArrayList<>());
// 동시성 컬렉션 (더 나은 성능)
List<String> concurrentList = new CopyOnWriteArrayList<>();
Map<String, Integer> concurrentMap = new ConcurrentHashMap<>();
cs

실전 예제: 대용량 데이터 처리 최적화

다음은 대용량 사용자 데이터를 처리하는 최적화된 예제입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class UserProcessor {
    private static final int BATCH_SIZE = 1000;
    public List<UserDTO> processLargeUserList(List<User> users) {
        return users.parallelStream()
            .filter(User::isActive)
            .collect(Collectors.groupingBy(user > user.getId() % BATCH_SIZE))
            .values()
            .parallelStream()
            .flatMap(batch > processBatch(batch).stream())
            .collect(Collectors.toList());
    }
    private List<UserDTO> processBatch(List<User> batch) {
        // 배치 단위로 처리
        return batch.stream()
            .map(this::convertToDTO)
            .collect(Collectors.toList());
    }
    private UserDTO convertToDTO(User user) {
        UserDTO dto = new UserDTO();
        dto.setId(user.getId());
        dto.setUsername(user.getUsername());
        // 필요한 변환 작업 수행
        return dto;
    }
    // 성능 측정 유틸리티 메서드
    public void measurePerformance(List<User> users) {
        long start = System.currentTimeMillis();
        List<UserDTO> result = processLargeUserList(users);
        long end = System.currentTimeMillis();
        System.out.println(“Processed “ + result.size() + ” users in “ + (end  start) + “ms”);
    }
}
cs

고급 최적화 기법: 커스텀 해시 함수 구현

해시 기반 컬렉션(HashMap, HashSet)의 성능은 해시 함수의 품질에 크게 의존합니다.

효율적인 해시 함수를 구현하면 충돌을 최소화하고 성능을 향상시킬 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class OptimizedKey {
    private final String value;
    public OptimizedKey(String value) {
        this.value = value;
    }
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        OptimizedKey that = (OptimizedKey) o;
        return Objects.equals(value, that.value);
    }
    @Override
    public int hashCode() {
        // 효율적인 해시 함수 구현
        int hash = 7;
        for (int i = 0; i < value.length(); i++) {
            hash = 31 * hash + value.charAt(i);
        }
        return hash;
    }
}
cs

Collections 유틸리티 클래스 활용

Java의 Collections 클래스는 컬렉션 작업을 위한 다양한 유틸리티 메서드를 제공합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import java.util.*;
public class CollectionsUtilExample {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        numbers.add(5);
        numbers.add(3);
        numbers.add(8);
        numbers.add(1);
        // 정렬
        Collections.sort(numbers);
        System.out.println(“Sorted List: “ + numbers);
        // 이진 검색
        int index = Collections.binarySearch(numbers, 3);
        System.out.println(“Index of 3: “ + index);
        // 역순 정렬
        Collections.reverse(numbers);
        System.out.println(“Reversed List: “ + numbers);
        // 최소/최대값 찾기
        int min = Collections.min(numbers);
        int max = Collections.max(numbers);
        System.out.println(“Min: “ + min + “, Max: “ + max);
        // 동기화된 컬렉션 생성
        List<Integer> synchronizedList = Collections.synchronizedList(numbers);
        // 불변 컬렉션 생성
        List<Integer> unmodifiableList = Collections.unmodifiableList(numbers);
    }
}
cs

 

Java 컬렉션 프레임워크를 최적화하여 사용하면 애플리케이션의 성능을 크게 향상시킬 수 있습니다.

적절한 컬렉션 선택, 초기 용량 설정, 효율적인 반복 처리, 함수형 프로그래밍 활용 등의 기법을 통해 더 효율적이고

확장 가능한 애플리케이션을 개발할 수 있습니다.

 

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다