Galileo Computing < openbook >
Galileo Computing - Professionelle Buecher. Auch fuer Einsteiger.
Galileo Computing - Professionelle Buecher. Auch fuer Einsteiger.


Java ist auch eine Insel von Christian Ullenboom
Buch: Java ist auch eine Insel (Galileo Computing)
gp Kapitel 12 Datenströme und Dateien
gp 12.1 Datei und Verzeichnis
gp 12.1.1 Dateien und Verzeichnisse mit der Klasse File
gp 12.1.2 Dateieigenschaften und -attribute
gp 12.1.3 Sicherheitsprüfung
gp 12.1.4 Umbenennen und Verzeichnisse anlegen
gp 12.1.5 Die Wurzel aller Verzeichnisse
gp 12.1.6 Verzeichnisse listen und Dateien filtern
gp 12.1.7 Dateien und Verzeichnisse löschen
gp 12.1.8 Implementierungsmöglichkeiten für die Klasse File
gp 12.1.9 Verzeichnisse nach Dateien rekursiv durchsuchen
gp 12.2 Dateien mit wahlfreiem Zugriff
gp 12.2.1 Ein RandomAccessFile öffnen
gp 12.2.2 Aus dem RandomAccessFile lesen
gp 12.2.3 Schreiben
gp 12.2.4 Die Länge des RandomAccessFile
gp 12.2.5 Hin und her in der Datei
gp 12.3 Übersicht über wichtige Stream- und WriterReader
gp 12.3.1 Die abstrakten Basisklassen
gp 12.3.2 Übersicht über Ein-/Ausgabeklassen
gp 12.4 Eingabe- und Ausgabe-Klassen: InputStream und OutputStream
gp 12.4.1 Die Klasse OutputStream
gp 12.4.2 Ein Datenschlucker
gp 12.4.3 Anwendung der Klasse FileOutputStream
gp 12.4.4 Die Eingabeklasse InputStream
gp 12.4.5 Anwenden der Klasse FileInputStream
gp 12.4.6 Kopieren von Dateien
gp 12.4.7 Daten filtern durch FilterInputStream und FilterOutputStream
gp 12.4.8 Der besondere Filter PrintStream
gp 12.4.9 System.in und System.out
gp 12.4.10 Bytes in den Strom schreiben mit ByteArrayOutputStream
gp 12.4.11 Ströme zusammensetzen mit SequenceInputStream
gp 12.5 Ressourcen wie Grafiken aus dem Klassenpfad und aus Jar-Archiven laden
gp 12.6 Die Unterklassen von Writer
gp 12.6.1 Die abstrakte Klasse Writer
gp 12.6.2 Datenkonvertierung durch den OutputStreamWriter
gp 12.6.3 In Dateien schreiben mit der Klasse FileWriter
gp 12.6.4 StringWriter und CharArrayWriter
gp 12.6.5 Writer als Filter verketten
gp 12.6.6 Gepufferte Ausgabe durch BufferedWriter
gp 12.6.7 Ausgabemöglichkeiten durch PrintWriter erweitern
gp 12.6.8 Daten mit FilterWriter filtern
gp 12.7 Die Klassen um Reader
gp 12.7.1 Die abstrakte Basisklasse Reader
gp 12.7.2 Automatische Konvertierungen mit dem InputStreamReader
gp 12.7.3 Dateien lesen mit der Klasse FileReader
gp 12.7.4 StringReader und CharArrayReader
gp 12.8 Schachteln von Eingabe-Streams
gp 12.8.1 Gepufferte Eingaben mit der Klasse BufferedReader
gp 12.8.2 LineNumberReader zählt automatisch Zeilen mit
gp 12.8.3 Eingaben filtern mit der Klasse FilterReader
gp 12.8.4 Daten mit der Klasse PushbackReader zurücklegen
gp 12.9 Kommunikation zwischen Threads mit Pipes
gp 12.9.1 PipedOutputStream und PipedInputStream
gp 12.9.2 PipedWriter und PipedReader
gp 12.10 Datenkompression
gp 12.10.1 Die Java-Unterstützung beim Komprimieren und Zusammenpacken
gp 12.10.2 Datenströme komprimieren
gp 12.10.3 Zip-Archive
gp 12.11 Prüfsummen
gp 12.11.1 Die Schnittstelle Checksum
gp 12.11.2 Die Klasse CRC32
gp 12.11.3 Die Adler32-Klasse
gp 12.12 Persistente Objekte und Serialisierung
gp 12.12.1 Objekte speichern
gp 12.12.2 Objekte lesen
gp 12.12.3 Die Schnittstelle Serializable
gp 12.12.4 Nicht serialisierbare Attribute mit transient aussparen
gp 12.12.5 Das Abspeichern selbst in die Hand nehmen
gp 12.12.6 Tiefe Objektkopien
gp 12.12.7 Versionenverwaltung und die SUID
gp 12.12.8 Wie die ArrayList serialisiert
gp 12.12.9 Serialisieren in XML-Dateien
gp 12.12.10 JSX (Java Serialization to XML)
gp 12.12.11 XML-API von Sun
gp 12.13 Zugriff auf SMB-Server
gp 12.13.1 jCIFS
gp 12.14 Tokenizer
gp 12.14.1 StreamTokenizer
gp 12.14.2 CSV (Comma Separated Values)-Dateien verarbeiten
gp 12.15 Die Logging-API


Galileo Computing

12.10 Datenkompressiondowntop

Damit Daten weniger Platz auf dem Datenträger einnehmen, werden sie komprimiert. Bei Netzwerkverbindungen ist die logische Konsequenz, dass weniger Daten natürlich auch schneller übertragen werden.

Über alle Plattformen hinweg haben sich Standards gebildet. Zwei Kompressionsstandards sollen an dieser Stelle beschrieben werden.

compress/decompress, GZip/GunZip

Seitdem der LZW-Algorithmus im Juni 1984 im IEEE Journal beschrieben wurde, gibt es unter jedem Unix-System die Dienstprogramme compress und uncompress, die verlustfrei Daten zusammenpacken.1 Über dieses Format wird ein Datenstrom gepackt und entpackt. gzip und gunzip2 sind freie Varianten von compress beziehungsweise uncompress und unterliegen der GNU Public Licence. Das Format enthält eine zyklische Überprüfung bezüglich defekter Daten. Die Endung einer Datei, die mit gzip gepackt ist, ist mit ».gz« angegeben, wobei die Endung unter compress nur ».Z« ist. gzip behält die Rechte und Zeitattribute der Datei bei.

Komprimieren mit tar?

tar ist kein Programm, mit dem sich Dateien komprimieren lassen. tar bündelt lediglich mehrere Dateien zu einer neuen Datei, ohne sie zu komprimieren. Oftmals werden die mit tar gepackten Dateien anschließend mit gzip beziehungsweise bzip2 gepackt. Die Endung ist dann ».tar.Z«. Werden mehrere Daten erst in einem Tar-Archiv zusammengefasst und dann gepackt, ist die Kompressionsrate höher, als wenn jede Datei einzeln komprimiert wird. Der Grund ist einfach, denn das Kompressionsprogramm kann die Redundanz besser ausnutzen. Der Nachteil ist freilich, dass für eine Datei gleich das ganze Tar-Archiv ausgepackt werden muss.

Zip

Das Utility zip erzeugt ein Archiv aus mehreren Dateien. Der Unterschied zu gzip liegt darin, dass zip kein Filterprogramm mit einem Datenstrom ist, sondern ein Programm, welches sich Dateien nimmt, die zu einem Archiv zusammengebunden werden. Auf jede Datei lässt sich anschließend individuell zugreifen. PkZip ist unter MS-DOS ein Standardprogramm, unter Windows ist es oft WinZip. Obwohl Zip und GZip von der Anwendung her unterschiedlich arbeiten, verwenden sie denselben Algorithmus. Beide basieren auf Algorithmen, die im RFC 1952 definiert sind.

Es gibt auch unkomprimierte Zip-Archive, obwohl diese selten sind. Ein Beispiel dafür sind die Java-Archive des Internet Explorers. Die größte Datei ist unkomprimiert 5,3 MB groß, gepackt wäre sie 2 MB groß. Sie wurden vermutlich aus Gründen der Geschwindigkeit nicht gepackt, da sich die Daten aus unkomprimierten Archiven schneller lesen lassen, weil keine Prozessorleistung für das Entpacken aufgewendet werden muss.


Galileo Computing

12.10.1 Die Java-Unterstützung beim Komprimieren und Zusammenpackendowntop

Unter Java ist ein Paket java.util.zip eingerichtet, um mit komprimierten Dateien zu operieren. Das Paket bietet zur Komprimierung zwei allgemein gebräuchlich Formate: GZip/GunZip zum Komprimieren beziehungsweise Entkomprimieren für Datenströme und Zip zum Behandeln von Archiven und Komprimieren von Dateien. Ebenfalls wird das eigene Archiv-Format Jar durch das Paket java.util.jar unterstützt. Jar ist eine Erweiterung des Zip-Formats.

Tar-Archive werden nicht unterstützt, jedoch gibt es eine Reihe freier Implementierungen, unter anderem von der Apache-Group: http://cvs.apache.org/viewcvs.cgi/jakarta-ant/src/main/org/apache/tools/tar/; sie definieren Ein- und Ausgabeströme.


Galileo Computing

12.10.2 Datenströme komprimierendowntop

Zum Packen und Entpacken von Strömen wird GZip verwendet. Wir sehen uns nun einige Datenströme an, die auf der Klasse FilterOutputStream basieren.

Daten packen

Die Klasse java.util.zip bietet zwei Unterklassen von FilterOutputStream, die das Schreiben von komprimierten Daten erlauben. Um Daten unter dem GZip-Algorithmus zu packen, müssen wir einfach einen vorhandenen Datenstrom zu einem GZIPOutputStream erweitern.

FileOutputStream out = new FileOutputStream( Dateiname );
GZIPOutputStream zipout = new GZIPOutputStream( out );

class java.util.zip.GZIPOutputStream
extends DeflaterOutputStream

gp GZIPOutputStream( OutputStream out)
Erzeugt einen packenden Datenstrom mit der voreingestellten Puffergröße von 512 Byte.
gp GZIPOutputStream( OutputStream out, int size )
Erzeugt einen packenden Datenstrom mit einem Puffer der Größe size.

Beispiel Eine Datei nach dem GZip-Format packen; das Programm verhält sich wie das unter Unix bekannte gzip.

Listing 12.26 gzip.java

import java.io.*;
import java.util.zip.*;
class gzip
{
  private static int BLOCKSIZE = 8192;
  public static void main( String args[] )
  {
    if ( args.length != 1 ) {
      System.out.println( "Usage: gzip source" );
      return;
    }
    try
    {
      GZIPOutputStream zipout =
        new GZIPOutputStream( new FileOutputStream(args[0] + ".gz") );

      byte buffer[] = new byte[blockSize];
      FileInputStream in = new FileInputStream( args[0] );
      for ( int length; (length = in.read(buffer, 0, BLOCKSIZE)) != -1; )
        zipout.write( buffer, 0, length );
      in.close();
      zipout.close();
    }
    catch ( IOException e )
    {
      System.out.println( "Error: Couldn't compress "+args[0] );
    }
  }
}

Zunächst überprüfen wir, ob ein Argument auf der Kommandozeile vorhanden ist. Aus diesem Argument konstruieren wir mit der Endung ».gz« einen FileOutputStream. Um diesen manteln wir dann noch einen GZIPOutputStream. Mittels read() lesen wir aus dem FileInputStream einen Block Daten und schreiben ihn in den GZIPOutputStream, der die Daten dann komprimiert.

Abbildung
Hier klicken, um das Bild zu Vergrößern

Daten entpacken

Um die Daten zu entpacken, müssen wir nur den umgekehrten Weg beschreiten. Zum Einsatz kommt hier eine der zwei Unterklassen von FilterInputStream. Wieder wickeln wir um einen InputStream einen GZIPInputStream und lesen dann daraus.


class java.util.zip.GZIPInputStream
extends InflaterInputStream

gp GZIPInputStream( InputStream in, int size )
Erzeugt einen auspackenden Datenstrom mit einem Puffer der Größe size.
gp GZIPInputStream( InputStream in )
Erzeugt einen auspackenden Datenstrom mit der voreingestellten Puffergröße von 512 Byte.

Beispiel Eine Anwendung, die sich so verhält wie das unter Unix bekannte gunzip.

Listing 12.27 gunzip.java

import java.io.*;
import java.util.zip.*;
public class gunzip
{
  private static int BLOCKSIZE = 8192;
  public static void main( String args[] )
  {
    if ( args.length != 1 ) {
      System.out.println( "Usage: gunzip source" );
      return;
    }
    String zipname, source;
    if ( args[0].endsWith(".gz") ) {
      zipname = args[0];
      source = zipname.substring(0, zipname.length() - 3);
    }
    else {
      zipname = args[0] + ".gz";
      source = args[0];
    }
    try
    {
      GZIPInputStream zipin =
        new GZIPInputStream( new FileInputStream( zipname ) );
      byte buffer[] = new byte[blockSize];
      FileOutputStream out = new FileOutputStream( source );
      for ( int length; (length = zipin.read(buffer, 0, BLOCKSIZE)) != -1; )
        out.write( buffer, 0, length );
      out.close();
      zipin.close();
    }
    catch ( IOException e ) {
      System.out.println("Error: Couldn't decompress "+args[0]);
    }
  }
}

Endet die Datei mit ».gz«, so entwickeln wir daraus den herkömmlichen Dateinamen. Endet sie nicht mit diesem Suffix, so nehmen wir einfach an, dass die gepackte Datei diese Endung besitzt, der Benutzer dies aber nicht angegeben hat. Nach dem Zusammensetzen des Dateinamens holen wir von der gepackten Datei einen FileInputStream und packen einen GZIPInputStream darum. Nun öffnen wir die Ausgabedatei und schreiben in Blöcken zu 8 KB die Datei vom GZIPInputStream in die Ausgabedatei.


Galileo Computing

12.10.3 Zip-Archivetoptop

Der Zugriff auf die Daten eines Zip-Archivs unterscheidet sich schon deshalb vom Zugriff auf die Daten eines GZip-Streams, weil diese in Form eines Archivs vorliegen. Unter Zip wird jede Datei einzeln und unabhängig komprimiert. Wurden etwa über Tar vorher alle Dateien in ein unkomprimiertes Archiv übernommen, kann der Packalgorithmus GZip beim Packen dieser Dateisammlung bessere Ergebnisse erzielen, als wenn wie beim Zip-Verfahren alle Dateien einzeln gepackt würden.

Die Klassen ZipFile und ZipEntry

Objekte der Klasse ZipFile repräsentieren ein Zip-Archiv und bieten Funktionen, um auf die einzelnen Dateien (Objekte der Klasse ZipEntry) des Archivs zuzugreifen. Intern nutzt ZipFile eine Datei mit wahlfreiem Zugriff (Random Access File), so dass wir auf spezielle Einträge sofort zugreifen können. Ein Zip-Archiv der Reihe nach auszulesen, so wie ein gepackter Strom es vorschreibt, ist überflüssig.

Unter Java ist jeder Eintrag in einem Zip-Archiv durch ein Objekt der Klasse ZipEntry repräsentiert. Liegt einmal ein ZipEntry-Objekt vor, so können ihm durch verschiedene Methoden Dateiattribute entlockt werden, beispielsweise die Originalgröße, das Kompressionsverhältnis, das Datum, wann die Datei angelegt wurde, und Weiteres. Auch kann ein Datenstrom erzeugt werden, so dass sich eine komprimierte Datei im Archiv lesen und schreiben lässt.

Um auf die Dateien eines Archivs zuzugreifen, muss ein ZipFile-Objekt erzeugt werden, was auf zwei Arten geschehen kann: entweder über den Dateinamen oder über ein File-Objekt. Es gibt drei Konstruktoren für Zip-Archive: Ein Standard-Konstruktor ist protected und kann daher nicht öffentlich verwendet werden. Bei den beiden anderen muss des Weiteren IOException und ZipException abgefangen werden.

Abbildung
Hier klicken, um das Bild zu Vergrößern


class java.util.zip.ZipFile
implements ZipConstants

gp ZipFile( String name ) throws ZipException, IOException
Öffnet ein Zip-Archiv mit dem Dateinamen.
gp ZipFile( File file ) throws ZipException, IOException
Öffnet ein Zip-Archiv mit dem gegebenen File-Objekt.
gp ZipFile( File file, int mode ) throws ZipException, IOException
Öffnet ein Zip-Archiv mit dem gegebenen File-Objekt. Der Modus ZipFile.OPEN_READ oder ZipFile.OPEN_READ|ZipFile.OPEN_DELETE bestimmt den Zugriff auf das Archiv. Der Standard-Konstruktor öffnet mit OPEN_READ.

Anschließend lässt sich eine Enumeration mit der Methode entries() erzeugen, und alle Dateien lassen sich als ZipEntry zurückgeben.


Beispiel Nachfolgend sehen wir im Programmbeispiel, wie eine Iteration durch die Einträge des Archivs aussehen kann:
ZipFile z = new ZipFile( "foo.zip" );
for ( Enumeration e = z.entries(); e.hasMoreElements(); )
{
  ZipEntry ze = (ZipEntry) e.nextElement();
  System.out.println( ze.getName() );
}

Neben der Enumeration gibt es noch eine weitere Möglichkeit, um an bestimmte Einträge heranzukommen: getEntry(String). Ist der Name der komprimierten Datei bekannt, gibt es sofort ein ZipEntry-Objekt zurück.

Wollen wir nun die gesuchte Datei auspacken, holen wir mittels getInputStream(ZipEntry) ein InputStream-Objekt und können dann auf den Inhalt der Datei zugreifen. Es ist bemerkenswert, dass getInputStream() keine Methode von ZipEntry ist, so wie wir es erwarten würden, sondern von ZipFile, obwohl dies mit den eigentlichen Dateien nicht viel zu tun hat.


Beispiel Liegt im Archiv foo.zip die gepackte Datei largeFile, dann gelangen wir mit Folgendem an deren Inhalt:
ZipFile file = new ZipFile( "foo.zip" );
ZipEntry entry = file.getEntry( "largeFile" );
InputStream input = file.getInputStream( entry );
Der InputStream liefert dann den entpackten Inhalt der Datei.


class java.util.zip.ZipFile
implements ZipConstants

gp ZipEntry getEntry( String name )
Liefert eine Datei aus dem Archiv. null, wenn kein Eintrag mit dem Namen existiert.
gp InputStream getInputStream( ZipEntry ze ) throws IOException
Gibt einen Eingabestrom zurück, mit dem auf den Inhalt einer Datei zugegriffen werden kann.
gp String getName()
Liefert den Pfadnamen des Zip-Archivs.
gp Enumeration entries()
Gibt eine Aufzählung des Zip-Archivs in Form von ZipEntry-Objekten zurück.
gp int size()
Gibt die Anzahl der Einträge im Zip-Archiv zurück.
gp void close() throws IOException
Schließt das Zip-Archiv.
gp ZipEntry createZipEntry( String name )
Erzeugt ein neues ZipEntry-Objekt mit dem angegebenen Dateinamen.

Eine Funktion, die eine Datei auspackt

Um die Datei tatsächlich auszupacken, müssen wir nur noch eine neue Datei erzeugen, diese mit einem Datenstrom verbinden und dann die dekomprimierte Ausgabe dahin umleiten. Eine kompakte Funktion getEntry(ZipFile, ZipEntry), die auch noch aus Geschwindigkeitsgründen einen BufferedInputStream beziehungsweise BufferedOutputStream um die Kanäle packt, kann so aussehen:

public static void getEntry( ZipFile zipFile, ZipEntry target )
  throws ZipException,IOException
{
  try
  {
    File file = new File( target.getName() );
    BufferedInputStream bis = new BufferedInputStream(
                                zipFile.getInputStream( target ) );
    File dir = new File( file.getParent() );
    dir.mkdirs();
    BufferedOutputStream bos = new BufferedOutputStream(
                                 new FileOutputStream( file ) );
    for ( int c; ( c = bis.read() ) != EOF; )
      bos.write( (byte)c );
    bos.close();
  }
}

Das Objekt ZipEntry und die Datei-Attribute

Ein Objekt der Klasse ZipEntry repräsentiert jeweils eine Datei oder ein Verzeichnis eines Archivs. Diese Datei kann gepackt (dafür ist die Konstante ZipEntry.DEFLATED reserviert) oder auch ungepackt sein (angezeigt durch die Konstante ZipEntry.STORED). Auf dem Objekt können verschiedene Attribute gesetzt und abgefragt werden. Dadurch lassen sich Statistiken über Kompressionsraten und Weiteres ermitteln. Entsprechend den folgenden Funktionen überschreibt ZipEntry auch die Funktionen toString(), hashCode() und clone() der Klasse Object:


class java.util.zip.ZipEntry
implements ZipConstants, Cloneable

gp String getName()
Liefert den Namen des Eintrags.
gp void setTime( long time )
Ändert die Modifikationszeit des Eintrags.
gp long getTime()
Liefert die Modifikationszeit des Eintrags oder -1, wenn diese nicht angegeben ist.
gp void setSize( long size )
Setzt die Größe der unkomprimierten Datei. Wir werden mit einer IllegalArgument Exception bestraft, wenn die Größe kleiner 0 oder größer 0xFFFFFFFF ist.
gp long getSize()
Liefert die Größe der unkomprimierten Datei oder -1, falls unbekannt.
gp long getCrc()
Liefert die CRC-32-Checksumme der unkomprimierten Datei oder -1, falls unbekannt.
gp void setMethod( int method )
Setzt die Kompressionsmethode entweder auf STORED oder DEFLATED.
gp int getMethod()
Liefert die Kompressionsmethode entweder auf STORED, DEFLATED oder -1, falls unbekannt.
gp void setExtra( byte extra[] )
Setzt das optionale Zusatzfeld für den Eintrag. Übersteigt die Größe des Zusatzfelds 0xFFFFF Bytes, dann wird eine IllegalArgumentException ausgelöst.
gp byte[] getExtra()
Liefert das Extrafeld oder null, falls es nicht belegt ist.
gp void setComment( String comment )
Setzt einen Kommentar-String der 0xFFFF Zeichen lang sein darf (sonst wird eine IllegalArgumentException ausgelöst).
gp String getComment()
Gibt den Kommentar oder null zurück.
gp long getCompressedSize()
Liefert die Dateigröße nach dem Komprimieren oder -1, falls diese unbekannt ist. Ist der Kompressionstyp ZipEntry.STORED, dann stimmt diese Größe natürlich mit dem Rückgabewert von getSize() überein.
gp boolean isDirectory()
Liefert true, falls der Eintrag ein Verzeichnis ist. Der Name der Datei endet mit einem Slash '/'.

Dateien und Attribute als Inhalte eines Archivs

Wir haben nun die Informationen, um uns den Inhalt eines Archivs mit den Attributen anzeigen zu lassen. Wir wollen im Folgenden zwei Klassen entwickeln, die dies umsetzen. Zunächst die allgemeine Klasse ZIPList:

Listing 12.28 ZIPList.java

import java.io.*;
import java.text.*;
import java.util.*;
import java.util.zip.*;
class ZIPList
{
  public ZIPList( File file ) throws IOException
  {
    ZipFile zipfile = new ZipFile( file );
    s = new String[ zipfile.size() ];
    int i = 0;
    for ( Enumeration e = zipfile.entries(); e.hasMoreElements(); )
      s[i++] = buildInfoString( (ZipEntry) e.nextElement() );
    // for sorting the entries
    collator = Collator.getInstance( Locale.GERMANY );
    collator.setStrength( Collator.PRIMARY );
  }
  public void sort()
  {
    Arrays.sort( s, collator );
  }
  public String[] getFileList()
  {
    return s;
  }
  // private
  private String buildInfoString( ZipEntry entry )
  {
    String fileName = entry.getName();
    long   size = entry.getSize(),
           compressedSize = entry.getCompressedSize(),
           time = entry.getTime();
    SimpleDateFormat format = new SimpleDateFormat();
    String outTime = format.format( new Date(time) );
    return (entry.isDirectory() ? "+" : "-") + fileName
      + "\tSize: " + size
      + "\tPacked: " + compressedSize
      + "\t" + outTime;
  }
  private Collator collator;
  private String s[];
}

Die Klasse enthält einen Konstruktor, der den Dateinamen des Archivs verlangt. Intern legt dieser ein Array von Zeichenketten an, welches alle Namen der Dateien im Archiv aufnimmt. Nach dem Aufruf enthält das Feld alle Dateien, und die Methode getFileList() gibt dieses Feld nach außen weiter. Den Weg, die Daten sofort auszugeben, haben wir hier absichtlich nicht gewählt, denn über sort() lassen sich die Einträge noch sortieren. Wir nutzen hier die statische Funktion Arrays.sort(), um ein Feld von Strings zu sortieren. Damit diese auch korrekt nach der deutschen Schreibweise einsortiert werden, nutzen wir einen Collator, der mit dem Exemplar von Locale.GERMANY initialisiert wird.

Der Konstruktor baut das Feld mit den Datei- und Verzeichnisnamen durch die Enumeration auf. Dabei wird für jedes ZipEntry eine private Funktion buildInfoString() genutzt. Sie baut einen primitiven String zusammen, der Dateiname, Größe, Packrate und Datum anzeigt. Daneben werden alle Verzeichnisse mit einem Pluszeichen markiert und Dateien mit einem Minuszeichen. Die Konsequenz dieser Notation ist, dass Verzeichnisse bei der Gesamtanzeige zuerst ausgegeben werden.

Die zweite Klasse ist ZIPListDemo. Sie öffnet eine Datei über die Kommandozeile und beschwert sich, falls der Parameter fehlt. Existiert die Datei nicht, so wird genauso eine Fehlermeldung ausgegeben wie beim nicht erteilten Zugriff. Der Dateiname wird zu ZIPList() weitergereicht, und die Daten werden dann sortiert ausgegeben:

Listing 12.29 ZIPListDemo

class ZIPListDemo
{
  public static void main( String args[] ) throws IOException
  {
    String fileName = null;
    if ( args.length != 1 )
      fileName = args[0];
    File file = new File( fileName );
    if ( !file.isFile() ) {
      System.out.println( "Error: file \"" + fileName +
                          "\" not found." );
      System.exit(1);
    }
    if ( !file.canRead() ) {
      System.out.println("Error: file \"" + fileName +
                         "\" cannot be read.");
      System.exit(1);
    }
    ZIPList l = new ZIPList( file );
    l.sort();
    String s[] = l.getFileList();
    for ( int i = 0; i < s.length; i++ )
      System.out.println( s[i] );
  }
}

Ein ganzes Archiv Datei für Datei entpacken

Auch ein Programm zum Entpacken des gesamten Zip-Archivs ist nicht weiter schwierig. Wir müssen nur mit einer Enumeration durch das Archiv laufen und dann für jeden Eintrag eine Datei erzeugen. Dazu nutzen wir eine modifizierte Version von getEntry() aus dem vorigen Abschnitt. Die Methode saveEntry(ZipFile, ZipEntry) muss, wenn sie alle Dateien ordnungsgemäß auspacken soll, erkennen, ob es sich bei der Datei um ein Verzeichnis handelt oder nicht. Dazu verwenden wir die Funktion isDirectory() des ZipEntry-Objekts. Denn diese Funktion versichert uns, dass es sich um ein Verzeichnis handelt und wir daher einen Ordner mittels mkdirs() anlegen müssen und keine Datei. Wenn es allerdings eine Datei ist, so verhält sich saveEntry() wie getEntry().

Listing 12.30 UnZip.java

import java.util.zip.*;
import java.io.*;
import java.util.*;
public class UnZip
{
  public static final int EOF = -1;
  public static void main( String args[] )
  {
    if ( args.length != 1 )
      System.out.println( "Usage:java UnZip zipfile" );
    else
    {
      try
      {
        ZipFile zf = new ZipFile( args[0] );
        
        for ( Enumeration enum = zf.entries(); enum.hasMoreElements(); )
        {
          ZipEntry target = (ZipEntry) enum.nextElement();
          System.out.print( target.getName() + " ." );
          saveEntry( zf, target );
          System.out.println( ". unpacked" );
        }
      }
      catch( FileNotFoundException e ) {
        System.out.println( "zipfile not found" );
      }
      catch( ZipException e ) {
        System.out.println( "zip error..." );
      }
      catch( IOException e ) {
        System.out.println( "IO error..." );
      }
    }
  }
  
  public static void saveEntry( ZipFile zf, ZipEntry target )
                                throws ZipException,IOException
  {
    File file = new File( target.getName() );
    if ( target.isDirectory() )
      file.mkdirs();
    else
    {
      InputStream is = zf.getInputStream( target );
      BufferedInputStream bis = new BufferedInputStream( is );
      File dir = new File( file.getParent() );
      dir.mkdirs();
      FileOutputStream fos = new FileOutputStream( file );
      BufferedOutputStream bos = new BufferedOutputStream(fos);
      for ( int c; ( c = bis.read() ) != EOF; )  // oder schneller
        bos.write( (byte) c );
      bos.close();
      fos.close();
    }
  }
}

Kompressionsgrad einer Zip-Datei

Wird eine Datei über einen ZipOutputStream erzeugt, lässt sich die Kompressionsrate über die Methode setLevel(int) einstellen. Der Level ist eine Zahl zwischen 0 und 9. Die Kompression übernimmt ein Deflater-Objekt, welches im DeflaterOutputStream (die Oberklasse von ZipOutputStream) verwaltet wird. So ruft ZipOutputStream lediglich vom Deflater die Methode setLevel() auf.






1 Interessanterweise wurde danach der LZW-Algorithmus von der Sperry Company patentiert - dies zeigt eigentlich, wie unsinnig das Patentrecht in den USA ist.

2 Gibt es sogar für den C=64:>http://www.cs.tut.fi/~albert/Dev/gunzip/.





Copyright (c) Galileo Press GmbH 2004
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press GmbH, Gartenstraße 24, 53229 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de