Dois-je tamponner le InputStream ou InputStreamReader?
-
27-09-2019 - |
Question
Quelles sont les différences (le cas échéant) entre les deux approches suivantes tampon?
Reader r1 = new BufferedReader(new InputStreamReader(in, "UTF-8"), bufferSize);
Reader r2 = new InputStreamReader(new BufferedInputStream(in, bufferSize), "UTF-8");
La solution
r1
est plus efficace. Le InputStreamReader
lui-même ne dispose pas d'un grand tampon. Le BufferedReader
peut être réglé pour avoir un tampon plus grand que InputStreamReader
. Le InputStreamReader
en r2
agirait comme un goulot d'étranglement.
Dans un écrou. Vous devriez lire les données à travers un entonnoir, et non par une bouteille
Mise à jour : voici un petit programme de référence, juste copy'n'paste'n'run il. Vous n'avez pas besoin de préparer des fichiers.
package com.stackoverflow.q3459127;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
public class Test {
public static void main(String... args) throws Exception {
// Init.
int bufferSize = 10240; // 10KB.
int fileSize = 100 * 1024 * 1024; // 100MB.
File file = new File("/temp.txt");
// Create file (it's also a good JVM warmup).
System.out.print("Creating file .. ");
BufferedWriter writer = null;
try {
writer = new BufferedWriter(new FileWriter(file));
for (int i = 0; i < fileSize; i++) {
writer.write("0");
}
System.out.printf("finished, file size: %d MB.%n", file.length() / 1024 / 1024);
} finally {
if (writer != null) try { writer.close(); } catch (IOException ignore) {}
}
// Read through funnel.
System.out.print("Reading through funnel .. ");
Reader r1 = null;
try {
r1 = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"), bufferSize);
long st = System.nanoTime();
for (int data; (data = r1.read()) > -1;);
long et = System.nanoTime();
System.out.printf("finished in %d ms.%n", (et - st) / 1000000);
} finally {
if (r1 != null) try { r1.close(); } catch (IOException ignore) {}
}
// Read through bottle.
System.out.print("Reading through bottle .. ");
Reader r2 = null;
try {
r2 = new InputStreamReader(new BufferedInputStream(new FileInputStream(file), bufferSize), "UTF-8");
long st = System.nanoTime();
for (int data; (data = r2.read()) > -1;);
long et = System.nanoTime();
System.out.printf("finished in %d ms.%n", (et - st) / 1000000);
} finally {
if (r2 != null) try { r2.close(); } catch (IOException ignore) {}
}
// Cleanup.
if (!file.delete()) System.err.printf("Oops, failed to delete %s. Cleanup yourself.%n", file.getAbsolutePath());
}
}
Résultats à mon Latitude E5500 avec un Seagate Momentus 7200.3 harddisk:
Creating file .. finished, file size: 99 MB. Reading through funnel .. finished in 1593 ms. Reading through bottle .. finished in 7760 ms.
Autres conseils
r1
est également plus pratique lorsque vous lisez flux basé sur la ligne comme supports de BufferedReader
méthode readLine
. Vous ne devez pas lire le contenu dans une mémoire tampon de tableau de caractères ou les caractères un par un. Cependant, vous devez fonte r1
à BufferedReader
ou utiliser explicitement ce type pour la variable.
J'utilise souvent cet extrait de code:
BufferedReader br = ...
String line;
while((line=br.readLine())!=null) {
//process line
}
En réponse à la question de Ross Studtman dans le commentaire ci-dessus (mais aussi pertinente pour l'OP):
BufferedReader reader = new BufferedReader(new InputStreamReader(new BufferedInputSream(inputStream), "UTF-8"));
Le BufferedInputStream
est superflu (et la performance des préjudices probablement dû à la copie étrangère). En effet, les caractères demandes de BufferedReader
du InputStreamReader
en gros morceaux en appelant InputStreamReader.read(char[], int, int)
, qui à son tour (par StreamDecoder
) appelle InputStream.read(byte[], int, int)
lire un grand bloc d'octets de la InputStream
sous-jacent.
Vous pouvez vous convaincre que cela est si en exécutant le code suivant:
new BufferedReader(new InputStreamReader(new ByteArrayInputStream("Hello world!".getBytes("UTF-8")) {
@Override
public synchronized int read() {
System.err.println("ByteArrayInputStream.read()");
return super.read();
}
@Override
public synchronized int read(byte[] b, int off, int len) {
System.err.println("ByteArrayInputStream.read(..., " + off + ", " + len + ')');
return super.read(b, off, len);
}
}, "UTF-8") {
@Override
public int read() throws IOException {
System.err.println("InputStreamReader.read()");
return super.read();
}
@Override
public int read(char[] cbuf, int offset, int length) throws IOException {
System.err.println("InputStreamReader.read(..., " + offset + ", " + length + ')');
return super.read(cbuf, offset, length);
}
}).read(); // read one character from the BufferedReader
Vous verrez la sortie suivante:
InputStreamReader.read(..., 0, 8192)
ByteArrayInputStream.read(..., 0, 8192)
Ceci démontre que la demande BufferedReader
une grande partie des caractères de la InputStreamReader
, ce qui demande une grande partie d'octets de la InputStream
sous-jacent.
FWIW, si vous ouvrez un fichier en Java 8, vous pouvez utiliser le Files.newBufferedReader (chemin) . Je ne sais pas comment la performance se compare aux autres solutions décrites ici, mais au moins il pousse la décision de ce concept de tampon dans le JDK.