Java文字列の性能最適化

7041 ワード

ベースタイプがStringに変換されます。
プログラムでは時々他のタイプをStringに変換する必要があります。いくつかの基礎タイプの値があります。文字列をつなぎ合わせる時、もし2つ以上の基礎タイプの値が前に置く必要があるなら、最初の値をStringに明示的に変換する必要があります。そうでなければ、System.out.println(1+'a')は98を出力します。もちろんです。String.valueOfのセットでこれを完成することができますが、もっといい方法があれば、コードを少なく打てば、誰がこのように書きますか?
基本タイプの前に串を付けるのが一番簡単です。この表式の結果はStringで、その後は任意に文字列のつなぎ合わせ操作ができます。コンパイラはそれらの基本タイプをStringに自動的に変換します。
残念なことに、これは最悪の実現方法です。なぜかを知りたいなら、まずこの文字列を紹介して、Javaでどうやって処理しますか?文字列(文字どおりの定数や変数、メソッド呼び出しの結果も)の後に+号が付いている場合、後にはどのタイプの表現がありますか?

string_exp + any_exp
Javaコンパイラはそれを:

new StringBuilder().append( string_exp ).append( any_exp ).toString()
表式に複数の+号があると、後からいくつかのStringBuider.appedの呼び出しが多くなります。最後はtoStering方法です。
StringBuiderという構造方法では、16文字のメモリバッファ領域が割り当てられます。したがって、後のスティッチングの文字が16を超えないなら、SteringBuiderはメモリを再割り当てする必要はありませんが、16文字を超えるとSteringBuiderはバッファを拡張します。最後にStringメソッドを呼び出すと、String Builderの中のバッファをコピーして、新しくStringオブジェクトを生成して返します。
これは、基本的なタイプがStringに変換された時、最悪の場合は、作成しなければならないことを意味します。一つのStringBuiderオブジェクト、一つのchar[16]配列、一つのStringオブジェクト、一つの入力値を格納できるchar[行列]。String.valueOfを使うと、少なくともStringBuiderの対象が無くなります。
ある時は基本タイプを変える必要がないかもしれません。たとえば、文字列を解析しています。単一引用符で区切られています。最初はこう書いたかもしれません。

final int nextComma = str.indexOf("'");
またはそうです

final int nextComma = str.indexOf('\'');
プログラムの開発が完了しました。需要が変更されました。任意のセパレータをサポートする必要があります。もちろん、あなたの第一の反応は、このセパレータを一つのStringオブジェクトに保存し、String.indexOf方法を使用して分割することです。あらかじめ配置されているセパレータがあると、m_に置いておきます。separatorフィールドにあります。解析したコードはこうなるはずです。

private static List<String> split( final String str )
{
    final List<String> res = new ArrayList<String>( 10 );
    int pos, prev = 0;
    while ( ( pos = str.indexOf( m_separator, prev ) ) != -1 )
    {
        res.add( str.substring( prev, pos ) );
        prev = pos + m_separator.length(); // start from next char after separator
    }
    res.add( str.substring( prev ) );
    return res;
}
しかし、後にこの区切り記号は1文字しかないことが分かります。初期化の時はStering m_をseparatorをchar m_に変更しました。separator、そしてsetterの方法も一緒に変えました。解析の方法はあまり大きく変えないでほしいです。

private static List<String> split2( final String str )
{
    final List<String> res = new ArrayList<String>( 10 );
    int pos, prev = 0;
    while ( ( pos = str.indexOf("" + m_separatorChar, prev ) ) != -1 )
    {
        res.add( str.substring( prev, pos ) );
        prev = pos + 1; // start from next char after separator
    }
    res.add( str.substring( prev ) );
    return res;
}
ご覧のように、indexOfメソッドの呼び出しは変更されましたが、それでも新しい文字列を作成して転送します。もちろん、このようにするのは間違いです。もう一つのindexOf方法はStringタイプではなくcharタイプを受信するからです。それを使って書き換えます。

private static List<String> split3( final String str )
{
    final List<String> res = new ArrayList<String>( 10 );
    int pos, prev = 0;
    while ( ( pos = str.indexOf(m_separatorChar, prev ) ) != -1 )
    {
        res.add( str.substring( prev, pos ) );
        prev = pos + 1; // start from next char after separator
    }
    res.add( str.substring( prev ) );
    return res;
}
上記3つの実装を用いてテストを行い、「abc,def,ghi,jkl,mno,pqr,stu,vwx,yz」という串を1000万回解析します。以下はJava 6_です41と7_15の運転時間。Java 7は、そのString.substring方法の線形複雑さのため、かえって運転時間が増えました。これについてはここの資料を参考にしてもいいです。
簡単な再構成で、文字列の分割に必要な時間が大幅に短縮されていることが見られます。
スプリット
スプリット2
スプリット3
Java 6
4.65 sec
10.34 sec
3.8 sec
Java 7
6.72 sec
10.34 sec
3.8 sec
文字列スティッチング
本文はもちろん、文字列スティッチングの他の2つの方法を全く話さないわけにはいかない。一つはString.co ncatで、これはあまり使われません。その内部には実はchar[]が割り当てられています。長さはスティッチング後の文字列の長さです。文字列のデータを中にコピーして、最後にプライベートな構造方法を使って新しい文字列を生成しました。この構造方法では、char[]をコピーしません。この方法で呼び出します。二つのオブジェクトを作成しました。一つはString自身です。もう一つはその内部のchar[]です。不幸なことに、二つの文字列だけをつなぎ合わせないと、この方法はもっと効率的です。
もう一つの方法はStringBuider類とその一連のアプリを使うことです。スペルの値がたくさんあるなら、この方法は一番早いです。それはJava 5に初めて導入されて、StringBufferの代わりに使われます。これらの主な違いはStringBurerがスレッド安全であるのに対し、StringBuiderはそうではない。しかし、よく合併される連結文字列ですか?
テストでは、私たちは0から100000までの数を全部つなぎ合わせました。それぞれString.co ncat、+操作符、そしてSteringBuiderを使いました。コードは以下の通りです。

String res = ""; 
for ( int i = 0; i < ITERS; ++i )
{
    final String s = Integer.toString( i );
    res = res.concat( s ); //second option: res += s;
}        
//third option:        
StringBuilder res = new StringBuilder(); 
for ( int i = 0; i < ITERS; ++i )
{
    final String s = Integer.toString( i );
    res.append( s );
}
String.co ncat

StringBuider.apped
10.145 sec
42.67 sec
0.012 sec
その結果,O(n)の時間複雑さは,明らかにO(n 2)よりもはるかに強いことが明らかになった。でも実際の仕事にはたくさんの+操作符が使われます。とても便利ですから。この問題を解決するために、Java 6 udate 20から、Otimize StringContactスイッチが導入されました。Java 7_にあります02とJava 7_15の間のバージョンはデフォルトで開いています。41の中でもデフォルトで閉じています)ので、手動で開けなければならないかもしれません。他の-XXのオプションと同じで、ドキュメントもかなり悪いです。
Optimze String concation operation where possible.(Introducd in Java 6 Update 20)
私たちはOracleのエンジニアがこのオプションを実現すると仮定した時、最善を尽くしたでしょう。いくつかのStringBuiderをつなぎ合わせるロジックをString.co ncatのようなものに変えたと言われています。まず適当な大きさのcharを作ってからコピーします。最後にStringが生成されます。これらのネストされたつなぎ合わせ操作は、それもサポートされているかもしれません。このオプションをオンにしてテストしたところ、+号の性能はString.co ncatに非常に近いことが分かりました。
String.co ncat

StringBuider.apped
10.19 sec
10.722 sec
0.013 sec
私たちはもう一つのテストをします。前に述べたように、デフォルトのStringBuiderコンストラクタは16文字のバッファ領域を割り当てています。17番目の文字を追加する必要がある場合、このバッファは拡張されます。100から100000までの数字をそれぞれ「1234578901234」の後ろに追加します。結果ストリングの長さは17から20の間であるべきであるため、デフォルト+オペレータの実装にはStringBuiderのサイズ変更が必要となる。対照的に、私たちはもう一つのテストをします。ここで直接StringBuiderを作成します。(21)バッファは十分大きいです。再調整しないでください。

final String s = BASE + i;
final String s = new StringBuilder( 21 ).append( BASE ).append( i ).toString();
このオプションをオンにしないと、+号の実現は、明示的なシュリング・バーナーダーの実現時間よりも半分多くなります。このオプションをオンにしたら、両方の結果は同じです。でも面白いのは、StringBuiderの実現自体でも、スイッチを入れたら速度が速くなるということです。
+,スイッチオフ
+,スイッチオン
new StrigBuider(21)、スイッチオフ
new StrigBuider(21)スイッチオン
0.958 sec
0.494 sec
0.636 sec
0.494 sec
締め括りをつける
  • 文字列に変換する場合、「」列の変換は避けるべきである。適切なString.valueOf方法または包装類のtoString方法を使用する。
  • は、できるだけStringBuiderを使用して文字列スティッチングを行います。古いコードをチェックして、差し替えのできるシュリングバッファerも変えます。
  • Java 6 udate 20によって導入された-XX:+Optimize StringContictオプションを使用して文字列スティッチングの性能を向上させます。最近のJava 7のバージョンではデフォルトで開いていますが、Java 6_41はまだオフです。
  • オリジナルの文章の転載は出典を明記してください。
    http://it.deepinmind.com
    英語のテキストリンク
    ブログの更新をすぐに知りたいです。微博に注目してください。
    Java翻訳ステーション