Java에서 String, StringBuffer, StringBuilder 클래스의 성능 상세 분석

우리는 먼저 세 가지 특징을 기억해야 한다.
  • String 문자열 상수
  • StringBuffer 문자열 변수(스레드 보안)
  • StringBuilder 문자열 변수(비스레드 보안)
  • 1. 정의
    API를 보면 String, StringBuffer, StringBuilder는 모두 CharSequence 인터페이스를 실현했다. 문자열과 관련이 있지만 처리 메커니즘은 다르다.
  • String: 변경할 수 없는 양입니다. 즉, 생성된 후에는 수정할 수 없습니다
  • StringBuffer: 가변 문자열 서열입니다. String과 마찬가지로 메모리에 저장된 것은 모두 질서정연한 문자열 서열 (char 유형의 수조) 입니다. 다른 점은 StringBuffer 대상의 값이 모두 가변적이라는 것입니다
  • StringBuilder: StringBuffer 클래스와 기본적으로 같고 모두 가변 문자열 교환 서열이다. 다른 점은 StringBuffer는 라인이 안전하고 StringBuilder는 라인이 안전하지 않다는 것이다.성능에 있어서 String 클래스의 조작은 새로운 String 대상을 생성하는 것이고 String Builder와 String Buffer는 문자수 그룹의 확장일 뿐이기 때문에 String 클래스의 조작은 String Buffer와 String Builder보다 훨씬 느리다
  • 2. 장면 사용
    String 클래스 사용 장면: 문자열이 자주 변하지 않는 장면에서 String 클래스를 사용할 수 있습니다. 예를 들어 상수의 성명, 소량의 변수 연산 등입니다.
    StringBuffer 클래스를 사용하는 장면: 문자열 연산(결합, 교체, 삭제 등)이 빈번하고 다중 스레드 환경에서 실행될 때 XML 해석, HTTP 매개 변수 해석, 봉인 등 StringBuffer를 사용할 수 있습니다.
    StringBuilder 클래스를 사용하는 장면: 문자열 연산(예를 들어 조립, 교체, 삭제 등)이 빈번하고 단일 라인에서 실행되는 환경에서 SQL 문장의 조립, JSON 봉인 등 StringBuilder를 사용할 수 있습니다.
    3. 분석
    간단하게 말하면 String 유형과 String Buffer 유형의 주요 성능 차이는 String이 변할 수 없는 대상이기 때문에 String 유형을 변경할 때마다 새로운 String 대상을 생성한 다음에 바늘을 새로운 String 대상으로 가리키는 것과 같다.따라서 내용을 자주 바꾸는 문자열은 String을 사용하지 않는 것이 좋다. 왜냐하면 매번 대상을 생성할 때마다 시스템 성능에 영향을 미치기 때문이다. 특히 메모리에 인용이 없는 대상이 많으면 JVM의 GC가 작업을 시작하기 때문에 그 속도는 상당히 느릴 것이다.
    StringBuffer 클래스를 사용하면 결과가 달라집니다. 매번 결과는 StringBuffer 대상 자체를 조작하는 것이지 새로운 대상을 생성하고 대상 인용을 바꾸는 것이 아닙니다.따라서 일반적인 상황에서 StringBuffer, 특히 문자열 대상이 자주 바뀌는 경우를 추천합니다.특정한 상황에서 String 대상의 문자열 결합은 JVM에 의해 StringBuffer 대상의 결합으로 해석되기 때문에 이런 때 String 대상의 속도는 StringBuffer 대상보다 느리지 않다. 특히 다음과 같은 문자열 대상 생성에서 String 효율은 StringBuffer보다 훨씬 빠르다.
    
    String S1 = “This is only a" + “ simple" + “ test";
    StringBuffer Sb = new StringBuilder(“This is only a").append(“ simple").append(“ test");
    
    String S1 대상을 생성하는 속도가 너무 빠르다는 것을 놀라게 될 것이다. 이때 String Buffer는 속도적으로 전혀 우세하지 않다는 것을 알게 될 것이다.사실 이것은 JVM의 수작이다. JVM의 눈에는 이것이
    
    String S1 = “This is only a" + “ simple" + “test"; 
    사실:
    
    String S1 = “This is only a simple test";
    
    그러니까 시간이 많이 필요 없지.그러나 여기서 주의해야 할 것은 문자열이 다른 String 대상에서 왔다면 속도가 그렇게 빠르지 않다는 것이다. 예를 들어 다음과 같다.
    
    String S2 = "This is only a";
    String S3 = "simple";
    String S4 = "test";
    String S1 = S2 +S3 + S4;
    
    이럴 때 JVM은 규칙적으로 원래 방식대로 합니다.
    4. JVM의 최적화된 처리
    정말 위의 성능 대가가 있을까요? 문자열 연결이 이렇게 자주 사용되는데 특별한 처리 최적화가 없습니까? 답은 있습니다. 이 최적화는 JVM에서 컴파일됩니다.java가bytecode에 도착했을 때.
    Java 프로그램을 실행하려면 컴파일할 때와 실행할 때 두 시기를 거쳐야 한다.컴파일할 때 Java JVM(Compiler)은 자바 파일을 바이트 코드로 변환합니다.런타임 시 JVM(JVM)이 컴파일을 실행할 때 생성된 바이트 코드입니다.이러한 두 시기를 통해 자바는 이른바 컴파일링을 하고 곳곳에서 운행했다.
    우리는 컴파일러가 어떤 최적화를 했는지 실험해 보자. 우리는 성능 대가가 발생할 수 있는 코드를 제조한다.
    
    public class Concatenation {
     public static void main(String[] args) {
       String userName = "Andy";
       String age = "24";
       String job = "Developer";
       String info = userName + age + job;
       System.out.println(info);
     }
    }
    
    네, Concatenation.java를 컴파일해 보세요.Concatenation을 얻다.class
    
    javac Concatenation.java
    
    그리고 우리는 javap를 사용하여 컴파일된 Concatenation을 역컴파일합니다.class 파일.javap -c Concatenation.만약javap 명령을 찾지 못하면, javap가 있는 디렉터리를 환경 변수에 추가하거나 javap의 전체 경로를 사용하는 것을 고려하십시오.
    
    17:22:04-androidyue~/workspace_adt/strings/src$ javap -c Concatenation
    Compiled from "Concatenation.java"
    public class Concatenation {
     public Concatenation();
      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 Andy
        2: astore_1
        3: ldc      #3         // String 24
        5: astore_2
        6: ldc      #4         // String Developer
        8: astore_3
        9: new      #5         // class java/lang/StringBuilder
       12: dup
       13: invokespecial #6         // Method java/lang/StringBuilder."<init>":()V
       16: aload_1
       17: invokevirtual #7         // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
       20: aload_2
       21: invokevirtual #7         // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
       24: aload_3
       25: invokevirtual #7         // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
       28: invokevirtual #8         // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
       31: astore    4
       33: getstatic   #9         // Field java/lang/System.out:Ljava/io/PrintStream;
       36: aload     4
       38: invokevirtual #10         // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       41: return
    }
    
    
    그 중에서ldc,astore 등은 자바 바이트 코드의 지령으로 어셈블리 지령과 유사하다.다음 설명에서는 Java 관련 컨텐트를 사용하여 설명합니다.우리는 위에 많은 StringBuilder가 있는 것을 볼 수 있지만, 우리는 자바 코드에 표시된 호출이 없다. 이것이 바로 자바 JVM이 최적화한 것이다. 자바 JVM이 문자열 호출을 만났을 때 StringBuilder 대상, 뒤에 있는 호출을 만들 것이다. 실제로는 StringBuilder 대상을 호출하는 append 방법이다.이렇게 하면 우리 위에서 걱정하는 문제가 없을 것이다.
    5. JVM만으로 최적화?
    JVM이 최적화를 도와줬으니 JVM의 최적화만으로도 충분하지 않겠습니까? 물론 아닙니다.
    다음은 최적화되지 않은 성능이 낮은 코드를 보겠습니다.
    
    public void implicitUseStringBuilder(String[] values) {
     String result = "";
     for (int i = 0 ; i < values.length; i ++) {
       result += values[i];
     }
     System.out.println(result);
    }
    
    javac 컴파일, javap로 보기
    
    public void implicitUseStringBuilder(java.lang.String[]);
      Code:
        0: ldc      #11         // String 
        2: astore_2
        3: iconst_0
        4: istore_3
        5: iload_3
        6: aload_1
        7: arraylength
        8: if_icmpge   38
       11: new      #5         // class java/lang/StringBuilder
       14: dup
       15: invokespecial #6         // Method java/lang/StringBuilder."<init>":()V
       18: aload_2
       19: invokevirtual #7         // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
       22: aload_1
       23: iload_3
       24: aaload
       25: invokevirtual #7         // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
       28: invokevirtual #8         // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
       31: astore_2
       32: iinc     3, 1
       35: goto     5
       38: getstatic   #9         // Field java/lang/System.out:Ljava/io/PrintStream;
       41: aload_2
       42: invokevirtual #10         // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       45: return
    
    그중 8: if_icmpge38과 35:goto5는 하나의 순환을 구성한다.8: if_icmpge 38은 JVM 작업 스택의 정수 대비가 (i 그러나 이 안에 중요한 것이 하나 있다. 바로 StringBuilder 대상의 생성이 순환 사이에서 발생한다는 것이다. 즉, 몇 번의 순환이 몇 개의 StringBuilder 대상을 만드는지 의미하는 것이다. 이렇게 하면 현저히 좋지 않다.적나라하게 수준 낮은 코드네.
    조금만 최적화하면 순간적으로 압박을 증가시킵니다.
    
    public void explicitUseStringBuider(String[] values) {
     StringBuilder result = new StringBuilder();
     for (int i = 0; i < values.length; i ++) {
       result.append(values[i]);
     }
    }
    
    컴파일된 정보
    
    public void explicitUseStringBuider(java.lang.String[]);
      Code:
        0: new      #5         // class java/lang/StringBuilder
        3: dup
        4: invokespecial #6         // Method java/lang/StringBuilder."<init>":()V
        7: astore_2
        8: iconst_0
        9: istore_3
       10: iload_3
       11: aload_1
       12: arraylength
       13: if_icmpge   30
       16: aload_2
       17: aload_1
       18: iload_3
       19: aaload
       20: invokevirtual #7         // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
       23: pop
       24: iinc     3, 1
       27: goto     10
       30: return
    
    위에서 보듯이 13:if_icmpge 30과 27: goto 10은 loop 순환을 구성하고 0: new #5는 순환 밖에 있기 때문에 StringBuilder를 여러 번 만들지 않습니다.
    전반적으로 말하자면, 우리는 순환체에서 스텔스나 현식으로 StringBuilder를 만드는 것을 최대한 피해야 한다.그래서 코드를 어떻게 컴파일하고 내부에서 어떻게 실행하는지 아는 사람들은 코드의 등급이 비교적 높다.
    결론
    대부분의 경우 StringBuffer > String
    Java.lang. StringBuffer는 스레드가 안전한 가변 문자 시퀀스입니다.String과 유사한 문자열 버퍼이지만 수정할 수 없습니다.임의의 시간에 특정한 문자 서열을 포함하지만, 어떤 방법으로 호출하면 이 서열의 길이와 내용을 바꿀 수 있다.프로그램에서 문자열 버퍼를 다중 스레드에 안전하게 사용할 수 있습니다.그리고 필요할 때 이 방법들을 동기화할 수 있기 때문에 임의의 특정한 실례상의 모든 조작은 마치 직렬 순서로 발생하는 것 같다. 이 순서는 관련된 모든 라인에서 진행되는 방법과 호출 순서가 일치한다.
    StringBuffer의 주요 동작은 append와 insert 방법입니다. 이 방법을 다시 불러와서 임의의 형식의 데이터를 받아들일 수 있습니다.모든 방법은 주어진 데이터를 문자열로 효과적으로 변환한 다음 문자열의 문자를 문자열 버퍼에 추가하거나 삽입할 수 있습니다.append 방법은 항상 이 문자를 버퍼의 끝에 추가합니다.insert 방법은 지정한 점에 문자를 추가합니다.
    예를 들어 z가 현재 내용이 "start"문자열 버퍼 대상을 인용한다면, 이 방법은 z.append ("le") 를 호출하면 문자열 버퍼에 "startle"(누적) 을 포함합니다.z.insert (4, "le") 는 문자열 버퍼를 "starlet"을 포함하도록 변경합니다.
    대부분의 경우 StringBuilder > StringBuffer
    java.lang. StringBuilder의 가변 문자 시퀀스는 JAVA 5.0에 추가되었습니다.이 유형은 StringBuffer와 호환되는 API를 제공하지만 동기화를 보장하지 않기 때문에 장면을 사용하는 것은 단일 루트입니다.이 클래스는 문자열 버퍼가 한 라인에서 사용될 때 사용되는 StringBuffer의 간단한 교체로 설계되었습니다.가능하다면, 이 종류를 우선적으로 사용하는 것을 권장합니다. 왜냐하면 대부분의 실현에서 StringBuffer보다 빠르기 때문입니다.양자의 사용 방법은 기본적으로 같다.

    좋은 웹페이지 즐겨찾기