본문 바로가기
JAVA/기타

Java Fast I/O 3 (feat. BOJ, String, StringBuilder)

by D.O.T 2024. 7. 6.

앞선 포스트와 마찬가지로 사람들은 PS에서 속도를 개선하기 위한 방법 중 하나로 StringBuilder를 사용한다.

 

StringBuilder

abstract sealed class AbstractStringBuilder implements Appendable, CharSequence
    permits StringBuilder, StringBuffer {
    
    byte[] value;
    byte coder;
    boolean maybeLatin1;
    int count;
    private static final byte[] EMPTYVALUE = new byte[0];
    
    AbstractStringBuilder() {
        value = EMPTYVALUE;
    }
    
    public AbstractStringBuilder append(String str) {
        if (str == null) {
            return appendNull();
        }
        int len = str.length();
        ensureCapacityInternal(count + len);
        putStringAt(count, str);
        count += len;
        return this;
    }
    
}

 

StringBuilder의 추상 클래스이다. (여기서 Java Code Convention이 거슬린건 저뿐인가요?)

StringBuilder는 내부에서 Buffer를 가지고 문자열을 추가하는 방식이라고 생각하면 된다.

이 때, 용량이 가득 찼을 때만 사이즈 재할당을 한다.

 

String

String의 경우, Debug 해보면 해당 과정을 알 수 있는데 Assembly Level에서 먼저 String Builder를 생성하게 된다.

String은 String Pool에서 관리되는 불변 객체이기 때문에 고정된 크기의 새로운 객체를 만들어야 하므로 가변 객체인 StringBuilder를 통해서 새로운 불변 객체인 String을 생성해낸다.

 

즉, String은 operator() + 연산 시 StringBuilder를 생성해서 String + String을 한다.

    String a = "a";
    String b = "b";
    String c = new StringBuilder(a).append(b).toString();

 

이게 과연 진실일까? 가장 간단한 확인은 이 포스트를 확인하는 사람이 Debugging 을 직접해보면 된다.

두 번째는 HashCode를 확인해보면 된다.

 

    String test = "test";
    final int beforeHash = System.identityHashCode(test);
    test += "isSame?";
    final int afterHash = System.identityHashCode(test);
    System.out.println("before: " + beforeHash + ", after: " + afterHash);

before: 664740647, after: 1804094807


Performance Test
public class StringOutputTest {

    public static void main(String[] args) {
       int iterations = 1;

       // Using String
       long startTime = System.nanoTime();
       String str = "";
       for (int i = 0; i < iterations; i++) {
          str += "a";
       }
       long endTime = System.nanoTime();
       System.out.println("String time: " + (endTime - startTime) + " ns");

       // Using StringBuilder
       startTime = System.nanoTime();
       StringBuilder sb = new StringBuilder();
       for (int i = 0; i < iterations; i++) {
          sb.append("a");
       }
       endTime = System.nanoTime();
       System.out.println("StringBuilder time: " + (endTime - startTime) + " ns");
    }

}

 

String time: 7897400 ns
StringBuilder time: 27000 ns

이게 내가 똥컴이라 그런지 1번만 연산해도 이렇게 차이가 난다니..

1번 연산의 결과만 보더라도 String에서 새로운 StringBuilder를 생성하기 때문에 이렇게 차이가 나는 것을 알 수 있다.

 

String time: 11001900 ns
StringBuilder time: 99400 ns

출력이 100개인 경우는 더욱 심하게 차이가 난다.

 

출력할 문자열에 개행 문자를 포함하거나 긴 문자열을 더해야하는 경우, += 은 절대 쓰지말자!