160 votes

Android : lecture efficace d'un flux d'entrée

Je fais une requête HTTP get à un site web pour une application Android que je suis en train de réaliser.

J'utilise un DefaultHttpClient et j'utilise HttpGet pour émettre la requête. Je reçois la réponse de l'entité et à partir de celle-ci, j'obtiens un objet InputStream pour obtenir le html de la page.

Je parcours ensuite 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 grosse page web - www.cokezone.co.uk pour que la taille du fichier ne soit pas trop importante. Existe-t-il une meilleure façon de procéder ?

Merci

Andy

0 votes

À moins que vous n'analysiez réellement les lignes, la lecture ligne par ligne n'a pas beaucoup de sens. Je préfère lire char par char via des tampons de taille fixe : gist.github.com/fkirc/a231c817d582e114e791b77bb33e30e9

362voto

Jaime Soriano Points 3803

Le problème dans votre code est qu'il crée beaucoup de lourdes String des objets, en copiant leur contenu et en effectuant des opérations sur eux. Au lieu de cela, vous devez utiliser StringBuilder pour éviter de créer de nouvelles String à chaque ajout et pour éviter de copier les tableaux de caractères. L'implémentation pour votre cas serait quelque chose comme 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 sous forme de String il suffit d'ajouter :

String result = total.toString() ;

Je vais essayer de mieux l'expliquer...

  • a += b (ou a = a + b ), où a y b sont des chaînes de caractères, copie le contenu de les deux a et b vers un nouvel objet (notez que vous copiez également l'objet a qui contient le accumulé String ), et vous effectuez ces copies à chaque itération.
  • a.append(b) donde a est un StringBuilder , ajoute directement b contenu à a de sorte que vous ne copiez pas la chaîne accumulée à chaque itération.

0 votes

Exactement ! Ceci est également expliqué dans Java Effective Item 51. J'avais l'habitude d'expérimenter le profilage et l'utilisation de StringBuilder au lieu de String permet de gagner beaucoup de temps de traitement.

25 votes

Pour les points bonus, fournissez une capacité initiale pour éviter les réallocations lorsque le StringBuilder se remplit : StringBuilder total = new StringBuilder(inputStream.available());

11 votes

Cela ne supprime-t-il pas les nouveaux personnages de la ligne ?

35voto

Makotosan Points 827

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

Votre code serait alors cette seule ligne :

String total = IOUtils.toString(inputStream);

Sa documentation peut être trouvée 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

0 votes

Je me rends compte que ma réponse est tardive, mais je viens de tomber sur ce sujet en faisant une recherche sur Google.

63 votes

L'API Android n'inclut pas IOUtils

2 votes

Exact, c'est pourquoi j'ai mentionné la bibliothèque externe qui en dispose. J'ai ajouté la bibliothèque à mon projet Android et cela a facilité la lecture des flux.

12voto

Je pense que c'est assez efficace... Pour obtenir un String à partir 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 l'UTF-8. Vous pourriez, bien sûr, définir le jeu de caractères comme un argument, en plus de InputStream.

7voto

Adrian Points 1072

Qu'en est-il de ceci. Il 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, cela englobe à la fois Steelbytes et Maurice Perry.

0 votes

Le problème est que je ne connais pas la taille de la chose que je lis avant de commencer - donc je pourrais avoir besoin d'une forme de croissance de tableau aussi. A moins que vous ne puissiez interroger un InputStream ou une URL via http pour connaître la taille de la chose que je récupère afin d'optimiser la taille du tableau d'octets. Je dois être efficace car c'est sur un appareil mobile, ce qui est le principal problème ! Cependant, merci pour cette idée - Je vais essayer ce soir et je vous ferai savoir comment cela se passe en termes de gain de performance !

0 votes

Je ne pense pas que la taille du flux entrant soit si importante. Le code ci-dessus lit 1000 octets à la fois mais vous pouvez augmenter/diminuer cette taille. Dans mes tests, cela n'a pas fait une grande différence si j'ai utilisé 1000/10000 octets. Mais il ne s'agissait que d'une simple application Java. Cela peut être plus important sur un appareil mobile.

5 votes

Vous pourriez vous retrouver avec une entité Unicode qui est découpée en deux lectures ultérieures. Il est préférable de lire jusqu'à une sorte de caractère de délimitation, comme \n ce qui est exactement ce que fait BufferedReader.

3voto

steelbytes Points 2548

Peut-être qu'au lieu de lire "une ligne à la fois" et de joindre les chaînes de caractères, essayez de "lire tout ce qui est disponible" afin d'éviter le balayage pour la fin de ligne, et d'éviter également les jonctions de chaînes de caractères.

ie, InputStream.available() y InputStream.read(byte[] b), int offset, int length)

0 votes

Hmm. ce serait donc comme ceci : int offset = 5000 ; Byte[] bArr = new Byte[100] ; Byte[] total = 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] ; } Est-ce vraiment plus efficace - ou ai-je mal écrit ? Veuillez donner un exemple !

2 votes

Non non non non, je veux dire simplement { byte total[] = new [instrm.available()] ; instrm.read(total,0,total.length) ; } et si vous en avez ensuite besoin comme String, utilisez { String asString = String(total,0,total.length, "utf-8") ; // assume utf8 :-) }

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X