2012年5月14日月曜日

StringとStringBuffer

ログを出力する場合など、文字列を処理しますね。その際、どのように文字列を扱っているでしょうか。Stringで"+"を使って連結するケースが多いのではないでしょうか。理由はカンタンだから、でしょうね。これまでのサンプルもそうしてきました。

でも、そういう文字列処理自体があまりなかったり、実行される回数が少なければ特に問題は発生しないかもしれませんが、実はStringは効率がよくありません。効率よく処理するにはStringBufferを使います。

以下のサンプルでは、文字列の連結を200回繰り返す処理を、StringとStringBufferで実装しています。かかる時間とGC(ガベージ・コレクション)がどれだけ動くか比較してみてください。

なお、文字列連結前にSystem.gc()を2回呼び出していますが、これはなくても構いません。GCが動こうとしたタイミングでサンプルを実行すると、意図しないGCのログが出るので先に実行させています。

StringBufferTest1aActivity.java
package jp.co.triware.samples.StringBufferTest1a;

import java.util.Calendar;
import java.util.Date;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

public class StringBufferTest1aActivity extends Activity {
    private static final String TAG = "StringBufferTest1aActivity";

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        final int count = 200;
        Button btnString = (Button)findViewById(R.id.string_btn);
        Button btnStringBuffer = (Button)findViewById(R.id.string_buffer_btn);
        final TextView tvResult = (TextView)findViewById(R.id.result_tv);

        btnString.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                System.gc();
                System.gc();
                String buf = "";
                Log.d(TAG, "String: Start");
                Calendar c1 = Calendar.getInstance();
                for (int i = 0; i < count; i ++) {
                    buf += "THE QUICK BROWN FOX #" + i + " JUMPS OVER THE LAZY DOG #" + i + ".\n";
                }
                Calendar c2 = Calendar.getInstance();
                long diff = c2.getTimeInMillis() - c1.getTimeInMillis();
                Log.d(TAG, "String: End");
                String msg = "String: " + diff + " ms";
                Log.d(TAG, msg);
                tvResult.setText(msg);
            }
        });

        btnStringBuffer.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                System.gc();
                System.gc();
                StringBuffer buf = new StringBuffer();
                Log.d(TAG, "StringBuffer: Start");
                tvResult.setText("StringBuffer: Start");
                Calendar c1 = Calendar.getInstance();
                for (int i = 0; i < count; i ++) {
                    buf.append("THE QUICK BROWN FOX #");
                    buf.append(i);
                    buf.append(" JUMPS OVER THE LAZY DOG #");
                    buf.append(i);
                    buf.append(".\n");
                }
                Calendar c2 = Calendar.getInstance();
                long diff = c2.getTimeInMillis() - c1.getTimeInMillis();
                Log.d(TAG, "StringBuffer: End");
                StringBuffer msg = new StringBuffer();
                msg.append("StringBuffer: ");
                msg.append(diff);
                msg.append(" ms");
                Log.d(TAG, msg.toString());
                tvResult.setText(msg);
            }
        });
    }
}

main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <Button
        android:id="@+id/string_btn"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="String"
        />
    <Button
        android:id="@+id/string_buffer_btn"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="StringBuffer"
        />

    <ScrollView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        >
        <TextView
            android:id="@+id/result_tv"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            />
    </ScrollView>
</LinearLayout>

Androidプロジェクトの設定
プロジェクト名:StringBufferTest1a
アプリケーション名:StringBufferTest1a
パッケージ名:jp.co.triware.samples.StringBufferTest1a
アクティビティーの作成:StringBufferTest1aActivity
ビルドターゲットや最小SDKバージョンは、お使いの開発環境に合わせて設定してください。

実行結果

まずは「String」を実行してみましょう。


D/dalvikvm(529): GC_EXPLICIT freed 53K, 53% free 2569K/5379K, external 884K/1038K, paused 48ms
D/dalvikvm(529): GC_EXPLICIT freed 1K, 53% free 2567K/5379K, external 884K/1038K, paused 50ms
D/StringBufferTest1aActivity(529): String: Start
D/dalvikvm(529): GC_CONCURRENT freed 423K, 54% free 2596K/5639K, external 884K/1038K, paused 5ms+4ms
D/dalvikvm(529): GC_CONCURRENT freed 509K, 55% free 2615K/5767K, external 884K/1038K, paused 5ms+4ms
D/dalvikvm(529): GC_CONCURRENT freed 486K, 55% free 2621K/5767K, external 884K/1038K, paused 5ms+4ms
D/dalvikvm(529): GC_CONCURRENT freed 485K, 55% free 2630K/5767K, external 884K/1038K, paused 5ms+4ms
D/dalvikvm(529): GC_CONCURRENT freed 473K, 55% free 2637K/5767K, external 884K/1038K, paused 5ms+4ms
D/dalvikvm(529): GC_CONCURRENT freed 435K, 55% free 2644K/5767K, external 884K/1038K, paused 5ms+4ms
D/dalvikvm(529): GC_CONCURRENT freed 474K, 55% free 2635K/5767K, external 884K/1038K, paused 5ms+4ms
D/dalvikvm(529): GC_CONCURRENT freed 458K, 55% free 2639K/5767K, external 884K/1038K, paused 5ms+4ms
D/dalvikvm(529): GC_CONCURRENT freed 432K, 55% free 2644K/5767K, external 884K/1038K, paused 5ms+3ms
D/dalvikvm(529): GC_CONCURRENT freed 457K, 55% free 2647K/5767K, external 884K/1038K, paused 5ms+4ms
D/dalvikvm(529): GC_CONCURRENT freed 419K, 54% free 2668K/5767K, external 884K/1038K, paused 5ms+3ms
D/dalvikvm(529): GC_CONCURRENT freed 502K, 55% free 2673K/5831K, external 884K/1038K, paused 5ms+3ms
D/dalvikvm(529): GC_CONCURRENT freed 478K, 54% free 2687K/5831K, external 884K/1038K, paused 5ms+4ms
D/dalvikvm(529): GC_CONCURRENT freed 478K, 55% free 2662K/5831K, external 884K/1038K, paused 5ms+4ms
D/dalvikvm(529): GC_FOR_MALLOC freed 548K, 56% free 2592K/5831K, external 884K/1038K, paused 30ms
D/StringBufferTest1aActivity(529): String: End
D/StringBufferTest1aActivity(529): String: 2235 ms
2235ミリ秒かかって、その間にGCが15回も動いています。

次は「StringBuffer」です。



D/dalvikvm(529): GC_EXPLICIT freed 241K, 56% free 2572K/5831K, external 884K/1038K, paused 51ms
D/dalvikvm(529): GC_EXPLICIT freed <1K, 56% free 2572K/5831K, external 884K/1038K, paused 44ms
D/StringBufferTest1aActivity(529): StringBuffer: Start
D/StringBufferTest1aActivity(529): StringBuffer: End
D/StringBufferTest1aActivity(529): StringBuffer: 297 ms
297ミリ秒で終わってしまいました。GCも動いていません。

StringのほうはGCが動いているために遅いとも言えるかもしれませんが、いずれにしてもStringBufferのほうが効率が良いことがわかりますね。

実行環境によってはあまり差がなかったりGCが動かなかったりするかもしれません。エミュレータでは差がはっきり出ますが、実際の端末では、あまり差がわからないかもしれません。その場合はループ回数を増やして試してみてください。実行時間に差が出ることがわかると思います。

0 件のコメント:

コメントを投稿