String vs StringBuilder vs StringBuffer

이주오·2021년 8월 5일
0

java

목록 보기
2/6
post-thumbnail

String 클래스


변경 불가능한 클래스

  • String 클래스에는 문자열을 저장하기 위해서 문자형 배열 참조변수(byte[]) value를 인스턴스 변수로 정의해놓고 있다.
    • 다른 블로그나 자료를 찾아보면 char 배열을 사용한다고 되어있는데 확인해보니 byte[]배열을 사용하고 있었다. 왜 변경된 것일까??
    • 찾아보니 jdk 9부터 기존 char[]에서 byte[]을 사용하여 String Compacting을 통한 성능 및 heap 공간 효율(2byte -> 1byte)을 높이도록 수정되었다고 한다.
  • 인스턴스 생성 시 생성자의 매개변수로 입력받는 문자열은 이 인스턴스변수에 문자형 배열로 저장되는 것이다.
public final class String implements java.io.Serializable, Comparable {
	private final byte[] value;
}
  • 한번 생성된 String 인스턴스가 갖고 있는 문자열은 읽어 올 수만 있고, 변경할 수는 없다.
    • 아래처럼 +로 결합하는 경우 새로운 문자열("ab")이 담긴 String 인스턴스가 생성되는 것이다.
String str = new String("Hello");
str = str + "world";

이미지 출처

  • 위처럼 문자열을 결합하는 것은 매 연산 시마다 새로운 문자열을 가진 String 인스턴스가 생성되어 메모리공간을 차지하게 되므로 가능한 한 결합횟수를 줄이는 것이 좋다.
  • 즉, 문자열간의 결합이나 추출 등 문자열을 다루는 작업이 많이 필요한 경우에는 String 클래스 대신 StringBuffer 또는 StringBuilder 클래스를 사용하는 것이 좋다.

그렇다면 자바에서는 왜 String을 불변으로 했을까??

  • 찾아보니 스트링을 불변하게 함으로써 캐싱, 보안, 동기화, 성능측면 이점이 있다고 한다.
  • 캐싱 : String을 불변하게 함으로써 String pool에 각 리터럴 문자열의 하나만 저장하며 다시 사용하거나 캐싱에 이용가능하며 이로 인해 힙 공간이 많이 절약된다.
  • 보안 : 아래와 같은 코드가 있다고 가정할 때 String이 변경 가능하다면 업데이트 쿼리를 실행할 때 까지 유효성 검사를 수행된 시점이라도 String이 안전할지 확신할 수 없다. 여전히 참조가 남아 있으며 SQL 주입에 노출되기 쉽다.
void criticalMethod(String userName) {
    // perform security checks
    if (!isAlphaNumeric(userName)) {
        throw new SecurityException(); 
    }
	
    // do some secondary tasks
    initializeDatabase();
	
    // critical task
    connection.executeUpdate("UPDATE Customers SET Status = 'Active' " +
      " WHERE UserName = '" + userName + "'");
}
  • 동기화 : 불변함으로써 동시에 실행되는 여러 스레드에서 공유가 가능하다. 또한 스레드가 값을 변경하면 String pool에 새 리터럴이 작성되기 때문에 안전하다.
  • 이외에도 해시코드 캐싱에도 이점이 있어 String을 불변하게 한다면 힙 메모리를 절약하고 해시 구현의 액세스 속도를 높여 성능을 향상되기 때문에 불변으로 만든 이유이다.

문자열 연결을 위한 Java 컴파일러 최적화

  • JDK 1.5 이상에서는 컴파일 단계에서 내부적으로 StringBuilder로 변경되어 동작된다.
    • concat() 메서드는 해당사항이 없다.
    • 만약 아래처럼 for문 안에서 문자열 연결 연산을 한다면 매번 StringBuilder 객체가 생성되어 GC는 엄청나게 낮은 성능을 보일 것이니 주의하자
public static void main(String[] args) {
	String result = "";
	for (int i = 0; i < 1e6; i++) {
   		result += "hello";
	}
	System.out.println(result);
}
public static void main(String[] args) {
      String result = "";
      for (int i = 0; i < 1e6; i++) 
      {
        StringBuilder tmp = new StringBuilder();
        tmp.append(result);
        tmp.append("hello");
        result = tmp.toString();
      }
      System.out.println(result);
  }
  • java9에서는 재컴파일을 피하고 바이트 코드를 변경하지 않도록 하기 위해 각 문자열 연결은 JDK Enhancement Proposal 280에 설명된 대로 invokedynamic 에 대한 호출로 변경되었다고 한다.

문자열의 비교

  • 문자열을 만들때는 두가지 방법이 있다.
    1. 문자열 리터럴을 지정하는 방법
    2. String 클래스의 생성자를 사용해서 만드는 방법

이미지 출처

  • String 클래스의 생성자를 이용한 경우에는 new 연산자에 의해서 힙 영역에 메모리할당이 이루어지기 때문에 항상 새로운 String 인스턴스가 생성된다.
  • 문자열 리터럴은 이미 존재하는 것을 재사용하는 것이다.
    • 내부적으로 String.intern() 호출
      1. String Pool에 같은 값이 있는지 찾는다.
      2. 같은 값이 있으면 그 참조값이 반환된다.
      3. 같은 값이 없으면 String Pool에 문자열이 등록된 후 해당 참조값이 반환된다.
    • 문자열 리터럴은 클래스가 메모리에 로드될 때 자동적으로 미리 생성
String str1 = "abc";
String str2 = "abc";

String str3 = new String("abc");
String str4 = new String("abc");

str == str2 // true
st3 == str4 // false
  • equals()를 사용했을 때는 문자열의 내용을 비교하기 때문에 true이지만 인스턴스의 주소를 ==로 비교했을 때는 결과가 다르다.

StringBuffer vs StringBuilder


공통점

  • 두 클래스 모두 는 String과 달리 mutable하다.
  • 따라서 스트링 클래스와 달리 지정된 문자열을 변경할 수 있다.
  • 내부적으로 문자열 편집을 위한 버퍼를 가지고 있으며 인스턴스를 생성할 때 그 크기를 지정할 수 있다.
    • 기본 capacity 16
    • 버퍼의 길이를 충분히 잡아주는 것이 좋다.
    • 길이를 넘어서게 되면 버퍼의 길이를 늘려주는 작업이 추가로 수행되어야하기 때문이다.
  • String 클래스처럼 문자열을 저장하기 위한 char 배열의 참조변수를 인스턴스 변수로 선언해놓고 있다.
    public final class StringBuffer implements java.io.Serializable {
    	private byte[] value;
    }

  • 스트링 클래스와 달리 equals() 메서드를 오버라이딩하지 않아 '==' 로 비교한 것과 같은 결과를 얻는다.

차이점

  • StringBuffer는 synchronized가 적용되어 멀티스레드 환경에서 Thread-safe하게 동작할 수 있다.
    • 다음에는 synchronized를 이용한 동기화 관련도 찾아보자
  • StringBuffer는 동기화로 인해 성능이 떨어지므로 상대적으로 속도가 느리다.
  • StringBuilder 클래스는 쓰레드의 동기화만 빼고 StringBuffer와 똑같은 기능으로 작성되어 있다.

부족한 부분 및 궁금증

  • 실제 성능차이 실습해보기(참고)

참고 출처

profile
동료들이 같이 일하고 싶어하는 백엔드 개발자가 되고자 합니다!

0개의 댓글