BufferedInputStream bzw. BufferedOutputStream

oraclin25

Erfahrenes Mitglied
Hallo zusammen,

ich versuche, die Klasse BufferedInputStream bzw. BufferedOutputStream zu verstehen. Dazu habe ich aus einer Literatur folgendes gefunden:

Als Beispiel für das Zusammenspiel von FileInputStream und FileOutputStream wollen wir ein Datei-Kopierprogramm entwerfen. Es ist einleuchtend, dass wir zunächst die Quelldatei öffnen müssen. Taucht ein Fehler auf, wird dieser zusammen mit allen anderen Fehlern in einer besonderen IOException-Fehlerbehandlung ausgegeben. Wir trennen hier die Fehler nicht besonders. Nach dem Öffnen der Quelle wird eine neue Datei angelegt. Das erledigt der Konstruktor FileOutputStream, dem es jedoch gleichgültig ist, ob es bereits eine Datei dieses Namens gibt; wenn, dann überschreibt es sie gnadenlos. Auch darum kümmern wir uns nicht. Wollten wir das berücksichtigen, sollten wir mit Hilfe der File-Klasse die Existenz einer gleichnamigen Datei prüfen.

Nach dem Anlegen können wir Byte für Byte auslesen und kopieren. Die Lösung über diesen nativen Weg ist natürlich in puncto Geschwindigkeit erbärmlich. Eine Lösung wäre, einen Dekorator dazwischenzuschalten, den BufferedInputStream. Doch das ist nicht nötig, weil wir einen Puffer mit read(byte[]) selbst füllen können. Da diese Methode die Anzahl tatsächlich gelesener Bytes zurückliefert, schreiben wir diese direkt mittels write() in den Ausgabepuffer. Hier erbringt eine Pufferung über eine Zwischen-Puffer-Klasse keinen Geschwindigkeitsgewinn, da wir ja selbst einen 64-KiB-Puffer einrichten.


Leider verstehe ich den 2. Absatz nicht. Wie habe ich mir BufferedInputStream bzw. BufferedOutputStream vorzustellen? Warum sollte dieses Konzept einen Geschwindigkeitsgewinn bringen im Vergleich zu dem naiven Ansatz?

Vielen Dank für Eure Hilfe.

Schöne Grüße aus Rheinland,

Eure Ratna
 
Hi

a)
Festplattendaten sind blockweise, also in Blöcke zu zB. jeweils 512 Byte eingeteilt.
Wenn man etwas lesen will müssen das immer kompette Blöcke sein.
Wenn man also Byte für Byte liest wäre das (theoretisch) der Extremfall,
weil pro Byte immer der komplette Block gelesen werden muss, in dem das Byte ist.
Für jedes Byte 512 Byte lesen. Von der sehr langsamen Festplatte (verglichen zu Prozessor/RAM).
Tut der Geschwindigkeit gar nicht gut.

Die Situation wird zwar durch das Betriebssystem entschärft (alle Nennenswerten speichern die kürzlich gebrauchten Blöcke selbst irgendwo im RAM, auch wenn das Programm zuerst nur ein Byte will, und lesen die weiteren Bytes dann von dort aus dem RAM).
Aber optimal ist es trotzdem nicht.
Besser wäre eben, ganze Blöcke im Programm anfordern.
64KB=genau 1024 ganze Blöcke zu 512 Byte.

b)
Die Aufrufe von read usw.
Was ist schneller, ca. 64000 Mal ein komplettes read oder
64000 Mal nur den wesentlichen Teil innerhalb read wiederholen und den Rest nur ein Mal?

usw.
...

Die BufferedStreams hätten, wie der Name sagt, auch eine Zwischenspeicherung von größeren Mengen in sich drin. Aber einerseits gibts ja schon eine Speicherschicht im OS, andererseits kannst du selbst 64KB auf einmal read´en. Kein BufferedIrgendwas nötig.
 
Hallo sheel,

vielen Dank für die ausführliche Antwort.

Code:
Was ist schneller, ca. 64000 Mal ein komplettes read...

Hier hab ich verstanden, was gemeint ist. Wir haben 64000 Byte, und alle müssen wir einzeln lesen. Es sind 1024 Blöcke (pro Block gibt es 512 Byte), die gelesen werden.

Wenn wir den Worst-Case nehmen:
es gibt 1024 Blöcke. Ein Block ist auf 512 Byte geteilt. Und es werden alle Blöcke bzw. alle 64000 Byte gelesen, da das File auf alle Blöcke verstreut ist.

Nun soll es ein wenig verbessert werden:
Code:
64000 Mal nur den wesentlichen Teil innerhalb read wiederholen und den Rest nur ein Mal

Bis wir zu diesem Schritt gekommen sind, müssen wir vorher die ganzen Blöcke anfordern? Im Zusammenhang mit dem BufferedOutputStream heißt das, dass die ganzen Blöcke erstmal in den Puffer reingeschrieben wurden?

Vielen Dank für die Mühe.

Schöne Grüße aus Rheinland,

Eure Ratna
 
Nicht ganz.

Bei b) meinte ich hauptsächlich Prozessorarbeit.

Ein Aufruf von read liest nicht nur, sondern muss auch seine Parameter überprüfen
(ist die gewünschte Länge wohl nicht unter 0, sonst Exception...usw).
Ein Aufruf, also eine Prüfung und 64K Byte lesen, ist schneller als
64K Prüfungen und 64K Lesen.

Verglichen zu a ist das nur ein kleiner Unterschied, aber immerhin ein Unterschied.


Bei a)
Eine Datei hat nicht jedes einzelne Byte auf andere Blöcke verteilt.
Eine Datei füllt schon komplette Blöcke (der letzte ist vllt. nicht ganz voll,
weil die Dateilänge ja kein Vielfaches von 512 sein muss).

Die Blöcke können auf der Platte verstreut sein, aber das macht für das Problem hier nichts
(es gibt auch einen Speicherbereich, welche Blöcke
in welcher Reihenfolge zu welcher Datei gehören. Egal).

Das Problem wäre: Angenommen, es gibt keinen einzigen Cache, auf keiner Ebene.
Nicht in deinem Programm und nicht im Betriebssystem.
Du willst jetzt Einzelbyte lesen.

Du liest das Byte 1 der Datei. Der komplette erste Block wird gelesen (512 Byte),
davon bekommst du Byte 1. Der Rest wird wieder "vergessen".
Du willst Byte 2. Der ganze Block 1 wird wieder gelesen, diesmal bekommst du Byte 2.
Für Byte 3 wird wieder der ganze erste Block gelesen...

Macht die 512-fache Arbeit für die Festplatte.
Und verglichen zum Prozessor ist die Platte sowieso langsam, das verschlimmert alles noch mehr.

Wenn man gleich sagt, ich will 512 Byte, wird der Block einmal gelesen
und man bekommt dann auch alle 512. Ohne wegschmeißen/wieder-lesen-müssen.


Warum Festplatten so blockweise sind?
Die sind nicht nur Stromzeug, sondern mechanisch.
Bei jedem Lesevorgang muss sich der Lesekopf in einen passenden Winkel bewegen
und auf ein paar Mikro/Nanosekunden genau abwarten,
wann von der drehenden Platte unter ihm die passende Stelle "vorbeikommt",
um dann schnell den Magnetismus dort zu ermitteln.
Einfach gesagt.
Das so genau zu machen, dass es einzelne Bit/Byte rauspicken kann wird unmöglich sein.
Zumindest im akzeptablem Preisbereich.
 
mhh.. ich stelle mir vor, nach dem Laden der zu schreibenden Datei in den Zwischenspeicher, müssen die Blöcke bzw. die Bytes ja auch einzeln gelesen werden?
 
Stimmt schon, aber der Zwischenspeicher/RAM
a) kann Byteweise arbeiten
b) und ist viel, viel, viel schneller als die Platte.

(Warum man nicht statt der "schlechten" Platte auch was RAM-artiges verwendet?
a) Beim Stromaus verliert der RAM alle Daten. Nicht gut zum Dauerhaftspeichern.
b) Vergleichbare Speichergrößen wie Festplatten (1,2TB...) wären als RAM viel teurer.

Die (relativ) neuen SSDs sind vom Prinzip her ca. sowas festplattentauglich.
Haben aber auch ihre eigenen Nachteile.)
 
Hallo sheel,

super.. vielen Dank für die Erklärung. So langsam hab ich das verstanden. Ich hab hier ein Beispiel:

Code:
//Construct the BufferedOutputStream object
            bufferedOutput = new BufferedOutputStream(new FileOutputStream(filename));
            
            //Start writing to the output stream
            bufferedOutput.write("Line one".getBytes());
            bufferedOutput.write("\n".getBytes()); 
            bufferedOutput.write("Line two".getBytes());
            bufferedOutput.write("\n".getBytes());
            
            //prints the character that has the decimal value of 65
            bufferedOutput.write(65);

Wohlgemerkt, hier geht es um einen OutputStream. Darauf wird durch eine Instanz von BufferedOutputStream gewrappt. Nach Ausführung folgender Zeilen:
Code:
bufferedOutput.write("Line one".getBytes());
bufferedOutput.write("\n".getBytes()); 
bufferedOutput.write("Line two".getBytes());
bufferedOutput.write("\n".getBytes());

sind die Daten ja noch nicht in "filename" geschrieben(also noch nicht in die Festplatte geschrieben), sondern sind bereits im Puffer bzw. Zwischenspeicher/RAM? geschrieben.

Erst wenn der Puffer geflusht wird:
Code:
bufferedOutput.flush();
, werden diese Daten fest ins file "filename" geschrieben.

Das ist effizienter als:
Code:
fos = new FileOutputStream(filename)
fos.write(...);
fos.write(...);
fos.write(...);
, da hier jede Zeile fos.write(...) die Daten direkt in die Festplatten bzw. ins File "filename" schreibt.

Ich nehme an, das is richtig so?

Nun eine Frage:
Ich habe bisher lediglich so etwas gesehen:
Code:
fos.write(contentInBytes);
Dabei ist contentInBytes wie folgt definiert:
Code:
String content = "This is the text content";
byte[] contentInBytes = content.getBytes();

contentInBytes is also ein Array vom Datentyp byte.
Ist das eigentlich der übliche Fall? Ist es nicht so, dass meistens eine Instanz von FileInputStream anstatt contentInBytes ist? Ich hab gerade im Google nachgeschaut:
Code:
public void write(int b)
           throws IOException

public void write(byte[] b)
           throws IOException

public void write(byte[] b,
         int off,
         int len)
           throws IOException

Anscheinend kann die Methode write keine Instanz von FileInputStream als Parameter aufnehmen? Aber wie wäre es denn, wenn die Eingabe quasi ein FileInputStream ist?

Vielen Dank für die Antwort.

Schöne Grüße aus Rheinland,
Eure Ratna
 
Die Sache mit write und flush: Genau, stimmt.

Nur im Kopf behalten, dass der BufferedOutputStream auch selbst in einem write flushen kann.
Der ist irgendwie so gemacht, dass er ab einer gewissen Zwischenspeichermenge
sich selbst auf die Platte raus entleert.
Natürlich erst dann, wenn ein paar Blöcke zusammengekommen sind, wenns sich lohnt.
Damit der vergleichsweise knappe Platz im RAM nicht so vollgestopft wird.

Also nicht denken "Wenn ich nie ein flush mache ändert sich die Datei nicht,
und ich kann so die writes rückgängig/ungeschehen machen."
Das stimmt so nicht.


Die Sache mit dem Fileinputstream:
Aus einem Inputstream kannst du Bytes lesen.
In einen Outputstream kannst du Bytes schreiben.
Die Bytes können zuerst aus einem Inputstream gelesen worden sein,
oder eben irgendwo anders herkommen.

Und was willst du jetzt damit erreichen,
einen Inputstream in einen Outputstream zu schreiben?
Willst du den Inhalt der einen Datei in die andere kopieren?
 
Code:
Willst du den Inhalt der einen Datei in die andere kopieren?

Ja, genau das möchte ich gerne haben. Wohlgemerkt --> mittels InputStream UND OutputStream. Ich dachte eigentlich:
input_file.txt mittels FileInputStream lesen
, danach mittels FileOutputStream ins File output_file.txt schrieben.

Geht das eigentlich?

Schöne Grüße aus Rheinland,

Eure Ratna
 
Ok

Geht das eigentlich?
Kopieren geht natürlich, nur nicht ganz so wie du es dir vorstellst.
Wohlgemerkt --> mittels InputStream UND OutputStream.
Jup, anders gehts auch nicht. Man braucht beide Arten.
Ich dachte eigentlich:
input_file.txt mittels FileInputStream lesen
, danach mittels FileOutputStream ins File output_file.txt schrieben.
Genau.
Nur musst du zwischen die Streams noch ein Bytearray etc. reinschieben.
Zuerst ins Array lesen, dann die Werte von dort aus schreiben.
 
Zurück