요약
문자열을 처리하기 위하여 자바는 String 클래스를 사용한다. 그러나, 자바에서는 StringBuffer 클래스를 문자열 처리를 위한 또 한가지의 방법으로 제공하고 있다. 이 두가지 클래스의 차이점은 무엇이고 어떤 것을 사용하는 것이 좋은가를 알아보자.
문자열을 처리하기 위하여 자바는 String 클래스를 사용한다. 그러나, 자바에서는 StringBuffer 클래스를 문자열 처리를 위한 또 한가지의 방법으로 제공하고 있다. 이 두가지 클래스의 차이점은 무엇이고 어떤 것을 사용하는 것이 좋은가를 알아보자.
String 클래스와 StringBuffer 클래스의 특징
대체로 초보자는 String 클래스만을 사용하고 있으며, 실력이 향상되어 StringBuffer 클래스를 알게 되면 성능 향상이라는 이유로 StringBuffer 클래스를 자주 사용하는 경향이 있다. 사실 두개의 클래스는 똑같이 문자열 처리를 위한 클래스이며 메모리상의 처리 방법에서 차이를 보여주고 있을 뿐이다. 이러한 처리 방법의 차이가 또한 성능의 차이를 보여주고 있다. 실제로 많은 경우 String 클래스보다 StringBuffer 클래스의 성능이 훨씬 좋다고 많은 사람들이 생각하고 있으며, 실제로 많은 경우에 성능 차이를 보이고 있다.
그렇다면, StringBuffer 클래스가 String 클래스보다 항상 더 나은 성능을 가지고 있을까? 그렇다면 왜, 자바를 설계한 사람들은 String 클래스를 기본 문자열처리 클래스로 정했을까? 여기에 대한 답을 알아보기로 하자.
String 클래스는 변경이 불가능한 immutable 클래스이다.
String 클래스에서 substring(), toLowerCase(), concat(), trim() 등의 메소드를 생각하면 String 클래스는 변경 가능한 클래스처럼 보인다. 그러나, 실제로는 이러한 메소드들은 원래 객체와 다른 새로운 String 객체를 만들어 반환한다. 또 하나의 String 객체가 생성되는 것이다. 따라서 원래 String 객체는 가지고 있는 문자열이 변경되지 않으며 여전히 사용가능한 채로 남는다.
즉, 기존의 String 객체에 substring()과 같은 문자열에 변경을 가하는 메소드를 실행하면 또 하나의 String 객체가 생성되어 서로 다른 두개의 String 객체가 존재하게 된다.
이런 이유로 String 클래스의 변경은 객체를 생성하기 위하여 시스템 자원(시간, 메모리 등)을 낭비한다고 생각되는 경향이 있다. 그렇다면 왜 immutable(변경불가) 클래스인가?
왜 immutable(변경불가) 클래스인가?
immutable 클래스는 몇 가지 조건과 특징을 가지고 있다.
첫번째는, 클래스가 가지고 있는 값(즉, String 클래스에서는 문자열)은 오직 생성자에서만 설정될 수 있으며, 그 값을 변경할 수 있는 어떠한 메소드도 가지고 있지 않아야 한다. 만약 변경을 원한다면, 원하는 값을 가진 새로운 객체를 생성한다.
이런 immutable 클래스의 가장 큰 장점은 안전하게 공유될 수 있다는 점이다. 즉, 변경은 적고, 읽기(즉, 문자열의 참조)만 많은 경우, 또는, 여러 쓰레드나 객체에서 공유하는 경우, synchronization(동기화)와 같은 특별한 안전장치 없이도 안전하게 공유될 수 있다.
대부분의 문자열이 복잡한 문자열 처리과정보다는 한번 설정된 문자열들을 여러 곳에서 공유하는 경우가 많으므로, 자바에서 기본 문자열을 처리하는 클래스로 String 클래스를 immutable 패턴으로 설정하였다.
StringBuffer 클래스는 변경이 가능한 mutable 클래스이다.
StringBuffer 클래스는 가지고 있는 문자열의 내용을 변경 가능하도록 만든 클래스이다. 즉, append(), insert(), delete() 등의 메소드를 통하여 StringBuffer 객체가 가지고 있는 문자열을 변경할 수 있으며, 이 때, String 클래스처럼 새로운 객체를 생성하지 않고, 기존의 문자열을 변경한다. 이 경우 객체 생성을 하지 않으므로, String 클래스보다 효율적이라고 생각하기 쉽지만, 동기화(synchronization)를 보장해야 하기 때문에 단순한 참조에서는 상대적으로 String 보다 나쁜 성능을 보인다. 따라서, 단순 참조가 많은 경우 StringBuffer 클래스보다 String 클래스가 유리하다. 물론, StringBuffer 클래스는 동기화되어 있으므로, 멀티 쓰레드에 대하여 안전하다.
또한, StringBuffer 객체는 문자열을 다루는 다른 메소드에서 사용되기 위하여, toString() 메소드를 통하여 String 객체를 생성하게 된다. 이때, 일반적으로 String 객체의 생성과 함께, 가지고 있는 문자열에 대한 복사가 이루어진다. 물론, 자바 규약은 성능 향상을 위하여 String 객체 생성 후에 문자열을 복사하지 않고, StringBuffer 객체와 문자열을 공유하여 참조하는 프록시 패턴을 적용하는 것을 허용하고 있다. 그러나, 이것은 반드시 그런 것은 아니며, 프록시 패턴의 특성상 StringBuffer 객체에 변경이 가해지면, 프록시는 바로 해제되며, 그 시점에서 문자열의 복사가 이루어진다. (프록시 패턴의 적용은 필수 요건이 아니며, 자바 가상 머신 구현체에 따라 다를 수 있으며, 사용상의 차이는 전혀 없고 성능 상의 차이만을 보일 뿐이다.)
성능 차이의 실제적인 비교
다음과 같은 질문을 생각해보자.
- StringBuffer 객체는 내용을 변경할 때 String 객체보다 효율적인가?
- String 객체는 가지고 있는 문자열을 변경할 때 어느 정도 StringBuffer 객체에 비해 성능 저하를 보이는가?
- StringBuffer 및 String 클래스는 모두 문자열 처리에서 가장 많이 쓰이는 substring() 메소드에 대하여 String 객체를 생성한다. 그렇다면 성능상의 차이가 있는가?
- StringBuffer 객체는 toString() 메소드를 통하여 String 객체를 생성하여야만 다른 객체에 문자열을 전달할 수 있다. toString() 메소드를 통한 String 객체 생성의 자원 소모는 어느 정도 인가?
- String 객체 및 StringBuffer 객체의 생성은 어느 정도의 자원 소모를 필요로 하는가?
여기에 대한 답을 얻기 위하여 간단한 테스트 프로그램을 작성하여 결과를 구해보았다. 다음과 같은 8가지 경우에 대하여 각각 64만번의 반복을 통하여 소요된 시간과 자유 메모리의 변화를 그래프로 보면 다음과 같다.
비교를 위해 테스트에 사용된 8가지 메소드
- String.concat() – String 클래스에 문자열 추가
- StringBuffer.append() – StringBuffer 클래스에 문자열 추가
- Stirng.substring() – String 클래스에서 문자열 일부 추출
- StirngBuffer.substring() – StringBuffer 클래스에서 문자열 일부 추출
- Stirng.toString() – String 클래스의 toString() 메소드 호출 (실제로는 자기자신을 돌린다)
- StirngBuffer.toString() – StringBuffer 클래스의 toString() 메소드 호출 (즉, String 객체로 변환)
- new String() – String 객체 생성
- new StringBuffer() – StringBuffer 객체 생성