Java - Spezielle Themen

1 Klasse StringBuilder zur Verwaltung "dynamischer" Zeichenketten

1.1 Hintergrund

1.2 Überblick zu Methoden der Klasse StringBuilder

1.2.1 Konstruktoren

1.2.2 Hinzufügen von Teilen einer Zeichenkette

1.2.3 Entfernen von Teilen einer Zeichenkette

1.2.4 Verändern von Teilen einer Zeichenkette

1.2.5 (Abschließendes) Umwandeln in einen String

1.2.6 Ermitteln Anzahl enthaltener Zeichen

2 Exceptions in Java

2.1 Wesen

2.2 Begriffe

2.3 Grundprinzip des Exception-Mechanismus

2.4 Behandeln von Exceptions

2.4.1 die try-catch-Anweisung

Syntax:

try
{
    beliebig komplexe Anweisungsstruktur
}
catch (Ausnahmetyp Bezeichner)
{
   beliebig komplexe Anweisungsstruktur
}

Erklärung:

2.4.2 das "Fehler"objekt

2.4.3 Fortfahren nach einem "Fehler"

2.4.4 try-Anweisung mit mehreren catch-Handlern

Übersicht Basisklassen zu Exception

2.4.5 mehrere catch-Handler "zusammenfassen"

2.4.6 finally-Klausel

2.5 Werfen von Exceptions - gesteuert durch die Entwickler

2.5.1 Bisher

Falls eine Ausnahmesituation aufgetreten ist, wurde dies vom Laufzeitsystem (der Virtual Machine) erkannt und eine entsprechende Exception geworfens

2.5.2 Ziel

2.5.3 Vorgehensweise

  1. eine Bedingung formulieren, die die (fachspezifische) Ausnahmesituation erkennt

    z.B.: Fakultätsberechnung ... wenn das übergebene Argument zu klein bzw. zu groß ist ...

  2. ... falls diese Ausnahmesituation vorliegt, ein Exception-Objekt werfen

    "zunächst" ein neues Exception-Objekt erzeugen (und initialisieren)...

    z.B.
    new Exception("Meldungstext")

    ... und diese dann "werfen"

    z.B.
    						throw new Exception("Meldungstext");

2.5.4 Konsequenz

2.5.5 Hinweis

für die "catch-or-throw-Regel" gibt es eine Ausnahme!

Alle Exceptions, die "Kinder" der Klasse RuntimeException sind, können, müssen aber nicht behandelt werden;
Eine explizite Angabe der throws-Klausel ist aber auch nicht notwendig

Begründung: das Auslösen solcher Exceptions resultiert meist aus Programmierfehlern, die in der Regel vermeidbar sind

2.6 Eigene (benutzerdefinierte) Exceptionklassen

3 Dateiverarbeitung

3.1 Allgemeines

3.2 Character-Streams

3.2.1 als Input-Stream

(1) zum zeichenweisen Lesen aus einer Datei

Klasse: FileReader
(befindet sich im Package java.io , deshalb import java.io.*; notwendig!
Konstruktor: public FileReader(String dateiname)
hierbei KÖNNTE ein Laufzeitfehler auftreten!!!
deshalb KÖNNTE hier eine Exception geworfen werden!!!
für diese ist die "catch-or-throw!-Regel zu beachten!!!
deshalb - um diese Exception zu behandeln - MUSS die Instantiierung der Klasse in eine try-catch -Anweisung gesetzt werden (wie alle folgenden Methodenaufrufe auch)
Empfehlung für catch-Handler:
catch (IOException e)
{
    ...
}
Lesemethode: public int read()
liefert das nächste Zeichen aus der Textdatei (als int) ab, oder -1, sobald das "Dateiende" ( End- of- File) erreicht ist
Schließen-Methode: public void close()
Beispiel-Code
CharacterBeispiel1.java

(2) zum "zeilenweisen" Lesen aus einer Textdatei

Klasse: BufferedReader
Kontruktor: public BufferedReader(FileReader fr)
typische Verwendung:
BufferedReader br;
...
br = new BufferedReader (new FileReader (...));
...
Lese-Methode: public String readLine()
liefert die "nächste Zeile" aus der Textdatei (ohne abschließendes Carriage Return (CR) und Line Feed (LF) ) oder eine NULL-Referenz (am Dateiende)
Schließen-Methode: public void close()
Beispiel-Code
CharacterBeispiel2.java
CharacterBeispiel2a.java
CharacterBeispiel2b.java

3.2.2 als Output-Stream

(1) zum "zeilenweisen" Schreiben in eine Textdatei

Klasse: FileWriter
Konstruktor: public FileWriter(String Dateiname)
Öffnet eine Datei zum Schreiben...
Falls die Datei bereits existiert, wird sie "zerstört";
falls sie noch nicht existiert, wird sie neu angelegt!
public FileWriter(String Dateiname, true)
Öffnet eine Datei zum Schreiben IM ERWEITERUNGSMODUS! (append)
d.h. sollte die Datei bereits existieren, wird sie so vorbereitet, dass sich Schreibaufträge AM ENDE der Datei auswirken;
ansonsten wird sie neu angelegt
Schreiben-Methode: public void write(String s)
Diese Methode schreibt NICHT automatisch CR + LF in die Datei! d.h. diese beiden Zeichen müssen durch die Anwendung ZUSÄTZLICH berücksichtigt werden!
wiederholte Aufrufe schreiben das Argument als "nächste Zeile" in die Datei
Schließen-Methode: public void close()
Beispiel-Code
CharacterBeispiel3.java

(2) zum "zeichweisen" Schreiben in eine Textdatei

Klasse: ebenfalls FileWriter
Konstruktor: wie bereits bekannt
Schreiben-Methode: public void write(int zeichen)
Schreibt das Zeichen im niedrigwertigsten Byte des int -Argumentes als "nächstes Zeichen" in die Datei.
(Anmerkung: Umkehrung der read-Methode aus der der Klasse FileReader:
diese liest das "nächste Zeichen" aus der Datei, setzt es in das niedrigwertigste Byte einer int -Variablen und liefert den int -Wert ab ...)
Schließen-Methode: public void close()
Beispiel-Code
CharacterBeispiel4.java
CharacterBeispiel4a.java

(3) zum "formatierten" Schreiben in eine Textdatei

Klasse: PrintWriter
Kontruktor: public PrintWriter(FileWriter fw)
siehe Kontruktor der Klasse BufferedReader
Schreib-Methoden:
es stehen (auch) DIE Methoden zur Verfügung, die für die Standardausgabe (System.out) genutzt werden können!
insbesondere:
print(...)
println(...)
format(...)
Hinweis: die println(...) -Methode bewirkt einen KORREKTEN Zeilenumbruch, d.h. unter Windows werden die 2 Zeichen "\r" und "\n" geschrieben!
Schließen-Methode: public void close()

3.3 Byte Streams

3.3.1 Grundsätzliches

Im Gegensatz zu Character-Streams, die "formatierte" (d.h. im Editor darstellbare) Informationen transportierbar machen, handelt es sich hier um Binärdaten

innerhalb dieser Binärdaten können einzelne Bytes sinnvolle vollständige Informationen enthalten oder aber erst durch die Zusammenfassung mehrerer Bytes ergibt sich eine vollständige Information

z.B. 4 "nebeneinander" liegende Bytes stehen für einen float-Wert

Architektur der Klassensysteme ist ähnlich der für Character-Streams

3.3.2 Bytweises Schreiben bzw. Lesen aus einer Binärdatei

(1) Schreiben einzelner Bytes in eine Binärdatei

Klasse: FileOutputStream
Kontruktor: public FileOutputStream(String dateiname)
Schreib-Methode public void write(int b)
schreibt nur das niedrigwertigste Byte aus dem übergebenen int -Wert in die Datei!
Schreiben-Methode: wie üblich
Beispiel-Code
ByteBeispiel1.java

(2) Lesen einzelner Bytes aus einer Binärdatei

Klasse: FileInputStream
Konstruktor: public FileInputStream(String dateiname)
Lese-Methode: public int read()
liefert den Wert des nächsten Bytes (in der Datei) als int ab, bzw. -1, wenn das Dateiende erreicht ist.
Beispiel-Code
ByteBeispiel2.java

3.3.3 Schreiben bzw. Lesen der vollständigen Informationen in primitiven Datentypen

z.B. eine vollständige Information befindet sich als short in 2 "zusammenhängenden" Bytes oder

z.B. eine vollständige Information befindet sich als float in 4 "zusammenhängenden" Bytes oder

z.B. eine vollständige Information befindet sich als double in 8 "zusammenhängenden" Bytes oder

Schreiben der Werte aus primitiven Datentypen in eine Binärdatei:

Klasse: DataOutputStream
Konstruktor: public DataOutputStream(BufferedOutputStream bo)
Schreib-Methoden: pro primitiven Datentyp eine Methode:
public void writeFloat(float x)
public void writeDouble(double x)
public void writeLong(long x)
public void writeBoolean(boolean x)
public void writeChar(int x)
public void writeByte(int x)
public void writeShort(int x)
public void writeInt(int x)

alle Methoden schreiben das interne Bit-Muster des übergebenen Werts 1:1 in die Datei

Beispiel-Code
ByteBeispiel3.java
ByteBeispiel5.java

Lesen "primitiver" Datentypen aus einer Binärdatei

Klasse: DataInputStream
Konstruktor: public DataInputStream(BufferedInputStream b)
Lesen-Methoden: pro primitiven Datentyp eine Methode
public boolean readBoolean()
public byte readByte()
public char readChar()
public short readShort()
public int readInt()
public float readFloat()
public double readDouble()
public long readLong()
Beispiel-Code
ByteBeispiel4.java
ByteBeispiel6.java

3.4 Wahlfreies Arbeiten mit Binärdateien

Hintergrund:
Nachdem eine Binärdatei stream-orientiert (z.B. über DataOutputStream) erstellt worden ist, kann Sie nicht nur stream-orientiert ausgelesen werden (z.B. über DataInputStream), sondern auch "wahlfrei";
dabei wird gezielt ein bestimmer Wert aus der Datei gelesen (z.B. "sofort" der letzte gespeicherte Wert)
Voraussetzung: die Struktur der Binärdatei ist BEKANNT und EINHEITLICH
z.B. es befinden sich NUR short- oder NUR double-Werte in der Datei
Idee: jeder Wert in der Datei befindet sich an einer berechenbaren "Position" in der Datei, relativ zum Dateianfang;
um z.B. den n-ten Wert zu erhalten, muss zunächst, muss zunächst dessen "Position" bestimmt werden:
(n-1)* Länge_eines_Wertes_in Bytes
vor dem n-ten Wert liegen ( n - 1) Werte, die jeweils eine einheitliche "Länge" aufweisen
Umsetzung in Java
  1. Datei zum lesen öffnen
  2. Position des gewünschten Wertes bestimmen UND diese zur aktuellen "Position" in der Datei machen
  3. wie (von DataInputStream) gewohnt Lesen

3.4.1 Klasse: RandomAccessFile

Konstruktor: public RandomAccessFile(String dateinname, String modus)
bzw. public RandomAccessFile(File ver, String modus)
Hinweise zum zweiten Parameter:
  • hier wird festgelegt, in welchem "Modus" die Datei geöffnet werden soll
  • hierüber wird festgelegt, in welcher Richtung Daten fließen können
  • konkret:
    "r" NUR Lesezugriffe erlaubt
    "rw" sowohl Lese- wie auch Schreibzugriffe erlaubt (z.B. für Aktualisieren)
Auswahl weiterer Methoden:
zur Ermittlung der Dateigröße:
public long length()
liefert die Größe in Bytes
zum Positionieren in der Datei:
public void seek(long pos)
setzt den "Dateizeiger" an die angegebene Position relativ zum Dateianfang
zum Lesen aus der Datei:
wie in der Klasse DataInputStream...
Beispiel-Code
WahlfreiBeispiel.java

4 Arbeiten mit Verzeichniseinträgen

Hintergrund:
das Betriebssystem verwaltet neben Dateien auch (Unter-)Verzeichnisse, innerhlab derer weitere (Unter-)Verzeichnisse und/oder Dateien "angesiedelt" sein können
aus Java heraus kann auf solche "Verzeichniseinträge" zugegriffen werden ...
Klasse: File
Beschreibung dieser Klasse auch im Package java.io
Methoden dieser Klasse werfen KEINE IOException
Konstruktor: public File(String verzeichniseintrag)
problemlos, unabhängig davon, ob der Verzeichniseintrag existiert oder nicht!
Auswahl von Methoden:
zum Prüfen, ob im Filesystem der angegebene Verzeichniseintrag überhaupt existiert:
public boolean exists()
liefert true , falls der Verzeichniseintrag existiert, ansonsten false
zum Prüfen, ob hinter einem Verzeichniseintrag ein Verzeichnis steckt:
public boolean isDirectory()
zum Prüfen, ob hinter einem Verzeichniseintrag eine "normale" Datei steckt:
public boolean isFile()
Ermittlung der Größe einer ("normalen") Datei:
public long length()
...
Hinweis:
ALLEN Konstruktoren der "Dateiverarbeitungsklassen" kann ANSTELLE eines String-Argumentes die Adresse eine File-Objektes übergeben werden
z.B. public FileWriter(File ver)
damit könnte in einer Anwendung ZUNÄCHST geprüft werden, ob eine Datei bereits existiert;
abhängig vom Prüfergebnis kann dann unterschiedlich reagiert werden ...
Verzeichnisdemo.java
				0001: package dateiverarbeitung;
0002: 
0003: import java.io.File;
0004: import java.util.Scanner;
0005: 
0006: public class Verzeichnisdemo 
0007: {
0008:     public static void main(String[] args) 
0009:     {
0010:         File f;
0011:         Scanner ein = new Scanner(System.in);
0012:         String ver;
0013:         System.out.print("Zu untersuchender Verzeichniseintrag: ");
0014:         ver = ein.nextLine();
0015:         f = new File(ver);
0016:         if (f.exists())
0017:             if (f.isDirectory())
0018:                 System.out.println("'" + ver + "'" + " existiert und ist ein "
0019:                         + "Verzeichnis");
0020:             else
0021:                 if (f.isFile())
0022:                     System.out.println("'" + ver + "'" + " existiert und ist "
0023:                             + "eine \"normale\" Datei.\nSie ist " +
0024:                             f.length() + " Byte groß.\n" + 
0025:                             "Vollständiger Pfad: " + f.getAbsolutePath());
0026:                 else
0027:                     System.out.println("'" + ver + "'" + " existiert und ist "
0028:                             + "eine \"NICHT normale\" Datei.");
0029:         else
0030:             System.out.println("'" + ver + "'" + " existiert nicht");
0031:             
0032:     }
0033: }

5 Laufzeitoptimierte Version einer Dateikopier-Anwendung

5.1 Hintergrund

das bytweise Lesen und Schreiben kann bei sehr großen Dateien einige Zeit dauern

(pro Byte aus der zu kopierenden Datei muss die read -Methode und auch die write -Methode aufgerufen werden

5.2 Optimierung

Grundidee:
nicht nur ein Byte lesen/schreiben, sondern jeweils MEHRERE
Umsetzung:
andere Methode der Klasse FileInputStream zum Lesen verwenden:
public int read(byte[] arr)
der Parameter muss vom Aufrufer mit der Adresse eines Byte-Array-Objektes versorgt werden
z.B. byte[] a = new byte[4096];
das Ergebnis gibt wieder, wieviele Elemente des Arrays (d.h. wie viele Bytes) noch aus der Datei gefüllt werden konnten
z.B.
int geleseneBytes;
...
... geleseneBytes = read(a) ...
andere Methode der Klasse FileInputStream zum Schreiben verwenden:
public void write(byte[] arr, int ab, int bis)
1. Parameter: Adresse eines vom Aufrufer bereitzustellenden byte-Array-Objektes
2. Parameter: ab welchem Array-Element(Index) "auslesen"
3. Parameter: wie viele Elemente sollen berücksichtigt werden
DateiKopieren2.java
				0001: /*Beispiel zu Kopieren BELIEBIGER Dateien - laufzeitoptimiert*/
0002: package dateiverarbeitung;
0003: 
0004: import java.util.Scanner;
0005: import java.io.*;
0006: 
0007: public class DateiKopieren2
0008: {
0009:     public static void main(String[] args) 
0010:     {
0011:         Scanner ein = new Scanner(System.in);
0012:         String dnameOriginal, dnameKopie;
0013:         System.out.print("Zu kopierende Datei: ");
0014:         dnameOriginal = ein.nextLine();
0015:         System.out.print("Name der Kopie: ");
0016:         dnameKopie = ein.nextLine();
0017:         FileInputStream fein;
0018:         FileOutputStream faus;
0019:         
0020:         byte[] arr = new byte[4096];
0021:         int geleseneBytes;
0022:         
0023:         if (dnameOriginal.equals(dnameKopie))
0024:         {
0025:             System.err.println("Ein- und Ausgabe-Datei identisch");
0026:             return;
0027:         }
0028:         try
0029:         {
0030:             fein = new FileInputStream(dnameOriginal);
0031:             faus = new FileOutputStream(dnameKopie);
0032:             while ((geleseneBytes = fein.read(arr)) > 0)
0033:             {
0034:                 faus.write(arr, 0, geleseneBytes);
0035:                 System.out.println(geleseneBytes);
0036:             }
0037:             
0038:             fein.close();
0039:             faus.close();
0040:         } 
0041:         catch (IOException e)
0042:         {
0043:             System.out.println("Dateifehler - " + e.getMessage());
0044:         }
0045:     }
0046: }