Android Lesen aus einem Eingabestream effizient

152

Ich mache eine HTTP-Get-Anfrage an eine Website für eine Android-Anwendung, die ich mache.

Ich verwende einen DefaultHttpClient und HttpGet, um die Anforderung auszugeben. Ich erhalte die Entitätsantwort und erhalte daraus ein InputStream-Objekt zum Abrufen des HTML-Codes der Seite.

Ich gehe dann die Antwort wie folgt durch:

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

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

Dies ist jedoch schrecklich langsam.

Ist das ineffizient? Ich lade keine große Webseite - www.cokezone.co.uk, daher ist die Dateigröße nicht groß. Gibt es einen besseren Weg, dies zu tun?

Vielen Dank

Andy

RenegadeAndy
quelle
Wenn Sie die Zeilen nicht tatsächlich analysieren, ist es nicht sehr sinnvoll, Zeile für Zeile zu lesen. Ich würde lieber char by char über Puffer mit fester Größe lesen: gist.github.com/fkirc/a231c817d582e114e791b77bb33e30e9
Mike76

Antworten:

355

Das Problem in Ihrem Code besteht darin, dass viele schwere StringObjekte erstellt, deren Inhalt kopiert und Operationen an ihnen ausgeführt werden. Verwenden Sie stattdessen, um StringBuilderzu vermeiden, dass Stringan jedem Anhang neue Objekte erstellt werden, und um zu vermeiden, dass die char-Arrays kopiert werden. Die Implementierung für Ihren Fall wäre ungefähr so:

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

Sie können es jetzt verwenden, totalohne es zu konvertieren. StringWenn Sie das Ergebnis jedoch als benötigen String, fügen Sie einfach Folgendes hinzu:

String result = total.toString ();

Ich werde versuchen, es besser zu erklären ...

  • a += b(oder a = a + b), wobei aund bZeichenfolgen sind, kopiert den Inhalt von beiden a und b in ein neues Objekt (beachten Sie, dass Sie auch kopieren a, das die akkumulierten enthält String), und Sie kopieren diese bei jeder Iteration.
  • a.append(b), wo aist ein StringBuilder, hängt den bInhalt direkt an a, sodass Sie die akkumulierte Zeichenfolge nicht bei jeder Iteration kopieren.
Jaime Soriano
quelle
23
StringBuilder total = new StringBuilder(inputStream.available());
Geben Sie
10
Schneidet dies nicht neue Zeilenzeichen aus?
Nathan Schwermann
5
Vergessen Sie nicht, das while in try / catch wie folgt einzuwickeln: try {while ((line = r.readLine ())! = null) {total.append (line); }} catch (IOException e) {Log.i (Tag, "Problem mit Readline in der inputStreamToString-Funktion"); }
Botbot
4
@botbot: Protokollieren und Ignorieren einer Ausnahme ist nicht viel besser als nur die Ausnahme zu ignorieren ...
Matti Virkkunen
50
Es ist erstaunlich, dass Android keine integrierte Stream-zu-String-Konvertierung hat. Es readlineist lächerlich, wenn jedes Code-Snippet im Web und jede App auf dem Planeten eine Schleife erneut implementiert . Dieses Muster sollte in den 70er Jahren mit Erbsengrün gestorben sein.
Edward Brey
35

Haben Sie die integrierte Methode zum Konvertieren eines Streams in einen String ausprobiert? Es ist Teil der Apache Commons-Bibliothek (org.apache.commons.io.IOUtils).

Dann wäre Ihr Code diese eine Zeile:

String total = IOUtils.toString(inputStream);

Die Dokumentation dazu finden Sie hier: http://commons.apache.org/io/api-1.4/org/apache/commons/io/IOUtils.html#toString%28java.io.InputStream%29

Die Apache Commons IO-Bibliothek kann hier heruntergeladen werden: http://commons.apache.org/io/download_io.cgi

Makotosan
quelle
Mir ist klar, dass dies eine späte Antwort ist, aber ich bin gerade über eine Google-Suche darauf gestoßen.
Makotosan
61
Die Android-API enthält keine IOUtils
Charles Ma
2
Richtig, deshalb habe ich die externe Bibliothek erwähnt, die es hat. Ich habe die Bibliothek zu meinem Android-Projekt hinzugefügt und es war einfach, aus Streams zu lesen.
Makotosan
Wo kann ich das herunterladen und wie hast du das in dein Android-Projekt importiert?
Safari
3
Wenn Sie es herunterladen müssen, würde ich es nicht "eingebaut" nennen. Trotzdem habe ich es gerade heruntergeladen und werde es ausprobieren.
B. Clay Shannon
15

Eine andere Möglichkeit mit Guave:

Abhängigkeit: compile 'com.google.guava:guava:11.0.2'

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

String total = new String(ByteStreams.toByteArray(inputStream ));
Andrey
quelle
9

Ich glaube, das ist effizient genug ... Um einen String aus einem InputStream zu erhalten, würde ich die folgende Methode aufrufen:

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();
}

Ich benutze immer UTF-8. Neben InputStream können Sie natürlich auch den Zeichensatz als Argument festlegen.

Budimir Grom
quelle
6

Was ist damit? Scheint eine bessere Leistung zu geben.

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));
}

Bearbeiten: Eigentlich umfasst diese Art sowohl Steelbytes als auch Maurice Perrys

Adrian
quelle
Das Problem ist - ich weiß nicht, wie groß das Ding ist, das ich lese, bevor ich anfange - also muss möglicherweise auch eine Art Array wachsen. Sofern Sie keinen InputStream oder keine URL über http abfragen können, um herauszufinden, wie groß das Abrufen ist, um die Größe des Byte-Arrays zu optimieren. Ich muss effizient sein wie auf einem mobilen Gerät, was das Hauptproblem ist! Vielen Dank für diese Idee - Probieren Sie es heute Abend aus und lassen Sie Sie wissen, wie es mit dem Leistungsgewinn umgeht!
RenegadeAndy
Ich denke nicht, dass die Größe des eingehenden Streams so wichtig ist. Der obige Code liest jeweils 1000 Bytes, aber Sie können diese Größe erhöhen / verringern. Bei meinen Tests machte es keinen großen Unterschied, ob ich 1000/10000 Bytes verwendete. Das war allerdings nur eine einfache Java-App. Auf einem mobilen Gerät kann dies wichtiger sein.
Adrian
4
Möglicherweise erhalten Sie eine Unicode-Entität, die in zwei aufeinanderfolgende Lesevorgänge unterteilt ist. Lesen Sie besser bis zu einer Art Grenzzeichen wie \ n, was genau das ist, was BufferedReader tut.
Jacob Nordfalk
4

Möglicherweise etwas schneller als Jaime Sorianos Antwort, und ohne die Multi-Byte-Codierungsprobleme von Adrians Antwort schlage ich vor:

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();
}
heiner
quelle
Können Sie erklären, warum es schneller wäre?
Akhil Dad
Die Eingabe wird nicht nach Zeilenumbrüchen durchsucht, sondern nur Blöcke mit 1024 Byte gelesen. Ich behaupte nicht, dass dies einen praktischen Unterschied machen wird.
Heiner
irgendwelche Kommentare über @Ronald Antwort? Er macht dasselbe, aber für einen größeren Block, der der Größe von inputStream entspricht. Wie unterschiedlich ist es auch, wenn ich als Nikola-Antwort eher ein Char-Array als ein Byte-Array scanne? Eigentlich wollte ich nur wissen, welcher Ansatz in welchem ​​Fall am besten ist? Auch readLine entfernt \ n und \ r, aber ich habe sogar Google Io App Code gesehen, den sie readline verwenden
Akhil Dad
3

Vielleicht lesen Sie lieber "eine Zeile nach der anderen" und verbinden Sie die Zeichenfolgen. Versuchen Sie "Alle verfügbaren lesen", um das Scannen nach Zeilenende und das Verknüpfen von Zeichenfolgen zu vermeiden.

dh InputStream.available()undInputStream.read(byte[] b), int offset, int length)

SteelBytes
quelle
Hmm. so wäre es also: int offset = 5000; Byte [] bArr = neues Byte [100]; Byte [] gesamt = Byte [5000]; while (InputStream.available) {offset = InputStream.read (bArr, offset, 100); für (int i = 0; i <Offset; i ++) {total [i] = bArr [i]; } bArr = neues Byte [100]; } Ist das wirklich effizienter - oder habe ich es schlecht geschrieben! Bitte geben Sie ein Beispiel!
RenegadeAndy
2
nein nein nein nein, ich meine einfach {byte total [] = new [instrm.available ()]; instrumentmread (total, 0, total.length); } und wenn Sie es dann als String brauchten, verwenden Sie {String asString = String (total, 0, total.length, "utf-8"); // nehme utf8 an :-)}
SteelBytes
2

Das Lesen von jeweils einer Textzeile und das Anhängen dieser Zeile an eine Zeichenfolge ist sowohl beim Extrahieren jeder Zeile als auch beim Overhead so vieler Methodenaufrufe zeitaufwändig.

Ich konnte eine bessere Leistung erzielen, indem ich ein Byte-Array mit angemessener Größe für die Stream-Daten zuordnete, das bei Bedarf iterativ durch ein größeres Array ersetzt wird, und versuchte, so viel zu lesen, wie das Array aufnehmen konnte.

Aus irgendeinem Grund konnte Android wiederholt nicht die gesamte Datei herunterladen, wenn der Code den von HTTPUrlConnection zurückgegebenen InputStream verwendete. Daher musste ich sowohl einen BufferedReader als auch einen handgerollten Timeout-Mechanismus verwenden, um sicherzustellen, dass ich entweder die gesamte Datei erhalten oder abbrechen würde die Übertragung.

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();
    }
}

EDIT: Es stellt sich heraus , dass , wenn Sie nicht den Inhalt neucodierte haben müssen (dh der Inhalt wollen AS IS ) Sie sollten keine der Reader - Subklassen verwenden. Verwenden Sie einfach die entsprechende Stream-Unterklasse.

Ersetzen Sie den Anfang der vorhergehenden Methode durch die entsprechenden Zeilen der folgenden, um sie zwei- bis dreimal zu beschleunigen .

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];
Huperniketes
quelle
Dies ist viel schneller als die oben genannten und akzeptierten Antworten. Wie benutzt man "Reader" und "Stream" auf Android?
SteveGSD
1

Wenn die Datei lang ist, können Sie Ihren Code optimieren, indem Sie ihn an einen StringBuilder anhängen, anstatt für jede Zeile eine String-Verkettung zu verwenden.

Maurice Perry
quelle
Es ist nicht so lange, um ehrlich zu sein - es ist die Seitenquelle der Website www.cokezone.co.uk - also wirklich nicht so groß. Auf jeden Fall weniger als 100kb.
RenegadeAndy
Hat jemand andere Ideen, wie dies effizienter gestaltet werden könnte - oder ob dies sogar ineffizient ist? Wenn letzteres zutrifft - warum dauert es so lange? Ich glaube nicht, dass die Verbindung schuld ist.
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);
José Araújo
quelle
1

Um den InputStream in String zu konvertieren, verwenden wir die BufferedReader.readLine () -Methode. Wir iterieren, bis der BufferedReader null zurückgibt , was bedeutet, dass keine Daten mehr zu lesen sind. Jede Zeile wird an einen StringBuilder angehängt und als String zurückgegeben.

 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();
    }
}`

Und schließlich rufen Sie aus jeder Klasse, in die Sie konvertieren möchten, die Funktion auf

String dataString = Utils.convertStreamToString(in);

Komplett

Yubaraj Poudel
quelle
-1

Ich bin es gewohnt, vollständige Daten zu lesen:

// inputStream is one instance InputStream
byte[] data = new byte[inputStream.available()];
inputStream.read(data);
String dataString = new String(data);
Ronald
quelle