Android Lettura di un flusso di Input in modo efficiente
-
21-09-2019 - |
Domanda
Sto per fare una richiesta HTTP get per un sito web per un'applicazione android che sto creando.
Io sto usando una DefaultHttpClient e l'utilizzo di HttpGet di emettere la richiesta.Ho l'entità di risposta e da questo ottenere un oggetto InputStream per ottenere il codice html della pagina.
Poi ho il ciclo attraverso la risposta facendo come segue:
BufferedReader r = new BufferedReader(new InputStreamReader(inputStream));
String x = "";
x = r.readLine();
String total = "";
while(x!= null){
total += x;
x = r.readLine();
}
Tuttavia questo è terribilmente lento.
Questo è inefficiente?Io non sono il caricamento di una grande pagina web - www.cokezone.co.uk così la dimensione del file non è grande.C'è un modo migliore per fare questo?
Grazie
Andy
Soluzione
Il problema nel codice è che si sta creando un sacco di oggetti pesanti String
, copiando il contenuto e l'esecuzione di operazioni su di essi. Invece, si dovrebbe utilizzare StringBuilder
per evitare la creazione di nuovi oggetti String
su ogni aggiungere e per evitare di copiare gli array char. L'implementazione per il vostro caso sarebbe qualcosa di simile a questo:
BufferedReader r = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder total = new StringBuilder();
for (String line; (line = r.readLine()) != null; ) {
total.append(line).append('\n');
}
È ora possibile utilizzare total
senza convertirlo in String
, ma se avete bisogno il risultato come String
, è sufficiente aggiungere:
Risultati String = total.toString ();
Cercherò di spiegare meglio ...
-
a += b
(oa = a + b
), dovea
eb
sono stringhe, copia il contenuto di siaa
eb
a un nuovo oggetto (si noti che si sta anche copiandoa
, che contiene il accumulatoString
), e si stanno facendo queste copie ad ogni iterazione. -
a.append(b)
, dovea
è unStringBuilder
, aggiunge direttamente contenutib
aa
, in modo da non copiare la stringa accumulato ad ogni iterazione.
Altri suggerimenti
Hai provato il costruito nel metodo per convertire un flusso in una stringa? E 'parte della libreria Apache Commons (org.apache.commons.io.IOUtils).
Allora il vostro codice sarebbe questa una riga:
String total = IOUtils.toString(inputStream);
La documentazione può essere trovato qui: http : //commons.apache.org/io/api-1.4/org/apache/commons/io/IOUtils.html#toString%28java.io.InputStream%29
La libreria Apache Commons IO può essere scaricato da qui: http://commons.apache.org/io/download_io.cgi
Un'altra possibilità con Guava:
dipendenza: compile 'com.google.guava:guava:11.0.2'
import com.google.common.io.ByteStreams;
...
String total = new String(ByteStreams.toByteArray(inputStream ));
Credo che questo sia abbastanza efficiente ... Per ottenere una stringa da un InputStream, lo chiamerei il seguente metodo:
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();
}
Io uso sempre UTF-8. Si potrebbe, naturalmente, impostare charset come argomento, oltre InputStream.
Che dire di questo. Sembra dare migliori prestazioni.
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));
}
Modifica: In realtà questo tipo di comprende sia steelbytes e di Maurice Perry
Forse un po 'più veloce di risposta di Jaime Soriano, e senza i problemi di codifica multi-byte di risposta di Adriano, suggerisco:
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();
}
Forse piuttosto che leggere 'una riga alla volta' e unire le corde, prova a 'leggere tutte disponibili' in modo da evitare la scansione di fine linea, e per evitare anche stringa unisce.
cioè, InputStream.available()
e InputStream.read(byte[] b), int offset, int length)
Leggere una riga di testo alla volta, e aggiungendo detta linea ad una stringa individualmente sia in termini di tempo, sia in estrazione ogni linea e l'overhead di tante invocazioni di metodi.
Sono stato in grado di ottenere migliori prestazioni allocando un array di byte di dimensioni decenti per contenere i dati di flusso, e che è iterativo sostituito con un array più grande, se necessario, e cercando di leggere quanto più la matrice potrebbe contenere.
Per qualche ragione, Android ripetutamente fallito per scaricare l'intero file quando il codice utilizzato InputStream restituito da HttpURLConnection, così ho dovuto ricorrere all'utilizzo sia un BufferedReader e un meccanismo di timeout arrotolato a mano per garantire avrei né ottenere l'intera file o annullare il trasferimento.
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();
}
}
Modifica Si scopre che se non c'è bisogno di avere il contenuto ri-codificato (cioè, si desidera che il contenuto di AS IS ) non si dovrebbe utilizzare uno dei sottoclassi Reader. Basta usare la sottoclasse flusso appropriata.
Sostituire l'inizio del metodo precedente con le corrispondenti linee del seguente accelerarlo un extra 2 a 3 volte .
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];
Se il file è lungo, è possibile ottimizzare il codice aggiungendo ad uno StringBuilder invece di utilizzare un concatenazione di stringhe per ogni linea.
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);
Per convertire l'InputStream per Stringa si usa il BufferedReader.readLine() metodo.È scorrere fino alla BufferedReader return null, il che significa che non ci sono più dati da leggere.Ogni linea sarà aggiunto un StringBuilder e restituito come Stringa.
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();
}
}`
E, infine, dalla classe in cui si desidera convertire chiamare la funzione
String dataString = Utils.convertStreamToString(in);
completa
sto uso per leggere i dati completa:
// inputStream is one instance InputStream
byte[] data = new byte[inputStream.available()];
inputStream.read(data);
String dataString = new String(data);