본문 바로가기
개발&프로그래밍

[JAVA] Java의 String Pool과 문자열 최적화

by 재아군 2024. 11. 15.

[JAVA] Java의 String Pool과 문자열 최적화

 

 

String Pool의 이해

String Pool은 Java가 문자열을 저장하고 재사용하는 특별한 메모리 영역이다. 리터럴로 생성된 문자열은 자동으로 이 풀에 저장된다.

String str1 = "hello";  // String Pool에 저장
String str2 = "hello";  // 풀에서 재사용
String str3 = new String("hello");  // 새로운 객체 생성

System.out.println(str1 == str2);  // true
System.out.println(str1 == str3);  // false

 

intern() 메서드 활용

intern() 메서드를 사용하면 문자열을 명시적으로 String Pool에 저장할 수 있다.

String str1 = new String("hello").intern();
String str2 = "hello";
System.out.println(str1 == str2);  // true

// 실제 활용 예시
public class UserKey {
    private final String domain;
    private final String userId;

    public UserKey(String domain, String userId) {
        this.domain = domain.intern();  // 자주 사용되는 도메인명은 intern()
        this.userId = userId;
    }
}

 

intern() 사용 시 주의사항

// 메모리 사용량이 증가할 수 있는 예
List<String> list = new ArrayList<>();
for (int i = 0; i < 1000000; i++) {
    list.add(("String" + i).intern());  // String Pool이 계속 증가
}

 

문자열 연결 최적화

 

1. + 연산자 사용 시 주의점

String result = "";
for (int i = 0; i < 10000; i++) {
    result += i;  // 매우 비효율적
}

 

2. StringBuilder 활용

StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
    sb.append(i);
}
String result = sb.toString();

 

3. StringBuffer vs StringBuilder 성능 비교

// 단일 스레드 환경: StringBuilder
StringBuilder sb = new StringBuilder();
long start = System.nanoTime();
for (int i = 0; i < 100000; i++) {
    sb.append(i);
}
System.out.println("StringBuilder: " + (System.nanoTime() - start));

// 멀티 스레드 환경: StringBuffer
StringBuffer buffer = new StringBuffer();
start = System.nanoTime();
for (int i = 0; i < 100000; i++) {
    buffer.append(i);
}
System.out.println("StringBuffer: " + (System.nanoTime() - start));

 

메모리 최적화 기법

 

1. 문자열 분할과 결합

// 비효율적인 방법
String[] parts = "a,b,c,d,e".split(",");
String result = String.join(",", parts);

// 효율적인 방법
StringJoiner joiner = new StringJoiner(",");
for (String part : parts) {
    joiner.add(part);
}
String result = joiner.toString();

 

2. 문자열 캐싱

public class StringCache {
    private static final Map<String, String> cache = new ConcurrentHashMap<>();

    public static String getCanonicalString(String str) {
        String cached = cache.get(str);
        if (cached != null) return cached;

        String canonical = str.intern();
        cache.put(str, canonical);
        return canonical;
    }
}

 

3. StringBuilder 초기 용량 설정

// 예상 크기로 초기화하여 재할당 횟수 줄이기
StringBuilder sb = new StringBuilder(1000);
for (int i = 0; i < 100; i++) {
    sb.append("somewhat long string");
}

 

성능 비교 결과

다양한 문자열 처리 방식의 성능 비교:

public class StringPerformanceTest {
    private static final int ITERATIONS = 100000;

    public static void main(String[] args) {
        // + 연산자
        long start = System.currentTimeMillis();
        String s = "";
        for (int i = 0; i < ITERATIONS; i++) {
            s += "a";
        }
        System.out.println("+ operator: " + (System.currentTimeMillis() - start));

        // StringBuilder
        start = System.currentTimeMillis();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < ITERATIONS; i++) {
            sb.append("a");
        }
        System.out.println("StringBuilder: " + (System.currentTimeMillis() - start));

        // StringBuffer
        start = System.currentTimeMillis();
        StringBuffer buffer = new StringBuffer();
        for (int i = 0; i < ITERATIONS; i++) {
            buffer.append("a");
        }
        System.out.println("StringBuffer: " + (System.currentTimeMillis() - start));
    }
}

 

권장 사항 정리

  1. String Pool 활용
    • 자주 사용되는 문자열은 intern() 고려
    • 동일한 문자열이 많은 경우 메모리 절약
  2. StringBuilder vs StringBuffer
    • 단일 스레드: StringBuilder
    • 멀티 스레드: StringBuffer
    • 가능하면 초기 용량 지정
  3. 문자열 연결
      • 연산자는 간단한 연결에만 사용
    • 반복문에서는 항상 StringBuilder 사용
    • String.format()은 성능에 민감하지 않은 경우에만 사용
  4. 메모리 관리
    • 대량의 문자열 처리 시 메모리 모니터링
    • 불필요한 문자열 객체 생성 최소화
    • 문자열 캐시 사용 검토

 

댓글