먼저 세 가지 특성을 기억해야 합니다.
문자열 문자열 상수
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;
위의 성능 비용이 실제로 발생합니까? 문자열 접합이 그렇게 일반적으로 사용되는 경우, 특별한 처리 최적화는 없습니까? 대답은 '예'입니다. 이 최적화는 JVM에서 수행됩니다. .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); } }
javac Concatenation.java
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 }
五、仅靠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中文网!