Advanced Java Services | Channels |
Die Channelklassen realisieren das Konzept des blockweisen Lesens. Während Streaming immer unidirektional ist, also entweder Lesen oder Schreiben, können Channels auch bidirektional sein, sodaß ein und derselbe Channel zum Lesen und zum Schreiben verwendet werden kann. Nicht alle Channels haben jedoch diese Fähigkeit. Trotz der verwirrenden Vielfalt der speziellen Channelinterfaces gibt es glücklicherweise nicht so viele Channelklassen.
Alle diese Klassen sind abstract. Man braucht also Methoden, die einem Instanzen dieser Typen liefern. Diese sind dann vom Typ einer realen Unterklasse.
Wir beginnen mit der getChannel()-Methode von FileInputStream.
Die Methode getChannel() von FileInputStream liefert ein Objekt vom Typ sun.nio.ch.FileChannelImpl.
Diese Klasse ist public im (versteckten) Package sun.nio.ch. Man findet sie in rt.jar, aber nicht im
mit dem JDK mitgelieferten Quellcode.
Wichtig:
Der von einem FileInputStream gewonnene FileChannel kann nur zum Lesen verwendet werden.
Ein Schreibversuch wird mit einer java.nio.channels.NonWritableChannelException beantwortet.
Nehmen wir an, wie haben eine kleine Datei mit einer Größe von 723 byte.
String file = "test.txt"; int bufsize = 1024; try(FileInputStream fis = new FileInputStream(file);) // FileNotFoundException { FileChannel fc = fis.getChannel(); ByteBuffer buffer = ByteBuffer.allocate(bufsize); System.out.println(buffer); int count = fc.read(buffer); System.out.println("count = " + count); System.out.println(buffer); } catch(FileNotFoundException ex) { ex.printStackTrace(); } catch(IOException ex) { ex.printStackTrace(); }
Der obige Vorgang ergibt dann folgende Konsolmeldungen.
java.nio.HeapByteBuffer[pos=0 lim=1024 cap=1024] count = 723 java.nio.HeapByteBuffer[pos=723 lim=1024 cap=1024]
Die read()-Methode füllt also den Buffer wie im vorigen Kapitel beschrieben.
Angenommen es handelt sich um Textdaten, die wir ausgeben wollen, müssen wir den Buffer mit flip() zum Auslesen umkonfigurieren. Die Methode flip() erledigt dies. Sie setzt limit auf den Wert von position und anschließen position auf 0.
buffer.flip(); System.out.println(buffer); // java.nio.HeapByteBuffer[pos=0 lim=723 cap=1024] byte[] barr = new byte[count]; buffer.get(barr, 0, count); String st = new String(barr, Charset.defaultCharset());
Wenn wir denselben Buffer wiederverwenden wollen, dann müssen alle Positionszeiger auf den Ausgangszustand gestellt werden:
buffer.clear(); // setzt den Positionszeiger auf Anfang, aber löscht nichts count = fc.read(buffer); System.out.println("count = " + count); // -1
Da wir beim ersten Lesen bereits die ganze Datei eingelesen haben, liefert read() den Wert -1, also das Dateiende.
Die Methode getChannel() von FileOutputStream liefert ein Objekt vom Typ sun.nio.ch.FileChannelImpl.
Diese Klasse ist public im (versteckten) Package sun.nio.ch. Man findet sie in rt.jar, aber nicht im
mit dem JDK mitgelieferten Quellcode.
Wichtig:
Der von einem FileInputStream gewonnene FileChannel kann nur zum Schreiben verwendet werden.
Ein Leseversuch wird mit einer java.nio.channels.NonreadableeChannelException beantwortet.
Nehmen wir an wir haben einen Text, den wir mit Hilfe eines Channels schreiben wollen. Wir legen einen ByteBuffer an und bringen diesen Text in den Buffer.
String file = "dest.txt"; String text = "Der Mensch kann wohl tun, was er will, aber er kann nicht wollen, was er will (Schopenhauer)"; int bufsize = 1024; ByteBuffer buffer = ByteBuffer.allocate(bufsize); byte[] barr = text.getBytes(); buffer.put(barr, 0, barr.length); // 92 byte
Mit flip() müssen wir nun den Buffer zum Auslesen bereit machen. Danach können wir den Buffer der write()-Methode von FileChannel übergeben. Das letzte clear() schaltet den Buffer wiedr auf Schreiben um.
buffer.flip(); // flip vor write notwendig // flip setzt das limit, damit nicht zuviel weggeschrieben wird fc.write(buffer); // buffer (wieder) zum Schreiben bereit machen buffer.clear(); // clear oder rewind
Das ganze setzen wir in ein "try-with-resources"-Konstrukt, um die Datei automatisch zu schließen.
String file = "dest.txt"; String text = "Der Mensch kann wohl tun, was er will, aber er kann nicht wollen, was er will (Schopenhauer)"; int bufsize = 1024; ByteBuffer buffer = ByteBuffer.allocate(bufsize); try(FileOutputStream fos = new FileOutputStream(file);) { FileChannel fc = fos.getChannel(); // text in den Buffer bringen byte[] barr = text.getBytes(); buffer.put(barr, 0, barr.length); // 92 byte // buffer zum Auslesen bereit machen buffer.flip(); // flip vor write notwendig //System.out.println("limit = " + buffer.limit()); // flip setzt das limit, damit nicht zuviel weggeschrieben wird fc.write(buffer); // buffer zum Schreiben bereit machen buffer.clear(); // clear oder rewind } catch(FileNotFoundException ex) { ex.printStackTrace(); } catch(IOException ex) { ex.printStackTrace(); }
Die Methode getChannel() von RandomAccessFile liefert ebenfalls ein Objekt vom Typ sun.nio.ch.FileChannelImpl.
Diese Klasse ist public im (versteckten) Package sun.nio.ch. Man findet sie in rt.jar, aber nicht im
mit dem JDK mitgelieferten Quellcode.
Wichtig:
Der von RandomAccessFile gewonnene FileChannel kann wie zu Erwarten sowohl zum Lesen als auch zum Schreiben verwendet werden.
Das folgende Codeschnipsel liest eine kleine Textdatei ein, wandelt ihn in Kleinschrift bzw. Großschrift um und schreibt ihn wieder in dieselbe Datei.
try(RandomAccessFile raf = new RandomAccessFile(file, mode);) // FileNotFoundException { FileChannel fc = raf.getChannel(); ByteBuffer buffer = ByteBuffer.allocate(bufsize); // aus dem mChannel lesen, in den Buffer schreiben String text = read(fc, buffer); // throws IOException // Umwandlung in Klein- bzw. Großbuchstaben text = Character.isUpperCase(text.charAt(0)) ? text.toLowerCase() : text.toUpperCase() ; // Channelposition auf 0 setzen fc.position(0); //aus dem Buffer lesen, in den Channel schreiben write(fc, buffer, text); } catch(FileNotFoundException ex) { ex.printStackTrace(); } catch(IOException ex) { ex.printStackTrace(); }
Die zugehörige read()-Methode
private static String read(FileChannel fc, ByteBuffer buffer) throws IOException { StringBuilder sb = new StringBuilder(); for(int count = 0; (count = fc.read(buffer)) != -1;) // IOException { // buffer wurde gefüllt buffer.flip(); // buffer zum Auslesen einrichten byte[] barr = new byte[count]; buffer.get(barr, 0, count); String st = new String(barr, Charset.defaultCharset()); sb.append(st); buffer.clear(); // setzt den Positions-Zeiger auf Anfang, } return sb.toString(); }
Die zugehörige write()-Methode
private static void write(FileChannel fc, ByteBuffer buffer, String text) throws IOException { byte[] barr = text.getBytes(); int bufsize = buffer.capacity(); // bufsize+1 damit man in die schleife kommt for(int offset = 0, rest = bufsize+1; rest > bufsize; offset += bufsize) { buffer.clear(); rest = barr.length - offset; if (rest <= bufsize) bufsize = rest; buffer.put(barr, offset, bufsize); // buffer füllen buffer.flip(); // zum Auslesen einrichten fc.write(buffer); // write liest bis zum Limit } }
Hinweis:
Die folgende Methoden liefern identische Werte:
randomAccessFile.getFilePointer() und filechannel.position().
Die folgende Methoden setzen den Dateizeiger auf dieselbe Position:
randomAccessFile.seek(newPosition) und filechannel.position(newPosition).