> Java > java지도 시간 > Java의 String, StringBuffer 및 StringBuilder 클래스 성능에 대한 자세한 분석

Java의 String, StringBuffer 및 StringBuilder 클래스 성능에 대한 자세한 분석

高洛峰
풀어 주다: 2017-01-22 11:25:12
원래의
1418명이 탐색했습니다.

먼저 세 가지 특성을 기억해야 합니다.

문자열 문자열 상수

StringBuffer 문자열 변수(스레드 안전)

StringBuilder 문자열 변수(비스레드) 보안)

1. 정의
API를 보면 String, StringBuffer 및 StringBuilder는 모두 문자열과 관련되어 있지만 처리 메커니즘이 다릅니다.

문자열: 불변 수량입니다. 즉, 생성된 후에는 수정할 수 없습니다.

StringBuffer: 가변 문자열 시퀀스입니다. String과 마찬가지로 정렬된 문자열 시퀀스(char 유형의 배열)를 메모리에 저장합니다. 차이점은 StringBuffer 개체의 값이 가변적이라는 것입니다.

StringBuilder: 기본적으로 StringBuffer 클래스와 동일합니다. 둘 다 가변 문자이자 문자열 시퀀스입니다. 차이점은 StringBuffer는 스레드로부터 안전하고 StringBuilder는 스레드로부터 안전하지 않다는 것입니다. 성능 측면에서 String 클래스의 작업은 새로운 String 개체를 생성하는 것이고 StringBuilder 및 StringBuffer는 문자 배열의 확장일 뿐이므로 String 클래스의 작업은 StringBuffer 및 StringBuilder보다 훨씬 느립니다.

2. 사용 시나리오
String 클래스 사용 시나리오: String 클래스는 상수 선언이나 소수의 변수 연산과 같이 문자열이 자주 변경되지 않는 시나리오에서 사용할 수 있습니다.
StringBuffer 클래스 사용 시나리오: 문자열 작업(예: 접합, 교체, 삭제 등)을 자주 수행하고 멀티 스레드 환경에서 실행하는 경우 XML 구문 분석, HTTP 매개 변수와 같은 StringBuffer 사용을 고려할 수 있습니다. 구문 분석 및 캡슐화.
StringBuilder 클래스 사용 시나리오: 문자열 작업(예: 접합, 교체, 삭제 등)을 자주 수행하고 단일 스레드 환경에서 실행하는 경우 SQL 문 조합과 같은 StringBuilder 사용을 고려할 수 있습니다. JSON 캡슐화 등

3. 분석
간단히 말하면 String 유형과 StringBuffer 유형의 주요 성능 차이는 String은 불변 객체이므로 String 유형이 변경될 때마다 실제로 생성되는 것과 동일합니다. 새 String 개체를 만든 다음 포인터를 새 String 개체에 지정합니다. 따라서 내용을 자주 변경하는 문자열에는 String을 사용하지 않는 것이 가장 좋습니다. 객체가 생성될 때마다 시스템 성능에 영향을 미치기 때문입니다. 특히 메모리에 참조되지 않은 객체가 너무 많으면 JVM의 GC가 시작됩니다. 작동하면 속도가 확실히 느려질 것입니다.

StringBuffer 클래스를 사용하면 결과가 달라집니다. 각 결과는 새 개체를 생성한 다음 개체 참조를 변경하는 대신 StringBuffer 개체 자체에서 작동합니다. 따라서 일반적으로 특히 문자열 개체가 자주 변경되는 경우에는 StringBuffer를 사용하는 것이 좋습니다. 일부 특수한 경우에는 String 객체의 문자열 연결이 실제로 JVM에 의해 StringBuffer 객체의 연결로 해석되므로 이러한 경우 String 객체의 속도는 StringBuffer 객체의 속도보다 느리지 않으며 특히 다음 문자열 객체는 다음과 같습니다. 생성된 문자열 효율성은 StringBuffer:

String S1 = “This is only a" + “ simple" + “ test";
StringBuffer Sb = new StringBuilder(“This is only a").append(“ simple").append(“ test");
로그인 후 복사

String S1 개체 생성 속도가 너무 빠르다는 사실에 놀랄 것입니다. 이번에는 StringBuffer가 실제로 속도 면에서 전혀 이점이 없습니다. 사실 이는 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;
로그인 후 복사

This At this 시간이 지나면 JVM은 원래 방식으로 작동합니다.

4. 심층적인 JVM 최적화 처리

위의 성능 비용이 실제로 발생합니까? 문자열 접합이 그렇게 일반적으로 사용되는 경우, 특별한 처리 최적화는 없습니까? 대답은 '예'입니다. 이 최적화는 JVM에서 수행됩니다. .java를 바이트코드로 컴파일합니다.

Java 프로그램을 실행하려면 컴파일 시간과 런타임이라는 두 가지 기간을 거쳐야 합니다. 컴파일하는 동안 Java JVM(컴파일러)은 Java 파일을 바이트코드로 변환합니다. 런타임 시 JVM(Java Virtual Machine)은 컴파일 시 생성된 바이트코드를 실행합니다. 이 두 기간을 통해 Java는 소위 컴파일을 한 곳에서 달성하고 어디에서나 실행됩니다.

컴파일 중에 어떤 최적화가 이루어졌는지 실험해보고 성능 저하가 있을 수 있는 코드 조각을 만들어 보겠습니다.

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를 컴파일합니다. Get Concatenation.class

javac Concatenation.java
로그인 후 복사

그런 다음 javap를 사용하여 컴파일된 Concatenation.class 파일을 디컴파일합니다. javap -c 연결. 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가 있음을 알 수 있지만 Java 코드에서는 이를 명시적으로 호출하지 않습니다. 이는 JavaJVM에서 수행한 최적화입니다. JavaJVM이 문자열 접합을 발견하면 후속 접합이 생성됩니다. 사실, StringBuilder 개체의 추가 메서드를 호출하는 것입니다. 이렇게 하면 위에서 걱정했던 문제가 발생하지 않습니다.

五、仅靠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_icmpge 38 和35: goto 5构成了一个循环。8: if_icmpge 38的意思是如果JVM操作数栈的整数对比大于等于(i < values.length的相反结果)成立,则跳到第38行(System.out)。35: goto 5则表示直接跳到第5行。

但是这里面有一个很重要的就是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 要快。两者的使用方法基本相同。

更多详细分析Java中String、StringBuffer、StringBuilder类的性能相关文章请关注PHP中文网!

원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿