Lectura de Android de un flujo de entrada de manera eficiente

Estoy haciendo una solicitud de obtención de HTTP a un sitio web para una aplicación de Android que estoy creando.

Estoy usando un DefaultHttpClient y el uso de HttpGet para emitir la solicitud. Obtengo la respuesta de la entidad y obtengo un objeto InputStream para obtener el html de la página.

Luego paso por la respuesta haciendo lo siguiente:

BufferedReader r = new BufferedReader(new InputStreamReader(inputStream)); String x = ""; x = r.readLine(); String total = ""; while(x!= null){ total += x; x = r.readLine(); } 

Sin embargo, esto es tremendamente lento.

¿Esto es ineficiente? No estoy cargando una gran página web: www.cokezone.co.uk, por lo que el tamaño del archivo no es grande. ¿Hay una mejor manera de hacer esto?

Gracias

Andy

El problema en su código es que está creando muchos objetos String pesados, copiando sus contenidos y realizando operaciones sobre ellos. En su lugar, debe usar StringBuilder para evitar la creación de nuevos objetos String en cada anexo y evitar copiar las matrices char. La implementación para su caso sería algo como esto:

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

Ahora puede usar total sin convertirlo a String , pero si necesita el resultado como String , simplemente agregue:

Resultado de cadena = total.toString ();

Trataré de explicarlo mejor …

  • a += b (o a = a + b ), donde a y b son cadenas, copia el contenido de b a un nuevo objeto (tenga en cuenta que también está copiando a , que contiene la String acumulada ), y usted están haciendo esas copias en cada iteración.
  • a.append(b) , donde a es un StringBuilder , agrega directamente b contenidos a a , por lo que no copia la cadena acumulada en cada iteración.

¿Has probado el método integrado para convertir una secuencia en una cadena? Es parte de la biblioteca de Apache Commons (org.apache.commons.io.IOUtils).

Entonces tu código sería esta única línea:

 String total = IOUtils.toString(inputStream); 

La documentación para ello se puede encontrar aquí: http://commons.apache.org/io/api-1.4/org/apache/commons/io/IOUtils.html#toString%28java.io.InputStream%29

La biblioteca Apache Commons IO se puede descargar desde aquí: http://commons.apache.org/io/download_io.cgi

Otra posibilidad con Guava:

dependencia: compile 'com.google.guava:guava:11.0.2'

 import com.google.common.io.ByteStreams; ... String total = new String(ByteStreams.toByteArray(inputStream )); 

¿Qué tal esto? Parece dar un mejor rendimiento.

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

Editar: En realidad, este tipo de abarca steelbytes y Maurice Perry

Creo que esto es lo suficientemente eficiente … Para obtener un String de un InputStream, llamaría al siguiente método:

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

Yo siempre uso UTF-8. Por supuesto, podría establecer charset como argumento, además de InputStream.

Posiblemente algo más rápido que la respuesta de Jaime Soriano, y sin los problemas de encoding de múltiples bytes de la respuesta de Adrián, sugiero:

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

Tal vez en lugar de leer ‘una línea a la vez’ y unir las cadenas, intente ‘leer todo disponible’ para evitar el escaneo de fin de línea, y también evitar combinaciones de cadenas.

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

Leer una línea de texto a la vez, y agregar dicha línea a una cadena individualmente consume mucho tiempo tanto en la extracción de cada línea como en la sobrecarga de tantas invocaciones de métodos.

Pude obtener un mejor rendimiento asignando una matriz de bytes de tamaño decente para contener los datos de la ruta, y que se reemplaza iterativamente con una matriz más grande cuando es necesario, y tratando de leer todo lo que la matriz podría contener.

Por alguna razón, Android no pudo descargar el archivo completo cuando el código utilizó el InputStream devuelto por HTTPUrlConnection, así que tuve que recurrir al uso de un BufferedReader y un mecanismo de tiempo de espera para garantizar que obtuviera todo el archivo o cancelara la transferencia.

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

EDITAR: Resulta que si no necesita volver a codificar el contenido (es decir, desea el contenido TAL CUAL ), no debe usar ninguna de las subclases de Reader. Simplemente use la subclase Stream apropiada.

Reemplace el comienzo del método anterior con las líneas correspondientes de lo siguiente para acelerarlo de 2 a 3 veces más .

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

Si el archivo es largo, puede optimizar su código añadiéndolo a un StringBuilder en lugar de utilizar una concatenación de cadenas para cada línea.

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

Para convertir el InputStream a String usamos el método BufferedReader.readLine () . Realizamos iteraciones hasta que BufferedReader devuelva nulo, lo que significa que no hay más datos para leer. Cada línea se agregará a un StringBuilder y se devolverá como 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(); } }` 

Y finalmente, desde cualquier clase en la que quieras convertir, llama a la función

 String dataString = Utils.convertStreamToString(in); 

completar

Estoy acostumbrado a leer datos completos:

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