Advanced   Java   Services Channels Back Next Up Home


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.


Channel-Hierarchie

java-channel-hierarchie.jpg


Methoden die Channels liefern

FileChannel-Hierarchie

java-filechannel-hierarchie.jpg

Wir beginnen mit der getChannel()-Methode von FileInputStream.


Der FileChannel 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.


Aus dem Channel lesen = in den Buffer schreiben

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.


Der FileChannel von FileOutputStream

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.


In den Channel schreiben = aus dem Buffer lesen

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();
}

Der FileChannel von RandomAccessFile

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).

Valid XHTML 1.0 Strict top Back Next Up Home