String을 이어 붙이는 방법들에 대해 알아보고, 어떤 방법이 효율적인지 생각해보자!
String은 불변한다!
String의 속성을 생각해보면 String은 불변(immutable)하다. 그런데 String에다+ 연산자를 사용해서 이어 붙여 사용하곤 한다. 분명 String은 불변한다고 했는데 왜 추가하면 추가하는대로 결과를 보여줄 수 있을까?
먼저, int 변수의 경우 primitive type 이기 때문에 메모리 상의 stack 공간에 값으로 저장된다. 그래서 int 변수의 값을 바꿀 수 있다.
하지만, String 변수의 경우 Reference type이므로 stack 공간에 실제 값이 저장된 heap 공간의 주소가 저장되어 있고, heap 공간 내의 Constant Pool에서 String을 저장하여 관리한다. 이 때, 같은 변수 명으로 다른 String을 할당한다 하더라도 Constant Pool에 새로 할당한 String 값이 저장되고 이를 참조하게 변하는 것이고 그 전의 String 값이 바뀌는 것이 아니므로 불변한다.
String이 불변함으로써 좋은 점!
- ConstantPool에서 관리할 수 있기 때문에 같은 String이 많은 경우 Heap 영역은 많은 메모리를 저장할 수 있다.
- String이 불변이 아니라면 보안상의 문제를 발생시킬 수 있다.
- DB의 username, password, host, port 등의 정보가 String으로 다루어지는데 가변적이라면 해커의 공격으로 값이 변경될 수 있다.
- 동기화 문제를 신경쓰지 않아도 된다. (값이 변경되지 않으니까!)
- Java는 String의 hashcode 값은 생성단계에서부터 캐싱한다. 따라서 String의 hashcode는 사용할 때마다 매번 계산되지 않는다.
- HashMap<String, Integer> 처럼 key가 String인 해시맵의 경우 이미 String의 hashcode는 캐싱되어 있기 때문에 String key로 조회할 때마다 hashcode를 계산할 필요가 없어서 더 빠른 속도로 사ㅛㅇ할 수 있다.
- Hashcode를 미리 캐싱할 수 있는 이유는 String이 변하지 않기 때문이다.
String의 + 연산자
앞서 보았던 String의 불변성 때문에 내가 사용하던 + 연산자는 사실 연산자를 수행할 때마다 새로운 String을 생성하여 Stack 메모리와 Heap 메모리(Constant Pool)에 쌓고 있었던 것이다.
String str = "a"; // memory : @418
str = str + "b"; // memory : @416
str = str + "c"; // memory : @504
String str2 = "a"; // memory : @418
StringBuilder & StringBuffer
이렇게 + 연산자를 많이 사용해서 메모리를 낭비하는 일은 효율적이지 못하다. 이럴 때, StringBuilder, StringBuffer를 사용해서 효율적인 성능을 낼 수 있다. 이 둘은 String과 달리 가변성을 가지기 때문에 String 값이 변하더라도 메모리 주소를 동일하게 가지고 있다. 따라서 String 값이 빈번하게 바뀌는 프로그램이라면 StringBuilder, StringBuffer를 사용하는 것이 좋다. 참고로, 이 둘을 상속하는 AbstractStringBuilder는 문자열을 추가하게 되면 추가할 문자열의 크기 만큼 현재 문자열을 저장하는 배열 공간을 늘려주고, 문자열을 넣어주는 방식으로 되어 있다.
StringBuilder와 StringBuffer는 가변성이라는 점에서 동일하지만 동시성 문제에서는 차이를 보인다. StringBuffer의 경우 동기화를 지원하고 StringBuilder의 경우 동기화를 지원하지 않는다. 그 이유는 StringBuffer에서 synchronized 키워드를 사용하기 때문이다. synchronized 키워드는 멀티 쓰레딩 환경에서 A 쓰레드와 B쓰레드가 동시에 접근하는 것을 막는 역할인데 속도 저하의 원인이기도 하다. 이 때문에, StringBuilder는 StringBuffer보다 동기화 부분에서 취약하지만 속도면에서는 빠르다.
정리
- String은 불변성을 띄고, + 연산자를 통해 변경할 때마다 Constant Pool에 추가하여 효율성이 떨어진다.
- 그렇다고, 무조건 쓰지 말란 것은 아니고, 불변하는 문자열이 많은 경우에는 String이 효율적이다.
- StringBuffer와 StringBuilder는 String과 달리 가변성을 띄기 때문에 변경이 잦은 경우 효과적이다.
- StringBuffer는 동시성 문제를 해결할 수 있는 반면 StringBuilder는 동시성 문제를 해결하지 못한다.
- 동시성 문제가 발생할 수 있는 멀티쓰레딩 환경에서는 StringBuffer를 쓰자.
- StringBuilder는 synchronized 를 사용하는 StringBuffer보다 속도 측면에서 빠르다.
- 동시성 문제가 발생하지 않는 환경에서는 StringBuilder를 쓰자.
- StringBuffer는 동시성 문제를 해결할 수 있는 반면 StringBuilder는 동시성 문제를 해결하지 못한다.
출처
'개인 공부' 카테고리의 다른 글
정적 팩토리 메서드 (0) | 2022.11.21 |
---|---|
Logback.xml 동일 패키지 내 다른 레벨 처리 (2) | 2022.11.10 |
Garbage Collector 동작원리 (0) | 2022.11.01 |
프로토 타입 패턴 (0) | 2022.10.30 |
도커(Docker)와 가상머신(VM) (0) | 2022.10.28 |