assignments/Assignment_011/readme.md

8.6 KiB

Mist die Festplatte ist schon wieder voll

🎓 Benotetes Assignment 🎓

📆 Fällig: 03.06.2026 📆

Wenn wir ehrlich sind ist es auch nur begrenzt spannend, den ganzen Tag Kaninchen dabei zuzusehen, wie diese Fibonacci-Folgen produzieren. Auch die ganze Ruhe in den Alpen geht Ihnen inzwischen schwer auf die Nerven.

Endlich ist der Urlaub zuende und Sie kommen entspannt wieder nach Hause. Sogar der RE 10b ist pünktlich in Mannheim, sodass noch etwas vom Tag übrig bliebt.

Was kann mehr entspannen als ... eine Runde Battlefield 6.

Doch leider ist die Festplatte voll und der 67 GB Download passt nicht mehr drauf. Sie müssen dringend Platz schaffen! Also suchen Sie nach Möglichkeiten, die Daten auf Ihrer Festplatte, die eigentlich eine SSD ist, zu komprimieren.

Da kommt Ihnen die nächste PR2-Aufgabe gerade gelegen.

CompressingOutputStream

Gehen Sie in das Paket pr2.io.compressor.

Implementieren Sie eine Klasse namens CompressingOutputStream, die von FilterOutputStream abgeleitet ist. Diese Klasse soll eine Komprimierung der Daten durchführen, wobei ein sogenanntes run length encoding verwendet werden soll. Immer wenn ein Byte zwei- oder mehrmals hintereinander vorkommt, soll statt der wiederholten Bytes, die Sequenz 255, das Byte und die Anzahl der Wiederholungen geschrieben werden. So wird z.B. aus der Bytefolge { 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05 } durch die Kompression die Folge { 0xff, 0x05, 0x09 }. Wiederholt sich ein Byte nicht, wird es einfach direkt übernommen. Es sind maximal 253 Wiederholungen möglich. Kommt ein Byte häufiger als 254-mal hintereinander vor, werden zuerst die ersten 254 Byte komprimiert und dann die nächsten Bytes bearbeitet. (Die Bytewerte sind hier als Hexadezimalzahlen angegeben, 0xff würde dann in dezimalen Darstellung 255 entsprechen.)

Beachten Sie den Spezialfall, dass die Eingabedaten eine 255 (0xff) enthalten, in diesem Fall müssen Sie die Escapesequenz { 0xff, 0xff } schreiben, um dies bei der Dekompression erkennen zu können. So wird z.B. aus der Eingabe { 0x03, 0xff, 0x05 } komprimiert { 0x03, 0xff, 0xff, 0x05 }.

Testen Sie Ihre Implementierung ausführlich mit Unit-Tests. Sie können folgende Bytefolgen als Startpunkt nehmen, sie sind aber auf keinen Fall ausreichend.

Input Komprimiert
0x00, 0x01, 0x02, 0x03 -> 0x00, 0x01, 0x02, 0x03
0xff, 0xff, 0xff -> 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
0xff, 0x01, 0x02, 0x03 -> 0xff, 0xff, 0x01, 0x02, 0x03
0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 -> 0x00, 0xff, 0x01, 0x06
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05 -> 0xff, 0x05, 0x09
0x00, 0x01, 0x01, 0x03, 0x03, 0x03, 0x03 -> 0x00, 0xff, 0x01, 0x02, 0xff, 0x03, 0x04
0x01, 0x01, 0x03, 0x03, 0x03, 0x03 -> 0xff, 0x01, 0x02, 0xff, 0x03, 0x04
0x01, 0x02, 0x01, 0x03, 0x03, 0x03 -> 0x01, 0x02, 0x01, 0xff, 0x03, 0x03
0x01, 0x01, 0xff, 0x01, 0x02, 0x03 -> 0xff, 0x01, 0x02, 0xff, 0xff, 0x01, 0x02, 0x03
0x01, 0x01, 0xff -> 0xff, 0x01, 0x02, 0xff, 0xff
0xff -> 0xff, 0xff
0xff, 0x01, 0x01 -> 0xff, 0xff, 0xff, 0x01, 0x02
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 -> 0xff, 0x01, 0x09
0x01, 0x01, 0x01, 0x01, 0xff -> 0xff, 0x01, 0x04, 0xff, 0xff

DecompressingInputStream

Implementieren Sie eine Klasse namens DecompressingInputStream, die von InputStream abgeleitet ist. Diese Klasse soll eine Dekomprimierung der Daten durchführen, die vorher mit einem CompressingOutputStream komprimiert wurden.

Testen Sie Ihre Implementierung ausführlich mit Unit-Tests. Sie können die in der obigen Tabelle dargestellten Bytefolgen als Startpunkt nehmen, sollten aber noch mehr Fälle testen.

FileCompressor

Implementieren Sie eine Klasse namens FileCompressor. Diese nimmt von der Kommandozeile zwei Argumente an, die zwei Dateien bezeichnen. Die zuerst angegebene Datei wird mithilfe eines CompressingOutputStream komprimiert und das Ergebnis wird in die als zweite Option angegebene Datei geschrieben. Denken Sie an eine sinnvolle Fehlerbehandlung.

Alternativ kann man die Klasse auch ohne Optionen aufrufen. In diesem Fall werden die Daten von der Standard-Eingabe gelesen und das komprimierte Ergebnis auf die Standard-Ausgabe geschrieben.

Diese letzte Anforderung bedeutet, dass man z.B. das folgende machen kann:

$ echo "Duuuuuuuuuuuuduuuuuuuuuu" | \
    java -cp . pr2.io.compressor.tool.FileCompressor > \
    result.rle

FileDecompressor

Implementieren Sie eine Klasse namens FileDecompressor. Diese nimmt von der Kommandozeile zwei Argumente an, die zwei Dateien bezeichnen. Die zuerst angegebene Datei wird mithilfe eines DecompressingInputStreams dekomprimiert und das Ergebnis wird in die als zweite Option angegebene Datei geschrieben. Denken Sie an eine sinnvolle Fehlerbehandlung.

Alternativ kann man die Klasse auch ohne Optionen aufrufen. In diesem Fall werden die Daten von der Standard-Eingabe gelesen und das dekomprimierte Ergebnis auf die Standard-Ausgabe geschrieben.

Diese letzte Anforderung bedeutet, dass man z.B. das folgende machen kann:

$ echo "Duuuuuuuuuuuuduuuuuuuuuu" | \
     java -cp . pr2.io.compressor.tool.FileCompressor | \
     java -cp . pr2.io.compressor.tool.FileDecompressor
Duuuuuuuuuuuuduuuuuuuuuu

DirectoryCompressor

Schreiben Sie eine Klasse namens DirectoryCompressor. Diese nimmt von der Kommandozeile den Namen eines Verzeichnisses entgegen und komprimiert alle darin gefundenen Dateien mit dem oben beschriebenen FileCompressor. Die komprimierten Dateien werden mit der Dateierweiterung .rle versehen, z.B. wird aus der Datei MyFile.txt die Datei MyFile.txt.rle. Die Originaldateien werden nicht verändert.

Beachten Sie die folgenden möglichen Fehlerfälle:

  • Der Benutzer vergisst, einen Pfad anzugeben.
  • Das angegebene Verzeichnis existiert nicht.
  • Der angegebene Pfad ist gar kein Verzeichnis.
  • Das Verzeichnis existiert, darf aber nicht gelesen werden.
  • Das Verzeichnis existiert, darf aber nicht geschrieben werden.

Darüber hinaus können noch andere Fehler auftreten, die Sie bitte entsprechend abfangen und behandeln sollen.

Das Programm soll den Benutzer über die bereits komprimierten Dateien informieren. Hierzu soll die Ausgabe ungefähr wie folgt aussehen:

Komprimiere Dateien in /Users/thomas/Temp/compressme

f1.txt (342 Bytes) -> f1.txt.rle (23 Bytes) [6%]
f2.txt (1368 Bytes) -> f2.txt.rle (83 Bytes) [6%]
f3.txt (13680 Bytes) -> f3.txt.rle (803 Bytes) [5%]

Für jede komprimierte Datei wird eine neue Zeile ausgegeben.

DirectoryDecompressor

Implementieren Sie eine Java-Klasse namens DirectoryDecompressor, die die Umkehroperation zum DirectoryCompressor zur Verfügung stellt. Man gibt ihr über die Kommandozeile einen Verzeichnisnamen und sie dekomprimiert alle darin gefundenen Dateien, die die Endung .rle haben.

Die komprimierten Dateien werden nach einer erfolgreichen Dekompression gelöscht.

Beachten Sie, dass auch hier unterschiedliche Fehler auftreten könne, die Sie bitte entsprechend abfangen und behandeln sollten.

Das Programm soll den Benutzer über seine Fortschritte informieren. Hierzu soll die Ausgabe ungefähr wie folgt aussehen:

Dekomprimiere Dateien in /Users/thomas/Temp/compressme2

f1.txt.rle (23 Bytes) -> f1.txt (342 Bytes)
f2.txt.rle (83 Bytes) -> f2.txt (1368 Bytes)
f3.txt.rle (803 Bytes) -> f3.txt (13680 Bytes)

Geheime Datei

Wenn Sie alles fertig implementiert haben, wenden Sie Ihre Software auf die Datei bild.bmp.rle an und dekomprimieren Sie sie.

  • Was ist zu sehen?
  • Um welchen Faktor wurde die Datei komprimiert?
  • Warum ist der Kompressionsfaktor hier so hoch?