入力ストリームからのAndroidの読み取りを効率的に


152

私が作成しているAndroidアプリケーションのWebサイトへのHTTP getリクエストを作成しています。

DefaultHttpClientを使用し、HttpGetを使用してリクエストを発行しています。エンティティの応答を取得し、これからページのhtmlを取得するためのInputStreamオブジェクトを取得します。

次に、次のようにして返信を循環します。

BufferedReader r = new BufferedReader(new InputStreamReader(inputStream));
String x = "";
x = r.readLine();
String total = "";

while(x!= null){
total += x;
x = r.readLine();
}

しかし、これはものすごく遅いです。

これは非効率ですか?大きなWebページ(www.cokezone.co.uk)をロードしていないので、ファイルサイズは大きくありません。これを行うより良い方法はありますか?

ありがとう

アンディ


実際に行を解析しているのでない限り、1行ずつ読み取るのはあまり意味がありません。固定サイズのバッファーを介して文字ごとに
Mike76

回答:


355

コードの問題は、大量の重いStringオブジェクトを作成し、それらのコンテンツをコピーし、それらに対して操作を実行することです。代わりに、を使用StringBuilderStringて、追加ごとに新しいオブジェクトを作成したり、char配列をコピーしたりしないようにする必要があります。あなたのケースの実装は次のようになります:

BufferedReader r = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder total = new StringBuilder();
for (String line; (line = r.readLine()) != null; ) {
    total.append(line).append('\n');
}

totalこれをStringに変換せずに使用できますが、結果が必要な場合はString、以下を追加してください。

文字列結果= total.toString();

私はそれをよりよく説明しようとする...

  • a += b(またはa = a + b)、どこabストリングス、コピーの内容が両方 a b(あなたもコピーしていることに注意してください新しいオブジェクトにa含まれ、蓄積された String)を、そしてあなたは、各反復でそれらのコピーを行っています。
  • a.append(b)、はでaあり、内容をStringBuilderに直接追加bするためa、反復ごとに蓄積された文字列をコピーしません。

23
StringBuilderがいっぱいになるとボーナスポイントの場合は、回避の再配分に初期容量を提供します StringBuilder total = new StringBuilder(inputStream.available());
トッケビ

10
これは改行文字を切り取りませんか?
Nathan Schwermann、2012年

5
このようにtry / catchでwhileをラップすることを忘れないでください:try {while((line = r.readLine())!= null){total.append(line); }} catch(IOException e){Log.i(tag、 "problem with readline in inputStreamToString function"); }
ボットボット

4
@botbot:例外をログに記録して無視することは、単に例外を無視することよりもはるかに良いことではありません...
Matti Virkkunen

50
Androidに組み込みのストリームから文字列への変換がないことは驚くべきことです。ウェブ上のすべてのコードスニペットと地球上のアプリがreadlineループを再実装するのは、とんでもないことです。そのパターンは70年代にエンドウグリーンで死んだはずです。
Edward Brey

35

ストリームを文字列に変換する組み込みメソッドを試しましたか?これは、Apache Commonsライブラリ(org.apache.commons.io.IOUtils)の一部です。

次に、コードは次の1行になります。

String total = IOUtils.toString(inputStream);

そのドキュメントはここにあります: http //commons.apache.org/io/api-1.4/org/apache/commons/io/IOUtils.html#toString%28java.io.InputStream%29

Apache Commons IOライブラリは、こちらからダウンロードできます。http//commons.apache.org/io/download_io.cgi


これは遅い応答だと思いますが、たまたまGoogle検索で偶然これに遭遇しました。
誠さん2010

61
Android APIにはIOUtilsが含まれていません
Charles Ma

2
右、それは私がそれを持っている外部ライブラリについて言及した理由です。ライブラリをAndroidプロジェクトに追加したところ、ストリームから簡単に読み取ることができました。
マコトサン2010年

これはどこでダウンロードできますか、それをどのようにAndroidプロジェクトにインポートしましたか?
サファリ

3
ダウンロードする必要がある場合は、「組み込み」とは呼びません。それにもかかわらず、私はそれをダウンロードし、それを試してみるつもりです。
B.クレイシャノン

15

Guavaのもう1つの可能性:

依存: compile 'com.google.guava:guava:11.0.2'

import com.google.common.io.ByteStreams;
...

String total = new String(ByteStreams.toByteArray(inputStream ));

9

これは十分に効率的だと思います... InputStreamから文字列を取得するには、次のメソッドを呼び出します。

public static String getStringFromInputStream(InputStream stream) throws IOException
{
    int n = 0;
    char[] buffer = new char[1024 * 4];
    InputStreamReader reader = new InputStreamReader(stream, "UTF8");
    StringWriter writer = new StringWriter();
    while (-1 != (n = reader.read(buffer))) writer.write(buffer, 0, n);
    return writer.toString();
}

私は常にUTF-8を使用しています。もちろん、InputStreamの他に、charsetを引数として設定することもできます。


6

これはどうですか。より良いパフォーマンスを与えるようです。

byte[] bytes = new byte[1000];

StringBuilder x = new StringBuilder();

int numRead = 0;
while ((numRead = is.read(bytes)) >= 0) {
    x.append(new String(bytes, 0, numRead));
}

編集:実際には、この種はsteelbytesとモーリス・ペリーの両方を網羅しています


問題は、始める前に読んでいるもののサイズがわからないことです。そのため、何らかの形で配列を拡大する必要があるかもしれません。さらに、httpを介してInputStreamまたはURLを照会して、バイト配列のサイズを最適化することで取得することがどれほど大きいかを知ることができます。主な問題であるモバイルデバイスで効率的にする必要があります!ただし、そのアイデアに感謝します-今夜は試してみて、パフォーマンスの向上という点でそれがどのように処理されるかを知らせます!
RenegadeAndy

着信ストリームのサイズはそれほど重要ではないと思います。上記のコードは一度に1000バイトを読み取りますが、そのサイズを増減できます。私のテストでは、1000/10000バイトを使用した天候にはそれほど大きな違いはありませんでした。しかし、それは単純なJavaアプリでした。モバイルデバイスではより重要な場合があります。
エイドリアン

4
2つの後続の読み取りに切り刻まれたUnicodeエンティティになる可能性があります。\ nのようなある種の境界文字まで読み取るほうがよい。これは、BufferedReaderが正確に行うことです。
Jacob Nordfalk、2011年

4

おそらく、ハイメソリアーノの回答よりもいくらか高速であり、エイドリアンの回答のマルチバイトエンコーディングの問題がないため、次のことをお勧めします。

File file = new File("/tmp/myfile");
try {
    FileInputStream stream = new FileInputStream(file);

    int count;
    byte[] buffer = new byte[1024];
    ByteArrayOutputStream byteStream =
        new ByteArrayOutputStream(stream.available());

    while (true) {
        count = stream.read(buffer);
        if (count <= 0)
            break;
        byteStream.write(buffer, 0, count);
    }

    String string = byteStream.toString();
    System.out.format("%d bytes: \"%s\"%n", string.length(), string);
} catch (IOException e) {
    e.printStackTrace();
}

なぜそれがより速くなるのか説明できますか?
Akhil Dad

入力を改行文字でスキャンするのではなく、1024バイトのチャンクを読み取るだけです。これが実際的な違いになるとは主張していません。
2016

@Ronaldの回答に対するコメントはありますか?彼は同じことをしていますが、inputStreamのサイズに等しい大きなチャンクを使用しています。また、ニコラが答えるようにバイト配列ではなく文字配列をスキャンする場合、それはどのように異なりますか?実際、私はどのアプローチがどの場合に最適か知りたいだけですか?また、readLineは\ nと\ rを削除しますが、readlineを使用しているgoogle ioアプリのコードも確認しました
Akhil Dad

3

むしろ、「一度に1行ずつ」読み取って文字列を結合し、「利用可能なすべてを読み取る」を試して、行末のスキャンを回避し、文字列結合も回避してください。

つまり、InputStream.available()そしてInputStream.read(byte[] b), int offset, int length)


うーん。したがって、次のようになります。intoffset = 5000; Byte [] bArr =新しいByte [100]; Byte []合計= Byte [5000]; while(InputStream.available){offset = InputStream.read(bArr、offset、100); for(int i = 0; i <offset; i ++){total [i] = bArr [i]; } bArr = new Byte [100]; それは本当により効率的ですか、それとも私はそれをひどく書きましたか?例を挙げてください!
RenegadeAndy

2
いやいやいやいや、単に{バイト合計[] = new [instrm.available()]; instrm.read(total、0、total.length); そして、それが文字列として必要になった場合は、{String asString = String(total、0、total.length、 "utf-8");を使用します。// utf8を想定:-)}
SteelBytes

2

一度に1行のテキストを読み取り、その行を文字列に個別に追加すると、各行の抽出と多くのメソッド呼び出しのオーバーヘッドの両方で時間がかかります。

適切なサイズのバイト配列を割り当ててストリームデータを保持し、必要に応じて繰り返しより大きな配列に置き換え、配列が保持できる最大量を読み取ろうとすることで、パフォーマンスを向上させることができました。

何らかの理由で、コードがHTTPUrlConnectionによって返されたInputStreamを使用すると、Androidはファイル全体のダウンロードに繰り返し失敗したため、BufferedReaderと手動のタイムアウトメカニズムの両方を使用して、ファイル全体を取得するかキャンセルするかを確認する必要がありました転送。

private static  final   int         kBufferExpansionSize        = 32 * 1024;
private static  final   int         kBufferInitialSize          = kBufferExpansionSize;
private static  final   int         kMillisecondsFactor         = 1000;
private static  final   int         kNetworkActionPeriod        = 12 * kMillisecondsFactor;

private String loadContentsOfReader(Reader aReader)
{
    BufferedReader  br = null;
    char[]          array = new char[kBufferInitialSize];
    int             bytesRead;
    int             totalLength = 0;
    String          resourceContent = "";
    long            stopTime;
    long            nowTime;

    try
    {
        br = new BufferedReader(aReader);

        nowTime = System.nanoTime();
        stopTime = nowTime + ((long)kNetworkActionPeriod * kMillisecondsFactor * kMillisecondsFactor);
        while(((bytesRead = br.read(array, totalLength, array.length - totalLength)) != -1)
        && (nowTime < stopTime))
        {
            totalLength += bytesRead;
            if(totalLength == array.length)
                array = Arrays.copyOf(array, array.length + kBufferExpansionSize);
            nowTime = System.nanoTime();
        }

        if(bytesRead == -1)
            resourceContent = new String(array, 0, totalLength);
    }
    catch(Exception e)
    {
        e.printStackTrace();
    }

    try
    {
        if(br != null)
            br.close();
    }
    catch(IOException e)
    {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

編集:コンテンツを再エンコードする必要がない場合(つまり、コンテンツを現状のままにしたい場合)する場合)、Readerサブクラスを使用しないでください。適切なStreamサブクラスを使用するだけです。

上記のメソッドの先頭を次の対応する行に置き換えて、余分な2〜3倍の速度にします。

String  loadContentsFromStream(Stream aStream)
{
    BufferedInputStream br = null;
    byte[]              array;
    int                 bytesRead;
    int                 totalLength = 0;
    String              resourceContent;
    long                stopTime;
    long                nowTime;

    resourceContent = "";
    try
    {
        br = new BufferedInputStream(aStream);
        array = new byte[kBufferInitialSize];

これは、上記および受け入れられた回答よりもはるかに高速です。Androidで「リーダー」と「ストリーム」をどのように使用しますか?
SteveGSD、2014年

1

ファイルが長い場合は、各行に文字列連結を使用する代わりに、StringBuilderに追加することでコードを最適化できます。


正直言ってそれほど長くはありません-ウェブサイトwww.cokezone.co.ukのページのソースです-それほど大きくありません。間違いなく100kb未満です。
RenegadeAndy

これをどのようにしてより効率的にすることができるか、またはこれが非効率でさえあるかについて誰か他のアイデアがありますか?後者が当てはまる場合-なぜそんなに時間がかかるのですか?接続が原因であるとは思わない。
RenegadeAndy

1
    byte[] buffer = new byte[1024];  // buffer store for the stream
    int bytes; // bytes returned from read()

    // Keep listening to the InputStream until an exception occurs
    while (true) {
        try {
            // Read from the InputStream
            bytes = mmInStream.read(buffer);

            String TOKEN_ = new String(buffer, "UTF-8");

            String xx = TOKEN_.substring(0, bytes);

1

InputStreamをStringに変換するには、BufferedReader.readLine()メソッドを使用し ます。BufferedReaderがnullを返すまで繰り返します。つまり、読み取るデータがなくなるということです。各行はStringBuilderに追加され、Stringとして返されます。

 public static String convertStreamToString(InputStream is) {

        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
        StringBuilder sb = new StringBuilder();

        String line = null;
        try {
            while ((line = reader.readLine()) != null) {
                sb.append(line + "\n");
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return sb.toString();
    }
}`

そして最後に、変換したいクラスから関数を呼び出します

String dataString = Utils.convertStreamToString(in);

コンプリート


-1

私は完全なデータを読むために使用しています:

// inputStream is one instance InputStream
byte[] data = new byte[inputStream.available()];
inputStream.read(data);
String dataString = new String(data);
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.