質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.23%

InputStreamをStringに変換しようとするとOoMが発生

解決済

回答 5

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 1,594

sun-solar-arrow

score 103

InputStreamをStringに変換しようとするとOutOfMemoryErrorが発生します。原因は何でしょうか。発生しないように出来ないでしょうか。
InputStreamはAndroidのプロジェクトのrawフォルダの中にあるバイナリファイル(5.5MB)を次のようにInputStreamに変換したものです。

InputStream is = this.getRawResouces().openRawResouce();//thisは、ActivityのContext

メモリが876MBの環境でテストしています。

ソースコード

readメソッドを使って書いたコード

static String convertInputStreamToString(InputStream is) throws IOException {
    InputStreamReader reader = new InputStreamReader(is);
    StringBuilder builder = new StringBuilder();
    char[] buf = new char[512];
    int numRead;
    while (0 <= (numRead = reader.read(buf))) {
        builder.append(buf, 0, numRead);//oom発生
    }
        buf=null;
    return builder.toString();
    } 
//readLineは使っていません
    static String inputStreamToString(InputStream is){
        BufferedReader reader = null;
        try{
            reader = new BufferedReader(new InputStreamReader(is,"UTF-8"));
        }
        catch (UnsupportedEncodingException e){
            e.printStackTrace();
        }
        StringBuilder sb = new StringBuilder();
        String b = null;
        try{
            while ((b=reader.readLine()) !=null){
                sb.append(b);
            }
        }
        catch (IOException e){

        }
        return sb.toString();
    }

呼び出し元

private void checkVersion() throws Throwable{
        //呼び出し元は、onResume()のonClick()
        InputStream versionStream=this.getResources().openRawResource(R.raw.git);

        String verchecked=Event.convertInputStreamToString(versionStream);
        //ココでreadメソッドを使って書いたコードを使って変換しようとするが、OoM発生、スローされる
        versionStream.close();//closeは書いている
        java.lang.Process p = null;
        TextView vertext=(TextView) findViewById(R.id.version);

        try{
            p=Runtime.getRuntime().exec(verchecked+" version");
            verchecked=null;//vercheckedはもう使わないからnull
            InputStream returnVerison=p.getInputStream();//結果を返す
            p=null;//pはもう使わないからnullを代入
            String ver=Event.convertInputStreamToString(returnVerison);
            returnVerison.close();
            vertext.setText(ver);
            ver=null;
        }catch (IOException e){
            e.printStackTrace();
            vertext.setText("取得失敗: "+e);
        }
        vertext=null;
    }

     protected void onResume(){
            super.onResume();
          //このクラスはMainActivity(最初に呼び出されるActivity)から呼び出される
            Button Update=(Button) findViewById(R.id.UpdateButton);
            Update.setOnClickListener(new OnClickListener(){
                    @Override
                    public void onClick(View p1){
                        try{
                            checkVersion();
                        }catch(OutOfMemoryError e){
                            e.printStackTrace();
                            Toast.makeText(GitUpdate.this,"取得失敗:"+e,Toast.LENGTH_SHORT).show();
                        }
                    }
            });

ヒープ

Android StudioでMemoryを見ました。
画像です。
へこんでいるところがInputStreamを変換したところです。
また、コード内でも確認しました。
MB単位です。
totalMemory(),MaxMemory()
7 ,96

追加情報

別サイトにも前から投稿しています。別サイト:スタックオーバーフローこちらにも目を通しておいてください。

スタックトレース

06-22 20:06:54.681 W/System.err(28046): java.lang.OutOfMemoryError
06-22 20:06:54.681 W/System.err(28046):     at java.lang.AbstractStringBuilder.enlargeBuffer(AbstractStringBuilder.java:94)
06-22 20:06:54.681 W/System.err(28046):     at java.lang.AbstractStringBuilder.append0(AbstractStringBuilder.java:145)
06-22 20:06:54.681 W/System.err(28046):     at java.lang.StringBuilder.append(StringBuilder.java:216)
06-22 20:06:54.681 W/System.err(28046):     at java.lang.ProcessManager.exec(ProcessManager.java:211)
06-22 20:06:54.681 W/System.err(28046):     at java.lang.Runtime.exec(Runtime.java:173)
06-22 20:06:54.681 W/System.err(28046):     at java.lang.Runtime.exec(Runtime.java:246)
06-22 20:06:54.681 W/System.err(28046):     at java.lang.Runtime.exec(Runtime.java:189)
06-22 20:06:54.681 W/System.err(28046):     at com.jimdo.solarand.git.GitUpdate.checkVersion(GitUpdate.java:153)
06-22 20:06:54.681 W/System.err(28046):     at com.jimdo.solarand.git.GitUpdate.access$1000009(GitUpdate.java)
06-22 20:06:54.681 W/System.err(28046):     at com.jimdo.solarand.git.GitUpdate$100000001.onClick(GitUpdate.java:50)
06-22 20:06:54.681 W/System.err(28046):     at android.view.View.performClick(View.java:4487)
06-22 20:06:54.681 W/System.err(28046):     at android.view.View$PerformClick.run(View.java:18746)
06-22 20:06:54.691 W/System.err(28046):     at android.os.Handler.handleCallback(Handler.java:733)
06-22 20:06:54.691 W/System.err(28046):     at android.os.Handler.dispatchMessage(Handler.java:95)
06-22 20:06:54.691 W/System.err(28046):     at android.os.Looper.loop(Looper.java:149)
06-22 20:06:54.691 W/System.err(28046):     at android.app.ActivityThread.main(ActivityThread.java:5257)
06-22 20:06:54.691 W/System.err(28046):     at java.lang.reflect.Method.invokeNative(Native Method)
06-22 20:06:54.691 W/System.err(28046):     at java.lang.reflect.Method.invoke(Method.java:515)
06-22 20:06:54.691 W/System.err(28046):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:788)
06-22 20:06:54.691 W/System.err(28046):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:604)
06-22 20:06:54.691 W/System.err(28046):     at de.robv.android.xposed.XposedBridge.main(XposedBridge.java:132)
06-22 20:06:54.691 W/System.err(28046):     at de.robv.android.xposed.XposedBridge.main(Native Method)
06-22 20:06:54.691 W/System.err(28046):     at dalvik.system.NativeStart.main(Native Method)
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 5

checkベストアンサー

+3

Androidのソースコードが見当たらなかったのでJavaのソースコードを確認してみましたが、
StringBuilderクラスは、内部に保持しているバッファのサイズが足りなくなると

(元のバッファのサイズ + 1) × 2


のサイズのバッファを新たに確保しようとしています。
http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/lang/AbstractStringBuilder.java#AbstractStringBuilder.expandCapacity%28int%29

Android の実装もこれと同様であれば、条件によっては一時的に約15MB(※)のメモリを使用することになります。
※ 新しいバッファ ( = ファイルサイズ × 2) + 古いバッファ ( = ファイルサイズ)

いずれの方法にせよ、ファイルの内容全てを1度にメモリに展開しようとするとそれだけOOMの発生する確率が上がりますので、
以下のページの "java.io.Fileクラスを使用したコピー方法" を、まずは試してみてください。
http://www.openreference.org/articles/view/670

これなら、ファイルの内容を1024バイトずつ読み書きしますので、OOMが発生する確率は低いはずです。

上の方法だと実行速度が遅すぎる場合は、Apache-commons-io のFileUtils.doCopyFile メソッドの実装を参考にするなどしてみてください。
https://github.com/apache/commons-io/blob/trunk/src/main/java/org/apache/commons/io/FileUtils.java#L1114

もっとも、このコードが何をやっているのか、私には理解できませんでしたが…

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/06/26 08:29 編集

    分かりました。できないということなのでこの方法で頑張ります

    キャンセル

+1

InputStreamReaderはテキストファイルを読み込むためのものです。
バイナリファイルの読み込みには不向きでしょう。

バイナリデータを無理やり文字に変換しようとしていますから、
1バイトのバイナリデータが2バイトの文字に変換される場合があり、最悪2倍のメモリを消費する結果となっているのではないかと思われます。

そもそも、バイナリファイルをどうしてStringに変換する意図がわかりません。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/06/23 15:48

    キャンセル

  • 2016/06/23 23:05

    ともかく、バイナリデータに対してInputStreamReaderで読み込むことはできません。

    キャンセル

  • 2016/06/26 08:30 編集

    分かりました。バイナリデータを読み込まず別の方法でします。

    キャンセル

0

return builder.toString();        ではなくて、
return new String(sb) を使ってみてください。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/06/21 20:35

    ダメです。OoMが発生します

    キャンセル

0

なにかほかに非同期で処理をしていたりしませんか?

エラーログの追加をお願いします。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/06/22 20:21

    追記しました。

    キャンセル

  • 2016/06/22 20:59

    ファイルの中にある文字数はわかりますか?

    キャンセル

  • 2016/06/22 21:54

    質問の通りバイナリファイルなんで…ファイルサイズは5.5MBです。
    文字数をプログラム作って調べようとするとOoMも発生しますから分かりませんね。

    キャンセル

0

OutOfMemoryErrorはtoStringで発生しているわけではなく、appendで発生しているようですよ?
ファイルのデータを文字列に変換しようとしたものの、文字列として扱うにはデータ量が多すぎるのではないですか?ましてやAndroidなので、メモリの制約も厳しいはずです。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/06/23 15:47

    分かりました。appendで発生していると書き直します。

    キャンセル

  • 2016/06/23 16:45

    というかそもそも、
    Runtime.getRuntime().exec(verchecked+" version");
    した行でOOMEになっているみたいで、InputStreamからの読み込みではない様です。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.23%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る