[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));
}
}
권장 사항 정리
- String Pool 활용
- 자주 사용되는 문자열은 intern() 고려
- 동일한 문자열이 많은 경우 메모리 절약
- StringBuilder vs StringBuffer
- 단일 스레드: StringBuilder
- 멀티 스레드: StringBuffer
- 가능하면 초기 용량 지정
- 문자열 연결
-
- 연산자는 간단한 연결에만 사용
- 반복문에서는 항상 StringBuilder 사용
- String.format()은 성능에 민감하지 않은 경우에만 사용
-
- 메모리 관리
- 대량의 문자열 처리 시 메모리 모니터링
- 불필요한 문자열 객체 생성 최소화
- 문자열 캐시 사용 검토
'개발&프로그래밍' 카테고리의 다른 글
[JAVA] ThreadLocal 제대로 사용하기 (0) | 2024.11.17 |
---|---|
[JAVA] 효과적인 예외 처리 전략 (0) | 2024.11.16 |
[JAVA] 가비지 컬렉션 동작 원리와 모니터링 방법 (1) | 2024.11.14 |
[IntelliJ IDEA] 코드 리팩토링을 도와주는 플러그인 TOP 5 (0) | 2024.11.05 |
[JAVA] try-with-resources와 AutoCloseable 인터페이스 (0) | 2024.11.04 |
댓글