티스토리 뷰

Java 문자열 연결 연산 동작 메커니즘

  • Java String을 다루다 보면 문자열 조합을 위해서 + 연산자를 사용하는 경우가 많습니다.
  • 아래 예제를 실행하게 되면 내부에서는 어떤 동작이 이루어질까요?
public class Application {
  public static void main(String[] args) {
    String s1 = "afas";
    String s2 = "asfasfaasf";
    String s3 = s1 + s2;
    System.out.println(s3);
  }
}

검색을 해보자.

  • StackOverFlow를 찾아보니 이런 내용이 있습니다.

+ 연산자는 Java 컴파일러에서 구현이 되며 String + String 연산은 컴파일 타임에 상수 혹은 StringBuilder 코드로 변환된다.


Byte Code를 까보자.

  • 위 코드를 컴파일 하고 클래스 파일을 javap 를 이용하여 바이트 코드를 확인해볼 수 있습니다.
    • 참고로 JDK 1.8 환경입니다.
> javac Application.java
> javap -c Application.class

 

  • 우선 main 메서드의 0:, 3: 라인에서 s1을 "afas", s2를 "asfasfaasf"로 할당하는 것을 확인해볼 수 있습니다.

    • ldc는 상수를 실행 스택에 푸쉬 하는 OP Code 이며 문자열은 String Pool에 저장된 뒤에 가져와지는 방식
    • String Pool, String 객체 생성 방법에 대한 부분은 아래 포스팅을 참고하시면 좋습니다.
  • 6: 라인에서 StringBuilder 객체가 생성되고 14:, 18: 라인에서 StringBuilderappend() 메소드가 호출되고 (흠 .. 어떤 객체가 전달되는지는 알수 없군요) 21: 라인에서 StringBuildertoString() 메소드가 호출됩니다.

    • invokevirtual 은 인스턴스 메소드를 호출하는 OP Code 입니다.
public class com.jinseong.soft.string.Application {
  public com.jinseong.soft.string.Application();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: ldc           #2                  // String afas
       2: astore_1
       3: ldc           #3                  // String asfasfaasf
       5: astore_2
       6: new           #4                  // class java/lang/StringBuilder
       9: dup
      10: invokespecial #5                  // Method java/lang/StringBuilder."<init>":()V
      13: aload_1
      14: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      17: aload_2
      18: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      21: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      24: astore_3
      25: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
      28: aload_3
      29: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      32: return

 

  • 문맥상 컴파일 타임에 String s3 = s1 + s2; 코드는 아래처럼 변환되는 것을 유추해볼 수 있겠군요.

StringBuilder builder = new StringBuilder();
builder.append(s1);
builder.append(s2);
String s3 = builder.toString();

그럼 왜 String 문자열 연결이 느린가?

(이펙티브 자바) 규칙63. 문자열 연결은 느리니 주의하라
  • 위에서 본 바로는 문자열 연결 연산을 하게 되면 StringBuilder 로 변환되어 연결 연산을 수행한다고 하지 않았나 ?

까보자.

  • 눈으로 확인해봐야 겠습니다.

void testString() {
    long startTime = System.currentTimeMillis();
    String result = "";
    for(int i=0; i<100000; i++){
      result += "abcd";
    }
    long endTime = System.currentTimeMillis();
    System.out.println("걸린 시간 : " + (float)(endTime-startTime)/1000 + "초");
  }

void testStringBuilder() {
    long startTime = System.currentTimeMillis();
    StringBuilder builder = new StringBuilder(4 * 100000);
    for(int i=0; i<100000; i++){
      builder.append("abcd");
    }
    String result = builder.toString();
    long endTime = System.currentTimeMillis();
    System.out.println("걸린 시간 : " + (float)(endTime-startTime)/1000 + "초");
  }

//걸린 시간 : 18.801초
//걸린 시간 : 0.002초

 

  • 아래는 for문 부근에 대한 각각의 바이트 코드입니다. (정답은 간단했습니다..)
  • StringBuilder를 내부적으로 사용하긴 하지만 매번 새로 생성되어 append() 연산을 한 후 다시 String으로 변환되는 방식 입니다.
//testString()
			17: new           #7                  // class java/lang/StringBuilder
      20: dup
      21: invokespecial #8                  // Method java/lang/StringBuilder."<init>":()V
      24: aload_3
      25: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      28: ldc           #10                 // String abcd
      30: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      33: invokevirtual #11                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      36: astore_3
      37: iinc          4, 1
      40: goto          10


//testStringBuilder()
			21: if_icmpge     37
      24: aload_3
      25: ldc           #10                 // String abcd
      27: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      30: pop
      31: iinc          4, 1
      34: goto          17

관련글

 

[Java] String 객체는 어떻게 저장될까?

Java의 String 객체 저장 방법 사실 주제 자체는 굉장히 추상적입니다. 어떤 Scope 에서 이야기 하느냐에 따라서 많이 달라질 것 같네요 본 글에서는 다소 고수준에서 String Literal / String Object를 비교

jinseongsoft.tistory.com

 

반응형
댓글