まずは 3 つの特徴を覚えておく必要があります:
String 文字列定数
StringBuffer 文字列変数 (スレッドセーフ)
StringBuilder 文字列変数 (非スレッドセーフ)
1. 定義
API を見ると、 String 、 StringBuffer 、 StringBuilder はすべて CharSequence インターフェイスを実装していることがわかりますが、それらはすべて文字列に関連していますが、その処理メカニズムは異なります。
文字列: 不変の量です。つまり、作成後に変更することはできません。
StringBuffer: String と同様に、メモリに格納される順序付けされた文字列シーケンス (char 型の配列) です。違いは、StringBuffer オブジェクトの値が可変であることです。
StringBuilder: 基本的に StringBuffer クラスと同じですが、どちらも変数文字と文字列シーケンスです。違いは、StringBuffer がスレッドセーフであるのに対し、StringBuilder はスレッドアンセーフであることです。 パフォーマンスの点では、String クラスの操作は新しい String オブジェクトを生成することであり、StringBuilder と StringBuffer は文字配列の単なる展開であるため、String クラスの操作は StringBuffer や StringBuilder よりもはるかに遅くなります。
2. 使用シナリオ
String クラスの使用シナリオ: String クラスは、定数の宣言や少数の変数操作など、文字列が頻繁に変更されないシナリオで使用できます。
StringBuffer クラスを使用するシナリオ: 文字列操作 (スプライシング、置換、削除など) を頻繁に実行し、マルチスレッド環境で実行する場合は、XML 解析、HTTP パラメーター解析、およびカプセル化。
StringBuilder クラスを使用するシナリオ: 文字列操作 (結合、置換、削除など) を頻繁に実行し、シングルスレッド環境で実行する場合は、SQL ステートメントのアセンブル、JSON カプセル化などの StringBuilder の使用を検討できます。 、など。
3. 分析
簡単に言うと、String 型と StringBuffer 型の主なパフォーマンスの違いは、String 型が変更されるたびに、実際には新しい String オブジェクトを生成するのと同じであるということです。次に、ポインタを新しい String オブジェクトにポイントします。したがって、頻繁に内容を変更する文字列には String を使用しないことをお勧めします。オブジェクトが生成されるたびに、特にメモリ内に参照されていないオブジェクトが多すぎると、JVM の GC が開始され、システムのパフォーマンスに影響を及ぼします。動作しますが、速度は間違いなくかなり遅くなります。
StringBuffer クラスを使用する場合、結果は毎回異なります。新しいオブジェクトを生成してオブジェクト参照を変更するのではなく、StringBuffer オブジェクト自体に対する操作になります。したがって、一般に、特に文字列オブジェクトが頻繁に変更される場合には、StringBuffer を使用することをお勧めします。一部の特殊なケースでは、String オブジェクトの文字列連結は、実際には JVM によって StringBuffer オブジェクトの連結として解釈されるため、このような場合、String オブジェクトの速度が StringBuffer オブジェクトの速度よりも遅くなることはなく、特に次の文字列オブジェクトはString S1 オブジェクトの生成速度が単純に速すぎることに驚くでしょう。現時点では、StringBuffer にはまったく利点がありません。スピード。 。実際、これは JVM のトリックであり、JVM から見ると、この
String S1 = “This is only a" + “ simple" + “ test"; StringBuffer Sb = new StringBuilder(“This is only a").append(“ simple").append(“ test");
は実際には次のようになります。 したがって、もちろん、それほど時間はかかりません。ただし、ここで注意しなければならないのは、文字列が別の String オブジェクトからのものである場合、速度はそれほど速くないということです。 例:
String S1 = “This is only a" + “ simple" + “test";
このとき、JVM は元の方法で動作します。する。
4. 詳細な JVM 最適化処理
上記のようなパフォーマンスのコストは本当にありますか? 文字列のスプライシングは非常に一般的に使用されていますが、特別な処理の最適化はありませんか? 答えは「はい」です。この最適化は、JVM が .java をコンパイルするときに実行されます。バイトコード。 Java プログラムを実行するには、コンパイル時と実行時の 2 つの期間を経る必要があります。コンパイル中に、Java JVM (コンパイラー) は Java ファイルをバイトコードに変換します。実行時に、Java 仮想マシン (JVM) はコンパイル時に生成されたバイトコードを実行します。この 2 つの期間を通じて、Java はいわゆるコンパイルを 1 か所で実行し、どこでも実行できるようになりました。 コンパイル中に行われる最適化を試して、パフォーマンスに影響を与える可能性のあるコードを作成してみましょう。String S1 = “This is only a simple test";
String S2 = "This is only a"; String S3 = "simple"; String S4 = "test"; String S1 = S2 +S3 + S4;
次に、javap を使用して、コンパイルされた Concatenation.class ファイルを逆コンパイルします。 javap -c 連結。 javap コマンドが見つからない場合は、javap が存在するディレクトリを環境変数に追加するか、javap へのフルパスを使用することを検討してください。
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); } }
このうち、ldc、astoreなどは、アセンブリ命令と同様のJavaバイトコード命令です。次のコメントでは、説明のために Java 関連のコンテンツを使用します。 上記には多くの StringBuilder があることがわかりますが、Java コードでそれらを明示的に呼び出しているわけではありません。これは、JavaJVM が文字列のスプライシングに遭遇すると、StringBuilder オブジェクトを作成します。実際には、StringBuilder オブジェクトの append メソッドを呼び出します。そうすれば、上で心配したような問題はなくなります。
五、仅靠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中文网!