09.03.04 java.io.Reader und java.io.Writer
Sie kennen aus den letzten Kapiteln bereits die Klassen java.io.InputStream
und java.io.OutputStream
. Dieses Kapitel befasst sich mit den beiden verbleibenden Basis-Streams java.io.Reader
und java.io.Writer
. Da sich diese beiden abstrakten Klassen nur sehr wenig von den bereits bekannten unterscheiden, soll an dieser Stelle ein gemeinsames Kapitel für den Reader
und den Writer
genügen.
Der Hauptunterschied liegt darin, dass Reader
und Writer
im Gegensatz zu InputStream
und OutputStream
nur mit Zeichen (char
) umgehen können. Dies ist immer dann nützlich, wenn Sie mit gewöhnlichem Text arbeiten. Aus diesem Grund arbeiten die Methoden der Reader
und Writer
auch in den meisten Fällen (ausgenommen sind read()
und write(int i)
) ausschließlich mit Zeichen oder Zeichenketten.
Konkret wurde beim Reader
im Vergleich zum InputStream
aus der Methode read(byte[] b)
die Methode read(char[] cbuf)
, und aus read(byte[] b, int off, int len)
die Methode read(char[] cbuf, int off, int len)
. Zusätzlich wurden die Methoden read(CharBuffer target)
, welche versucht, die zu lesenden Zeichen in einen CharBuffer
zu speichern und ready()
, welche true
zurückliefert, falls der Reader
alle Daten verarbeitet hat, so dass mit dem Lesen begonnen werden kann, hinzugefügt. Dafür entfällt die Methode available
der InputStream
-Klasse. Anbei der Quellcode der Reader
-Klasse zum besseren Verständnis.
package java.io; public abstract class Reader implements Readable, Closeable { protected Object lock; protected Reader() { this.lock = this; } protected Reader(Object lock) { if (lock == null) { throw new NullPointerException(); } this.lock = lock; } public int read(java.nio.CharBuffer target) throws IOException { int len = target.remaining(); char[] cbuf = new char[len]; int n = read(cbuf, 0, len); if (n > 0) target.put(cbuf, 0, n); return n; } public int read() throws IOException { char cb[] = new char[1]; if (read(cb, 0, 1) == -1) return -1; else return cb[0]; } public int read(char cbuf[]) throws IOException { return read(cbuf, 0, cbuf.length); } abstract public int read(char cbuf[], int off, int len) throws IOException; private static final int maxSkipBufferSize = 8192; private char skipBuffer[] = null; public long skip(long n) throws IOException { if (n < 0L) throw new IllegalArgumentException("skip value is negative"); int nn = (int) Math.min(n, maxSkipBufferSize); synchronized (lock) { if ((skipBuffer == null) || (skipBuffer.length < nn)) skipBuffer = new char[nn]; long r = n; while (r > 0) { int nc = read(skipBuffer, 0, (int)Math.min(r, nn)); if (nc == -1) break; r -= nc; } return n - r; } } public boolean ready() throws IOException { return false; } public boolean markSupported() { return false; } public void mark(int readAheadLimit) throws IOException { throw new IOException("mark() not supported"); } public void reset() throws IOException { throw new IOException("reset() not supported"); } abstract public void close() throws IOException; }
Im Gegensatz zum InputStream
muss bei Readern
die close
-Methode überschrieben werden. Die read()
-Methode hingegen nicht, dafür aber read(char cbuf[], int off, int len)
.
Beim Writer
ist die Situation ähnlich. Aus write(byte[] b)
und write(byte[] b, int off, int len)
wird write(char[] cbuf)
und write(char[] cbuf, int off, int len)
. close()
und flush()
bleiben erhalten, sind aber abstrakt. Zusätzlich gibt es noch die Möglichkeit, write
mit einem String
anstelle eines char
-Arrays aufzurufen. Dies ist auch möglich, wenn nicht der komplette String
geschrieben werden soll. Hierfür wird write(String str, int off, int len)
verwendet. Alternativ können die write
-Aufrufe auch durch append
-Methoden mit einer CharSequence
bzw. einem char
als Übergabeparameter ersetzt werden. Diese rufen aber prinzipiell nur die entsprechenden write
-Methoden auf.
package java.io;
public abstract class Writer implements Appendable, Closeable, Flushable {
private char[] writeBuffer;
private final int writeBufferSize = 1024;
protected Object lock;
protected Writer() {
this.lock = this;
}
protected Writer(Object lock) {
if (lock == null) {
throw new NullPointerException();
}
this.lock = lock;
}
public void write(int c) throws IOException {
synchronized (lock) {
if (writeBuffer == null){
writeBuffer = new char[writeBufferSize];
}
writeBuffer[0] = (char) c;
write(writeBuffer, 0, 1);
}
}
public void write(char cbuf[]) throws IOException {
write(cbuf, 0, cbuf.length);
}
abstract public void write(char cbuf[], int off, int len) throws IOException;
public void write(String str) throws IOException {
write(str, 0, str.length());
}
public void write(String str, int off, int len) throws IOException {
synchronized (lock) {
char cbuf[];
if (len <= writeBufferSize) {
if (writeBuffer == null) {
writeBuffer = new char[writeBufferSize];
}
cbuf = writeBuffer;
} else { // Don't permanently allocate very large buffers.
cbuf = new char[len];
}
str.getChars(off, (off + len), cbuf, 0);
write(cbuf, 0, len);
}
}
public Writer append(CharSequence csq) throws IOException {
if (csq == null)
write("null");
else
write(csq.toString());
return this;
}
public Writer append(CharSequence csq, int start, int end) throws IOException {
CharSequence cs = (csq == null ? "null" : csq);
write(cs.subSequence(start, end).toString());
return this;
}
public Writer append(char c) throws IOException {
write(c);
return this;
}
abstract public void flush() throws IOException;
abstract public void close() throws IOException;
}[/sourcecode]
Auch bei einem Writer
muss write(char cbuf[], int off, int len)
anstelle von write(char c)
überschrieben werden.
Etwas verwirrt mich an dem Quellcode zu der Klasse Reader:
public int read(java.nio.CharBuffer target) throws IOException {
int len = target.remaining();
char[] cbuf = new char[len];
int n = read(cbuf, 0, len);
if (n > 0)
target.put(cbuf, 0, n);
return n;
}
Mit target.put wird ja der übergebene CharBuffer manipuliert. Soweit ich weiss, benutzt eine Methode doch eigentlich nur Kopien der Übergabeparamater, und nicht die Übergabeparamerter selbst, oder hab ich was völlig falsch verstanden?
Gruss
Cyrill
Hallo Cyrill,
das ist prinzipiell korrekt, Java verwendet Call by Value, scheint sich aber bei Objekten wie Call by Reference zu verhalten. Bevor ich das Rad neu erfinde verweise ich lieber auf diese sehr gute Erklärung: http://www.java-forum.org/allgemeines/4904-call-value-call-reference.html .
Grüße
Stefan