Warum liest BufferedInputStream eine Datei byteweise schneller als FileInputStream?

70

Ich habe versucht, eine Datei mithilfe von FileInputStream in ein Array einzulesen, und das Einlesen einer ~ 800-KB-Datei in den Speicher dauerte ca. 3 Sekunden. Ich habe dann den gleichen Code ausprobiert, außer dass der FileInputStream in einen BufferedInputStream eingewickelt war und es ungefähr 76 Millisekunden dauerte. Warum wird das Lesen einer Datei byteweise mit einem BufferedInputStream so viel schneller durchgeführt, obwohl ich sie immer noch byteweise lese? Hier ist der Code (der Rest des Codes ist völlig irrelevant). Beachten Sie, dass dies der "schnelle" Code ist. Sie können den BufferedInputStream einfach entfernen, wenn Sie den "langsamen" Code möchten:

InputStream is = null;

    try {
        is = new BufferedInputStream(new FileInputStream(file));

        int[] fileArr = new int[(int) file.length()];

        for (int i = 0, temp = 0; (temp = is.read()) != -1; i++) {
            fileArr[i] = temp;
        }

BufferedInputStream ist über 30-mal schneller. Weit mehr als das. Warum ist das so und ist es möglich, diesen Code effizienter zu gestalten (ohne externe Bibliotheken zu verwenden)?

ZimZim
quelle

Antworten:

124

In liest FileInputStreamdie Methode read()ein einzelnes Byte. Aus dem Quellcode:

/**
 * Reads a byte of data from this input stream. This method blocks
 * if no input is yet available.
 *
 * @return     the next byte of data, or <code>-1</code> if the end of the
 *             file is reached.
 * @exception  IOException  if an I/O error occurs.
 */
public native int read() throws IOException;

Dies ist ein nativer Aufruf an das Betriebssystem, das die Festplatte zum Lesen des einzelnen Bytes verwendet. Dies ist eine schwere Operation.

Mit a BufferedInputStreamdelegiert die Methode an eine überladene read()Methode, die die 8192Anzahl der Bytes liest und sie puffert, bis sie benötigt werden. Es wird immer noch nur das einzelne Byte zurückgegeben (die anderen bleiben jedoch in Reserve). Auf diese Weise BufferedInputStreamruft das Betriebssystem weniger native Aufrufe zum Lesen aus der Datei auf.

Zum Beispiel ist Ihre Datei 32768bytelang. Um alle Bytes im Speicher mit a FileInputStreamabzurufen, benötigen Sie 32768native Aufrufe des Betriebssystems. Mit a BufferedInputStreambenötigen Sie nur 4, unabhängig von der Anzahl der read()Anrufe, die Sie (noch 32768) tätigen werden .

Um es schneller zu machen, sollten Sie die NIO- FileChannelKlasse von Java 7 in Betracht ziehen , aber ich habe keine Beweise, die dies unterstützen.


Hinweis: Wenn Sie verwendet FileInputStream‚s read(byte[], int, int)direkt stattdessen Methode, mit einem byte[>8192]würden Sie keine brauchen BufferedInputStreamVerpackung es.

Sotirios Delimanolis
quelle
1
Aah ich sehe, ich hätte zuerst die API überprüfen sollen, bevor ich gefragt habe. Es ist also einfach ein interner 8K-Puffer. Das macht Sinn. Vielen Dank. Der "effizientere" Teil ist nicht erforderlich, aber ich dachte, mein Code wäre in irgendeiner Weise übermäßig redundant gewesen. Ich denke es ist nicht.
ZimZim
13
@ user1007059 Gern geschehen. Beachten Sie, dass Sie bei einer direkten Verwendung FileInputStreamder read(byte[], int, int)Methode mit a byte[>8192]keine BufferedInputStreamUmhüllung benötigen würden .
Sotirios Delimanolis
@SotiriosDelimanolis Wann wird read()Byte für Byte verwendet und wann wird ein read(byte[])Array von Bytes verwendet ? Da ich denke, Array zu lesen ist immer besser. Dann können Sie mir ein Beispiel geben, wo read()Byte für Byte ODER read(byte[])Array von Byte verwendet werden soll. ODER BufferedInputStream.?
Asif Mushtaq
@UnKnown Ich habe kein gutes Beispiel. Möglicherweise enthält das erste Byte ein Flag über den Inhalt der Datei oder andere Metadaten. Ich glaube nicht, dass irgendjemand jemals eine ganze Datei mit lesen würde read().
Sotirios Delimanolis
1
@emily BufferedInputStreamist schneller, wenn Ihr Code jedes Mal weniger Bytes (nicht unbedingt nur ein Byte) als die Puffergröße lesen muss. BufferedInputStreamverhält sich optimistisch und liest mehr als nötig, sodass bei Ihrer Rückkehr bereits die nächste Charge vorhanden ist.
Sotirios Delimanolis
2

Ein BufferedInputStream, der um einen FileInputStream gewickelt ist, fordert Daten vom FileInputStream in großen Blöcken an (standardmäßig etwa 512 Byte, glaube ich). Wenn Sie also nacheinander 1000 Zeichen lesen, muss der FileInputStream nur zweimal auf die Festplatte übertragen werden . Das wird viel schneller gehen!

usha
quelle
3
Es mag plattformabhängig sein , aber es ist 8192 auf aktuellem Android .
Pevik
Gleich, 8K, für fast alle Plattformen.
Luftkissenfahrzeug voller Aale
1

Dies liegt an den Kosten für den Festplattenzugriff. Nehmen wir an, Sie haben eine Datei mit einer Größe von 8 KB. Zum Lesen dieser Datei ohne BufferedInputStream wird eine 8 * 1024-fache Zugriffsdiskette benötigt.

Zu diesem Zeitpunkt kommt BufferedStream in die Szene und fungiert als Vermittler zwischen FileInputStream und der zu lesenden Datei.

In einem Schuss werden standardmäßig 8 KB Bytes gespeichert, und FileInputStream liest dann Bytes von diesem mittleren Mann. Dies verkürzt die Betriebszeit.

private void exercise1WithBufferedStream() {
      long start= System.currentTimeMillis();
        try (FileInputStream myFile = new FileInputStream("anyFile.txt")) {
            BufferedInputStream bufferedInputStream = new BufferedInputStream(myFile);
            boolean eof = false;
            while (!eof) {
                int inByteValue = bufferedInputStream.read();
                if (inByteValue == -1) eof = true;
            }
        } catch (IOException e) {
            System.out.println("Could not read the stream...");
            e.printStackTrace();
        }
        System.out.println("time passed with buffered:" + (System.currentTimeMillis()-start));
    }


    private void exercise1() {
        long start= System.currentTimeMillis();
        try (FileInputStream myFile = new FileInputStream("anyFile.txt")) {
            boolean eof = false;
            while (!eof) {
                int inByteValue = myFile.read();
                if (inByteValue == -1) eof = true;
            }
        } catch (IOException e) {
            System.out.println("Could not read the stream...");
            e.printStackTrace();
        }
        System.out.println("time passed without buffered:" + (System.currentTimeMillis()-start));
    }
Huseyin
quelle