web-dev-qa-db-fra.com

Android Lecture efficace à partir d'un flux d'entrée

Je lance une requête HTTP à un site Web pour une application Android que je suis en train de créer.

J'utilise un DefaultHttpClient et utilise HttpGet pour émettre la demande. Je reçois la réponse de l'entité et à partir de là, obtiens un objet InputStream pour obtenir le code HTML de la page.

Je fais ensuite défiler la réponse en procédant comme suit:

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

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

Cependant, c'est horriblement lent.

Est-ce inefficace? Je ne charge pas une grande page Web - www.cokezone.co.uk donc la taille du fichier n'est pas grande. Y a-t-il une meilleure manière de faire cela?

Merci

Andy

146
RenegadeAndy

Le problème dans votre code est qu'il crée beaucoup d'objets lourds String, en copiant leur contenu et en effectuant des opérations dessus. Au lieu de cela, vous devez utiliser StringBuilder pour éviter de créer de nouveaux objets String sur chaque ajout et pour éviter de copier les tableaux de caractères. La mise en œuvre pour votre cas ressemblerait à ceci:

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

Vous pouvez maintenant utiliser total sans le convertir en String, mais si vous avez besoin du résultat en tant que String, ajoutez simplement:

Résultat de la chaîne = total.toString ();

Je vais essayer de mieux l'expliquer ...

  • a += b (ou a = a + b), où a et b sont des chaînes, copie le contenu de les deuxaetb vers un nouvel objet (notez que vous copiez également a, qui contient le accumuléString), et que vous effectuez ces copies. à chaque itération.
  • a.append(b), où a est un StringBuilder, ajoute directement le contenu de b à a, afin de ne pas copier la chaîne accumulée à chaque itération.
342
Jaime Soriano

Avez-vous essayé la méthode intégrée pour convertir un flux en chaîne? Cela fait partie de la bibliothèque Apache Commons (org.Apache.commons.io.IOUtils).

Ensuite, votre code serait cette ligne:

String total = IOUtils.toString(inputStream);

La documentation correspondante se trouve ici: http://commons.Apache.org/io/api-1.4/org/Apache/commons/io/IOUtils.html#toString%28Java.io.InputStream%29

La bibliothèque Apache Commons IO peut être téléchargée ici: http://commons.Apache.org/io/download_io.cgi

32
Makotosan

Une autre possibilité avec Goyave:

dépendance: compile 'com.google.guava:guava:11.0.2'

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

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

Je pense que cela est assez efficace ... Pour obtenir une chaîne d'un InputStream, j'appellerais la méthode suivante:

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

J'utilise toujours UTF-8. Vous pouvez, bien sûr, définir charset comme argument, en plus de InputStream.

8
Nikola Tulimirovic

Et ça. Semble donner de meilleures performances.

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

Edit: En fait, ce genre de comprend à la fois steelbytes et Maurice Perry

6
Adrian

Peut-être un peu plus vite que la réponse de Jaime Soriano, et sans les problèmes d'encodage à plusieurs octets de la réponse d'Adrian, je suggère:

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

Peut-être plutôt que de lire "une ligne à la fois" et de joindre les chaînes, d'essayer "read all available" (lire tout disponible) afin d'éviter l'analyse pour la fin de ligne et les jointures de chaînes.

c'est-à-dire InputStream.available() et InputStream.read(byte[] b), int offset, int length)

3
SteelBytes

Lire une ligne de texte à la fois et ajouter cette ligne à une chaîne individuellement prend du temps, à la fois pour l'extraction de chaque ligne et pour la surcharge de tant d'appels de méthode.

J'ai pu obtenir de meilleures performances en allouant un tableau d'octets de taille décente pour contenir les données de flux. Ce dernier est remplacé de manière itérative par un tableau plus grand en cas de besoin, et en essayant de lire autant que le tableau pourrait en contenir.

Pour une raison quelconque, Android n'a pas réussi à télécharger le fichier entier à plusieurs reprises lorsque le code utilisait InputStream renvoyé par HTTPUrlConnection; récupérer le fichier entier ou annuler le transfert.

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: Il s’avère que si vous n’avez pas besoin de ré-encoder le contenu (c’est-à-dire que vous voulez le contenu TEL QUEL ), vous ne devriez pas N'utilisez aucune des sous-classes de Reader. Utilisez simplement la sous-classe de flux appropriée.

Remplacez le début de la méthode précédente par les lignes suivantes pour l’accélérer extra 2 à 3 fois.

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];
2
Huperniketes

Si le fichier est long, vous pouvez optimiser votre code en ajoutant à StringBuilder au lieu d'utiliser une concaténation String pour chaque ligne.

1
Maurice Perry
    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
José Araújo

Pour convertir InputStream en String, nous utilisons la méthode BufferedReader.readLine (). Nous itérons jusqu'à ce que [BufferedReader renvoie null, ce qui signifie qu'il n'y a plus de données à lire. Chaque ligne sera ajoutée à un StringBuilder et renvoyée sous forme de chaîne.

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

Et enfin, depuis n'importe quelle classe où vous voulez convertir, appelez la fonction

String dataString = Utils.convertStreamToString(in);

complet

1
yubaraj poudel