[Java] String, StringBuffer, StringBuilder

develemon·2024년 2월 27일
0

Java

목록 보기
1/3
post-thumbnail

Intro


백준 알고리즘 문제 [백준] 10989번 : 수 정렬하기 3를 풀다가 Java의 String, StringBuffer, StringBuilder에 대해 정리하게 되었다.

우선 개념 정리에 앞서서 백준 문제에 대해 작성한 첫 코드는 아래와 같았다.

import java.io.*;
import java.util.Arrays;

public class Main {

    public static void main(String[] args) throws IOException {

        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        int N = Integer.parseInt(br.readLine());
        int arr[] = new int[N];
        for (int i = 0; i < N; i++) {
            arr[i] = Integer.parseInt(br.readLine());
        }

        int[] sorted = Arrays.stream(arr).sorted().toArray();
        for (int a : sorted) {
            System.out.println(a);
        }
    }
}

로컬에서 테스트한 결과로는 문제 없었지만, 시간초과로 실패했다. 그러다가 검색을 통해 다른 사람의 코드([백준] 10989번 : 수 정렬하기 3 – JAVA [자바])로 실행해봤다.

import java.io.*;
import java.util.Arrays;

public class Main {

    public static void main(String[] args) throws IOException {

        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        int N = Integer.parseInt(br.readLine());
        int arr[] = new int[N];
        for (int i = 0; i < N; i++) {
            arr[i] = Integer.parseInt(br.readLine());
        }

        Arrays.sort(arr);
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < N; i++) {
            sb.append(arr[i] + "\n");
        }
        System.out.print(sb);
    }
}

이 코드로는 시간초과 없이 통과되었다. 여기서 궁금해지는 부분은 두 가지였다.

  • Arrays.sort()Arrays.stream().sorted.toArray()의 차이
  • StringBuilder는 무엇인가

첫 번째는 Arrays 객체에 대한 이해가 필요할 것 같다. 그러나 재밌는 건, Arrays.sort()를 하건 Arrays.stream()을 하건 관계 없이, arr 객체를 그대로 println() 했을 때에는 시간초과였지만 StringBuilder를 통해 append() 했을 때에는 통과되었다는 것이다. 그래서 지금은 두 번째 StringBuilder가 무엇인지에 대해 알아보려고 한다.

그러다가 검색을 통해 아래의 게시물을 확인하게 되었다.

→ [자바] String, StringBuilder, StringBuffer의 차이

위 게시물에서는 String, StringBuilder, StringBuffer 각각에 대해 설명하는데, 그 내용은 다음과 같다.

String


우선 String 클래스와 StringBuilder, StringBuffer 클래스의 가장 기본적인 차이는 String은 불변(Immutable)인 반면, 다른 클래스는 가변(mutable)이라는 것이다.

String 객체는 한번 생성되면 할당된 메모리 공간이 변하지 않는다. + 연산자 또는 concat 메서드를 통해 기존에 생성된 String 클래스 객체 문자열에 다른 문자열을 붙여도 기존 문자열에 새로운 문자열을 붙이는 것이 아니라, 새로운 String 객체를 만든 후, 새 String 객체에 연결된 문자열을 저장하고, 그 객체를 참조하도록 한다. 즉, String 클래스 객체는 Heap 메모리 영역(가비지 컬렉션이 동작하는 영역)에 생성되고, 한 번 생성된 객체의 내부 내용은 변경시킬 수 없으며, 기존 객체가 제거되면 Java의 가비지 컬렉션이 회수한다는 것이다.

String 객체의 이러한 특성에 의해 문자열 연산이 많은 경우에 그 성능이 좋지 않다. 하지만 불변(Immutable) 객체는 사용성이 좋고, 동기화에 대해 신경쓰지 않아도 되기 때문에(Thread-safe), 내부 데이터를 자유롭게 공유할 수 있다.

StringBuffer와 StringBuilder


String과는 달리, StringBuffer, StringBuilder는 문자열 연산 등으로 기존 객체의 공간이 부족하게 되는 경우, 기존의 버퍼 크기를 늘리며 유연하게 동작한다. 즉, 새로운 객체를 만들지 않기 때문에 복잡한 문자열 연산에서 String보다 성능이 유리하다.

그렇다면 두 클래스의 차이는 무엇일까. 바로 동기화 여부이다.

동기화

  • StringBuffer는 각 메서드별로 Synchronized Keyword가 존재하며, 멀티스레드 환경에서도 동기화를 지원
    → 멀티스레드 환경에 적합

  • StringBuilder는 동기화를 보장하지 않음
    → 단일스레드 환경에 적합

이로 인해 멀티스레드 환경이라면 값 동기화 보장을 위해 StringBuffer를 사용하고, 단일스레드 환경이라면 StringBuilder를 사용하는 것이 좋다. 단일스레드 환경에서 StringBuffer를 사용한다고 문제되는 것 까지는 아니지만, 동기화 관련 처리로 인해 StringBuilder에 비해 성능이 좋지 않다.

추가로 책 <Java의 정석>에서도 해당 내용을 찾아봤다. 발췌 내용은 아래와 같다.

String 클래스는 인스턴스를 생성할 때 지정된 문자열을 변경할 수 없지만 StringBuffer 클래스는 변경이 가능하다. 내부적으로 문자열 편집을 위한 버퍼(buffer)를 가지고 있으며, StringBuffer 인스턴스를 생성할 때 그 크기를 지정할 수 있다.
이 때, 편집할 문자열의 길이를 고려하여 버퍼의 길이를 충분히 잡아주는 것이 좋다. 편집 중인 문자열이 버퍼의 길이를 넘어서게 되면 버퍼의 길이를 늘려주는 작업이 추가로 수행되어야하기 때문에 작업효율이 떨어진다.
StringBuffer 클래스는 String 클래스와 유사한 점이 많다. 아래의 코드에서 알 수 있듯이, StringBuffer 클래스는 String 클래스와 같이 문자열을 저장하기 위한 char형 배열의 참조변수를 인스턴스 변수로 선언해 놓고 있다. StringBuffer 인스턴스가 생성될 때, char형 배열이 생성되며 이 때 생성된 char형 배열을 인스턴스 변수 value가 참조하게 된다.

public final class StringBuffer implements java.io.Serializable {
	private char[] value;
   ...
}

즉, 연산이 많지 않으면 String 클래스가 유리하지만, 연산이 많은 경우에는 StringBuffer가 유리하다는 것이다. 따라서 연산이 많은 경우에 대해 단순하게 성능 비교를 했을 때에는 StringBuilder > StringBuffer >>> String이므로, 많은 연산이 필요했던 위 백준 문제에서는 StringBuilder를 사용했는지 아닌지에 따라 통과 여부가 결정된 것이다.

정리


  • String : 불변 객체로, 짧은 문자열을 더하거나 연산이 많지 않은 경우 사용
  • StringBuffer : 스레드에 안전한 프로그램이 필요할 때나, 개발 중인 시스템의 부분이 스레드에 안전한지 모를 경우 사용
  • StringBuilder : 스레드에 안전한지 여부가 전혀 관계 없는 프로그램을 개발할 때 사용
profile
유랑하는 백엔드 개발자 새싹 블로그

0개의 댓글