maybemali0757
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # Anwendungssysteme - Threads [ToC] ## Fragestellung/Ziel: - Was müssen Softwarearchitekten über Architektur und technologische Optionen wissen? - Welche grundlegenden Konzepte und Technologien sind für die Entwicklung verteilter Systeme notwendig? ## Notizen: ### 1. Rollen im Software Engineering: - Produktbesitzer: Definiert Produktvision und leitet Anforderungen ab. - Softwarearchitekt: Bestimmt die Architektur und trifft Technologieentscheidungen. - Softwareentwickler: Baut die Softwaresysteme. ### 2. Wissensbedarf der Architekten: - Überblick über verschiedene architektonische Stile und Technologieoptionen. - Qualitätsdienste (Quality of Service, QoS) berücksichtigen. ### 3. Kursinhalte und Technologien: - **Enterprise Programming:** - Erweitertes Java (Annotations, Reflections), Abhängigkeitsmanagement, Build-Tools, Tests, Logging, Versionskontrolle, Debugging, Dependency Injection. - **Verteilte Architekturen:** - Architektur als Konzept, QoS-Dimensionen, Schichten und Ebenen, Middleware. - **Datenmanagement:** - Relationale Datenbanksysteme, Transaktionen und SQL, Objekt-relationales Mapping, XML, JSON, Protobuf. - **Kommunikation:** - Synchrone vs. asynchrone Kommunikation, RPC, Messaging und Pub/Sub. - **Das Web:** - HTTP, HTML, Webdienste, REST, Web-APIs, GraphQL, JavaScript, AJAX, mobile Apps. - **Anwendungsplattformen:** - Java EE/Jakarta, serviceorientierte Architektur, Microservices, Cloud-Dienste und Cloud-native, Serverless Computing. ### 4. Kursziele: - Grundlegende Konzepte verstehen. - Mit einer Technologieglossar vertraut sein. - Einige Technologien tiefergehend behandeln, da sie sehr wichtig sind oder ein grundlegendes Konzept veranschaulichen. ## Zusammenfassung: - Der Kurs bietet einen umfassenden Überblick über die Rollen und erforderlichen Kenntnisse im Bereich Softwarearchitektur, insbesondere im Kontext verteilter Systeme. Die Inhalte decken eine breite Palette von Technologien und Methoden ab, die für die moderne Softwareentwicklung entscheidend sind. ## Schlüsselwörter: - Softwarearchitektur, Technologieoptionen, Enterprise Programming, verteilte Architekturen, Datenmanagement, Kommunikation, Webentwicklung, Anwendungsplattformen. # Programmier-Rekap ## Fragestellung/Ziel: - Wie funktionieren Konzepte der Nebenläufigkeit in der Programmierung? - Wie werden Streams und Sockets in Java verwendet? ## Notizen: ### 1. Nebenläufigkeit (Concurrency): - Parallele oder pseudo-parallele Ausführung von Anweisungen zur Leistungssteigerung. - Beispiele für die Umsetzung mittels Threads und `Runnable`-Schnittstelle in Java. - Lebenszyklus von Threads: Erstellung, Ausführung, Warten, Beendigung. - Synchronisation und Locking zur Verwaltung des Zugriffs auf gemeinsame Ressourcen. ### 2. Streams: - Streams repräsentieren Datenflüsse von einer Quelle zu einem Ziel (z.B. Dateien, Netzwerke). - Unterscheidung zwischen Byte-Streams und Character-Streams zur Datenverarbeitung. - Beispiele für den Einsatz von `FileInputStream`, `FileOutputStream`, `BufferedReader` und `PrintWriter`. ### 3. Sockets: - Verwendung von Sockets zur Netzwerkkommunikation zwischen Server und Client. - Aufbau und Management von Verbindungen mittels `ServerSocket` und `Socket`. - Implementierungsbeispiele für einfache Client-Server-Kommunikationen. ### 4. Wichtige Konzepte: - Threads und `Runnable` für die Nebenläufigkeit. - Locking und Synchronisation für den Zugriffsschutz. - Byte- und Character-Streams für die Datenverarbeitung. - Client-Server-Kommunikation über Sockets. ## Zusammenfassung: - Der Kursabschnitt bietet eine grundlegende Auffrischung wichtiger Programmierkonzepte in Java, fokussiert auf Nebenläufigkeit, Datenströme und Netzwerkkommunikation. Diese Grundlagen sind essentiell für fortgeschrittene Programmieraufgaben und verteilte Anwendungen. ## Schlüsselwörter: - Threads, Synchronisation, Streams, Sockets, Datenflussmanagement, Server-Client-Architektur. ## Aufgaben zu Advanced Concepts ### Aufgabe 1 – Unchecked Exceptions - Entwickeln Sie eine Methode addPositive(), die zwei positive Zahlen (ints, inklusive 0) addiert. Werden negative Zahlen als Eingabeparameter übergeben, soll eine IllegalArgumentException geworfen werden. Schreiben Sie weiter eine main-Methode, die zwei int-Werte von der Konsole einliest und soll der Nutzer um erneute Eingabe gebeten werden. - Für welche Fehlerfälle sind „Unchecked Excepetions“ zu verwenden? Sollten diese Exceptions auch im Methodenkopf angezeigt werden? ```java import java.util.Scanner; public class PositiveNumberAdder { public static int addPositive(int a, int b) { if (a < 0 || b < 0) { throw new IllegalArgumentException("Beide Zahlen müssen positiv oder null sein."); } return a + b; } public static void main(String[] args) { Scanner scanner = new Scanner(System.in); while (true) { try { System.out.print("Geben Sie die erste positive Zahl (oder 0) ein: "); int num1 = scanner.nextInt(); System.out.print("Geben Sie die zweite positive Zahl (oder 0) ein: "); int num2 = scanner.nextInt(); int result = addPositive(num1, num2); System.out.println("Das Ergebnis der Addition ist: " + result); break; } catch (IllegalArgumentException ex) { System.out.println("Fehler: " + ex.getMessage()); System.out.println("Bitte versuchen Sie es erneut."); } catch (Exception ex) { System.out.println("Ein unerwarteter Fehler ist aufgetreten. Bitte geben Sie nur ganze Zahlen ein."); scanner.nextLine(); // Dies leert den Scanner-Puffer. } } scanner.close(); } } ``` #### Erläuterung der Implementierung: 1. **Methode `addPositive(int a, int b)`**: - Überprüft, ob die übergebenen Werte `a` und `b` positiv oder null sind. Wenn einer der Werte negativ ist, wird eine `IllegalArgumentException` mit einer entsprechenden Nachricht geworfen. - Wenn keine Ausnahme geworfen wird, gibt die Methode die Summe der beiden Zahlen zurück. 2. **`main` Methode**: - Erstellt einen `Scanner` zum Einlesen von Benutzereingaben. - Verwendet eine `while`-Schleife, um die Eingaben in einer Schleife zu verarbeiten, bis gültige Eingaben gemacht wurden. - Die Eingaben werden durch die `nextInt()` Methode des `Scanner`-Objekts gelesen. - Ruft `addPositive()` mit den eingegebenen Werten auf und gibt das Ergebnis aus, wenn keine Ausnahme geworfen wird. - Fängt die `IllegalArgumentException` ab, um Fehlermeldungen auszugeben und die Schleife fortzusetzen. - Fängt allgemeine Ausnahmen ab, um den Scanner-Puffer zu leeren und Probleme mit ungültigen Eingaben (nicht-integer) zu behandeln. #### Zum Einsatz von Unchecked Exceptions: - **Unchecked Exceptions** (z.B. `IllegalArgumentException`) sind für Programmfehler gedacht, die während der Laufzeit auftreten können und oft auf Fehler im Code hinweisen, wie z.B. das Übergeben eines ungültigen Arguments. - Diese sollten **nicht** im Methodenkopf angezeigt werden (`throws`-Klausel), da sie zur Klasse der `RuntimeExceptions` gehören. Diese Art von Ausnahmen werden normalerweise nicht im Voraus überprüft (nicht "checked"), und der Compiler erfordert keine Behandlung oder Deklaration im Methodenkopf. - Unchecked Exceptions sollten eingesetzt werden, wenn Fehler auftreten können, die sich nicht auf externe Faktoren zurückführen lassen (zum Beispiel falsche Parameterwerte oder Null-Zugriffe), und sie sollen in der Regel von dem Entwickler während der Entwicklung berücksichtigt und korrigiert werden. #### Erklärung - Ungeprüfte Ausnahmen (oder "Unchecked Exceptions") in Java sind jene, die von RuntimeException und ihren Unterklassen abgeleitet sind. Sie werden normalerweise verwendet, um Programmierfehler zu signalisieren, wie z.B. ungültige Argumente, Zugriff auf null Referenzen oder falsche Indexpositionen in Datenstrukturen. Diese müssen nicht im Methodenkopf deklariert werden (d.h., sie müssen nicht in der throws-Klausel aufgeführt werden), da der Compiler nicht erfordert, dass der Aufrufer diese Ausnahmen behandelt oder deklariert. - Ungeprüfte Ausnahmen (oder "Unchecked Exceptions") in Java sind jene, die von RuntimeException und ihren Unterklassen abgeleitet sind. Sie werden normalerweise verwendet, um Programmierfehler zu signalisieren, wie z.B. ungültige Argumente, Zugriff auf null Referenzen oder falsche Indexpositionen in Datenstrukturen. Diese müssen nicht im Methodenkopf deklariert werden (d.h., sie müssen nicht in der throws-Klausel aufgeführt werden), da der Compiler nicht erfordert, dass der Aufrufer diese Ausnahmen behandelt oder deklariert. ### Aufgabe 2 – Checked Exceptions - Erstellen Sie eine Klasse ThreeObjectBucket. Diese Klasse soll bis zu 3 Objekte vom Typ Object speichern können und dazu intern ein Array der Länge 3 verwenden.Implementieren sie zudem Methoden zum Hinzufügen, Lesen und Entfernen von Objekten. - Erstellen Sie nun eine main-Methode, die versucht vier Objekte in Ihr Bucket einzufügen. Was geschieht? Ist dieses Verhalten erwünscht? Wie können Exceptions helfen das Verhalten im Fehlerfall zu verbessern? Welche Art von Exceptions erscheint geeignet? - Erstellen Sie nun eine eigene Exceptionklasse BucketFullException. Bieten Sie einen Konstruktor an, der das Setzen einer Fehlermeldung ermöglicht. Integrieren Sie diese Exception in die Klasse ThreeObjectBucket; behandeln und testen Sie ihr Auftreten in der main-Methode. #### Schritt 1: Definieren der `BucketFullException` Klasse ```java public class BucketFullException extends Exception { public BucketFullException(String message) { super(message); } } ``` Diese Klasse erbt von `Exception`, was sie zu einer Checked Exception macht. Sie enthält einen Konstruktor, der es ermöglicht, eine spezifische Fehlermeldung zu setzen. #### Schritt 2: Implementieren der `ThreeObjectBucket` Klasse ```java public class ThreeObjectBucket { private Object[] bucket; private int count; public ThreeObjectBucket() { bucket = new Object[3]; count = 0; } public void addObject(Object obj) throws BucketFullException { if (count >= 3) { throw new BucketFullException("Der Bucket ist voll. Kein weiteres Objekt kann hinzugefügt werden."); } bucket[count] = obj; count++; } public Object getObject(int index) { return bucket[index]; } public void removeObject(int index) { if (index >= 0 && index < count) { bucket[index] = null; // Optional: Shift remaining elements to the left for (int i = index; i < count - 1; i++) { bucket[i] = bucket[i + 1]; } bucket[count - 1] = null; count--; } } } ``` #### Schritt 3: `main` Methode zum Testen der Klasse ```java public class Main { public static void main(String[] args) { ThreeObjectBucket bucket = new ThreeObjectBucket(); try { bucket.addObject("Objekt 1"); bucket.addObject("Objekt 2"); bucket.addObject("Objekt 3"); // Versuch, ein viertes Objekt hinzuzufügen bucket.addObject("Objekt 4"); } catch (BucketFullException e) { System.out.println(e.getMessage()); } } } ``` #### Analyse des Verhaltens In der `main`-Methode wird versucht, vier Objekte in den `ThreeObjectBucket` einzufügen. Beim vierten Objekt wird eine `BucketFullException` geworfen. Dieses Verhalten ist erwünscht, da es sicherstellt, dass die Datenstruktur ihre Kapazitätsgrenze nicht überschreitet und der Programmierer über diesen Zustand informiert wird. #### Vorteile der Verwendung von Exceptions - **Klarheit und Sicherheit:** Der Einsatz von Checked Exceptions erzwingt eine Fehlerbehandlung, die zur Laufzeit potenzielle Probleme abfangen kann. Dadurch werden robustere und sicherere Anwendungen ermöglicht. - **Kontrolliertes Fehlermanagement:** Exceptions ermöglichen es, Fehler dort zu behandeln, wo es am sinnvollsten ist. Der Entwickler hat die Kontrolle darüber, wie das Programm auf bestimmte Fehler reagiert. Die `BucketFullException` als Checked Exception ist besonders geeignet, da sie den Entwickler zwingt, sich während der Entwicklung mit der Möglichkeit eines vollen Buckets auseinanderzusetzen und entsprechend zu planen. Dies verbessert die Robustheit und Wartbarkeit des Codes. ### Aufgabe 3 – Thread vs. Runnable - Erstellen Sie eine Klasse SimpleThread, welche von Thread erbt. Überschreiben sie nun die run-Methode sodass sie „I'm a simple Thread.“ auf der Konsole ausgibt. - Erstellen Sie nun eine Klasse ThreadAndRunnableDemo, in deren main-Methode eine Instanz von SimpleThread erzeugt wird. Die Klasse Thread bietet u.a. die Methoden run() und start() an. Worin unterscheiden sich diese Methoden? Wie lässt sich ein parallel laufender Thread starten? - Erstellen Sie nun die Klasse SimpleRunnable, welche das Interface Runnable implementiert. Implementieren Sie die run-Methode so, dass sie „I'm a simple Runnable.“ auf der Konsole ausgibt. Erstellen und starten Sie in der main-Methode einen Thread, der das SimpleRunnable zur Ausführung bringt. #### Schritt 1: Implementierung der Klasse `SimpleThread` Zuerst erstellen wir eine einfache Klasse namens `SimpleThread`, die von `Thread` erbt und die Methode `run()` überschreibt: ```java public class SimpleThread extends Thread { @Override public void run() { System.out.println("I'm a simple Thread."); } } ``` Diese Klasse ist eine Erweiterung der `Thread`-Klasse und überschreibt die `run()`-Methode, um eine Nachricht auf der Konsole auszugeben. #### Schritt 2: Implementierung der `ThreadAndRunnableDemo` Klasse In der Klasse `ThreadAndRunnableDemo` zeigen wir den Unterschied zwischen den Methoden `run()` und `start()`: ```java public class ThreadAndRunnableDemo { public static void main(String[] args) { SimpleThread simpleThread = new SimpleThread(); simpleThread.run(); // Aufruf der run-Methode im Hauptthread simpleThread.start(); // Startet den Thread, führt die run-Methode parallel aus } } ``` - Die Methode `run()` führt den Code im aktuellen Thread aus, in diesem Fall im Hauptthread (main thread). - Die Methode `start()` hingegen startet einen neuen Thread und ruft darin die `run()` Methode auf. Dies führt zur parallelen Ausführung des Codes. #### Schritt 3: Implementierung der Klasse `SimpleRunnable` Nun implementieren wir eine Klasse `SimpleRunnable`, die das Interface `Runnable` implementiert: ```java public class SimpleRunnable implements Runnable { @Override public void run() { System.out.println("I'm a simple Runnable."); } } ``` Diese Klasse implementiert das `Runnable` Interface und definiert, was im `run()` passieren soll, nämlich eine Nachricht ausgeben. #### Schritt 4: Verwendung von `SimpleRunnable` in `ThreadAndRunnableDemo` Jetzt erweitern wir die `ThreadAndRunnableDemo` Klasse, um eine Instanz von `SimpleRunnable` in einem neuen Thread zu starten: ```java public class ThreadAndRunnableDemo { public static void main(String[] args) { SimpleThread simpleThread = new SimpleThread(); simpleThread.start(); // Startet den Thread, führt die run-Methode parallel aus SimpleRunnable simpleRunnable = new SimpleRunnable(); Thread thread = new Thread(simpleRunnable); thread.start(); // Startet den Thread, der das Runnable ausführt } } ``` In diesem Beispiel: - Ein `SimpleThread` wird erstellt und gestartet, was zu paralleler Ausführung führt. - Ein `SimpleRunnable` wird ebenfalls erstellt, aber um diesen auszuführen, müssen wir es einem neuen `Thread` Objekt übergeben und dann diesen `Thread` starten. #### Zusammenfassung Diese Implementierungen zeigen die Unterschiede zwischen direkter Verwendung von `Thread` durch Vererbung und der Implementierung des `Runnable` Interfaces. `Runnable` bietet mehr Flexibilität und erlaubt die Trennung der Thread-Steuerung vom auszuführenden Code, was insbesondere bei Anwendung von Konzepten der Objektorientierung und bei der Zusammenarbeit mit APIs, die Threads verwenden, vorteilhaft ist. ### Aufgabe 4 – Interrupts Ein Interrupt ist die Aufforderung an einen Thread die aktuelle Ausführung zu unterbrechen und etwas anderes zu tun. Häufig ist das die Terminierung der Ausführung. Das Verhalten im Falle eines Interrupts ist jedoch vom Entwickler festzulegen. Übernehmen Sie die Klasse SimpleRunnable aus Aufgabe 3. Passen Sie die run-Methode so an, dass folgendes Verhalten erreicht wird: Solange der ausführende Thread nicht interrupted wurde, gibt er "I'm a simple Runnable." auf der Konsole aus und schläft danach für 100ms. Wenn der Thread interrupted wird, wird „SimpleRunnable interrupted!“ ausgegeben. Danach endet die Ausführung. Erstellen Sie nun die Klasse InterruptRunnableDemo und darin eine main-Methode, die das SimpleRunnable in einem neuen Thread startet und nach einer Sekunde wieder interrupted. #### Implementierung der Klassen - **Klasse SimpleRunnable**: Diese Klasse implementiert das Runnable Interface und überprüft kontinuierlich, ob der Thread unterbrochen wurde. - **Klasse InterruptRunnableDemo**: Diese Klasse erstellt und startet einen Thread von SimpleRunnable und unterbricht ihn nach einer Sekunde. Hier ist ein Beispielcode für beide Klassen: ```java public class SimpleRunnable implements Runnable { public void run() { while (!Thread.currentThread().isInterrupted()) { System.out.println("I'm a simple Runnable."); try { Thread.sleep(100); // Schläft für 100ms } catch (InterruptedException e) { System.out.println("SimpleRunnable interrupted!"); return; // Beendet die Ausführung nach der Unterbrechung } } } } public class InterruptRunnableDemo { public static void main(String[] args) { Thread thread = new Thread(new SimpleRunnable()); thread.start(); // Startet den Thread try { Thread.sleep(1000); // Hauptthread schläft für 1 Sekunde } catch (InterruptedException e) { e.printStackTrace(); } thread.interrupt(); // Unterbricht den Thread } } ``` #### Diskussion und Besonderheiten ##### Beobachtetes Verhalten: Der SimpleRunnable-Thread gibt kontinuierlich "I'm a simple Runnable." aus und schläft jeweils für 100ms. Nach einer Sekunde unterbricht der Hauptthread (main) den SimpleRunnable-Thread. Das führt dazu, dass der InterruptedException ausgelöst wird, wenn der Thread gerade schläft. Der catch-Block fängt diese Ausnahme und gibt "SimpleRunnable interrupted!" aus. Danach wird die Ausführung des Threads beendet. ##### Ist das Verhalten gewünscht? - Ja, das Verhalten ist gewünscht und korrekt implementiert. Der Thread überprüft, ob er unterbrochen wurde und reagiert entsprechend darauf. #### Besonderheiten beim Arbeiten mit InterruptedException: Wenn ein Thread in Java eine InterruptedException fängt, signalisiert dies, dass ein anderer Thread wünscht, dass dieser Thread seine Arbeit beendet. Es wird allgemein empfohlen, auf diese Unterbrechungsanforderung zu reagieren, indem man die Ausführung des betroffenen Threads sauber abbricht. Dies ist ein wichtiges Muster im Multithreading, das hilft, Threads ordnungsgemäß und sicher zu beenden. ### Aufgabe 5 – Das Warten auf Threads mit join() - Implementieren Sie eine Klasse CountUp, die von Thread erbt und in ihrer run()-Methode die Zahlen von 1 bis zu einem im Konstruktor übergebenen Wert in aufsteigender Reihenfolge ausgibt. - Implementieren Sie eine Klasse CountDown, die von Thread erbt und in ihrer run()-Methode die Zahlen von 1 bis zu einem im Konstruktor übergebenen Wert in absteigender Reihenfolge ausgibt. - Erstellen Sie die Klasse ThreadJoinDemo, in der Sie CountUp, sowie CountDown instanziieren und starten, sodass die Zahlen von 1 bis 100 in aufsteigender und in absteigender Reihenfolge ausgegeben werden. Was stellen Sie bei der Ausführung fest? Sie wollen nun sicherstellen, dass die aufsteigende Zahlenreihe komplett vor der absteigenden ausgegeben wird. Wie können Sie dieses Ziel erreichen? #### Schritt 1: Implementierung der Klasse `CountUp` Die Klasse `CountUp` erbt von `Thread` und gibt Zahlen von 1 bis zu einem übergebenen Wert in aufsteigender Reihenfolge aus. ```java public class CountUp extends Thread { private int max; public CountUp(int max) { this.max = max; } @Override public void run() { for (int i = 1; i <= max; i++) { System.out.println("Up: " + i); try { Thread.sleep(10); // kurze Pause, um die Ausgabe zu verlangsamen } catch (InterruptedException e) { e.printStackTrace(); } } } } ``` #### Schritt 2: Implementierung der Klasse `CountDown` Die Klasse `CountDown` erbt ebenfalls von `Thread` und gibt Zahlen von einem übergebenen Wert bis 1 in absteigender Reihenfolge aus. ```java public class CountDown extends Thread { private int max; public CountDown(int max) { this.max = max; } @Override public void run() { for (int i = max; i >= 1; i--) { System.out.println("Down: " + i); try { Thread.sleep(10); // kurze Pause, um die Ausgabe zu verlangsamen } catch (InterruptedException e) { e.printStackTrace(); } } } } ``` #### Schritt 3: Implementierung der Klasse `ThreadJoinDemo` In der Klasse `ThreadJoinDemo` werden Instanzen von `CountUp` und `CountDown` erzeugt und gestartet. Zuerst ohne `join()` und dann mit `join()`, um den Unterschied zu verdeutlichen. ```java public class ThreadJoinDemo { public static void main(String[] args) { CountUp countUp = new CountUp(100); CountDown countDown = new CountDown(100); countUp.start(); countDown.start(); // Ohne join() werden die Ausgaben vermischt, da beide Threads parallel laufen. // Jetzt werden wir join() verwenden, um sicherzustellen, dass countUp zuerst abgeschlossen wird. try { countUp.join(); // Wartet bis countUp beendet ist. countDown.join(); // Optional, falls man sicherstellen möchte, dass das Programm erst endet, wenn beide Threads fertig sind. } catch (InterruptedException e) { e.printStackTrace(); } } } ``` #### Beobachtungen und Anpassungen mit `join()` - **Ohne `join()`**: Die Ausgaben der Zahlenreihen werden vermischt, da beide Threads gleichzeitig laufen. - **Mit `join()` auf `countUp`**: Der Hauptthread wartet, bis `countUp` komplett abgeschlossen ist, bevor `countDown` beginnt. Dies stellt sicher, dass die Zahlen erst in aufsteigender und dann in absteigender Reihenfolge vollständig ausgegeben werden. #### Zusammenfassung Durch den Einsatz von `join()` kann die Reihenfolge, in der Threads ihre Ausgaben tätigen, effektiv gesteuert werden, was besonders nützlich ist, wenn die Ausgabe oder das Ergebnis des einen Threads vom Abschluss eines anderen Threads abhängig ist. ### Aufgabe 6 – Synchronisierung - Implementieren Sie eine generische Queue auf Basis einer Liste, die maximal 10 Elemente aufnehmen soll. Erstellen Sie hierzu eine neue Klasse `MyThreadSafeQueue`. Es sollen eine enqueue- und eine dequeue-Methode angeboten werden. Beim Versuch ein elftes Element in die Queue einzufügen, soll eine von Ihnen zu implementierende checked Exception vom Typ `QueueFullException` geworfen werden. - Damit mehrere Threads ihre Queueimplementierung parallel verwenden können, müssen gleichzeitige Zugriffe unterbunden werden. **Synchronisieren** sie die kritischen Abschnitte, sodass sichergestellt ist, dass sich immer nur ein Thread in einem solchen Abschnitt befindet. - Nutzen sie diese Implementierung als Grundlage einer neuen Klasse `MyBlockingQueue`. Modifizieren Sie die Implementierung so, dass die enqueue- und dequeue Methoden blockieren bis die vorgesehene Operation ausgeführt werden kann. Tipp: Nutzen sie `wait()` und `notifyAll()`. #### Schritt 1: MyThreadSafeQueue - **Definition der Klasse und der maximalen Größe**: Die Klasse wird als generisch definiert, und es wird eine Konstante für die maximale Größe der Warteschlange festgelegt. - **Implementation der Methoden enqueue und dequeue**: Diese Methoden müssen threadsicher sein, was durch die Verwendung von synchronized Blöcken erreicht wird. - **Implementation der QueueFullException**: Eine benutzerdefinierte checked Exception wird erstellt, um zu signalisieren, dass keine weiteren Elemente zur Warteschlange hinzugefügt werden können, weil die Kapazität erreicht ist. #### Schritt 2: MyBlockingQueue Diese Klasse erweitert MyThreadSafeQueue und modifiziert die enqueue und dequeue Methoden, um das Blockieren zu ermöglichen, falls die Operation nicht sofort durchgeführt werden kann. - **Blockierendes enqueue**: Falls die Queue voll ist, wird der Thread in einen Wartezustand versetzt, bis Platz verfügbar ist. - **Blockierendes dequeue**: Falls die Queue leer ist, wird der Thread in einen Wartezustand versetzt, bis Elemente verfügbar sind. Die Verwendung von `wait()` und `notifyAll()` ermöglicht die korrekte Synchronisation zwischen den Threads, die versuchen, Elemente hinzuzufügen oder zu entfernen. #### Implementierung in Java Hier ist eine Basisimplementierung für **MyThreadSafeQueue**: ```java import java.util.LinkedList; import java.util.List; class QueueFullException extends Exception { public QueueFullException(String message) { super(message); } } public class MyThreadSafeQueue<T> { private final List<T> queue = new LinkedList<>(); private final int capacity = 10; public synchronized void enqueue(T element) throws QueueFullException { if (queue.size() == capacity) { throw new QueueFullException("Queue is full"); } queue.add(element); } public synchronized T dequeue() { if (queue.isEmpty()) { return null; } return queue.remove(0); } } ``` Und nun **MyBlockingQueue**: ```java public class MyBlockingQueue<T> extends MyThreadSafeQueue<T> { @Override public synchronized void enqueue(T element) throws InterruptedException, QueueFullException { while (queue.size() == capacity) { wait(); } super.enqueue(element); notifyAll(); } @Override public synchronized T dequeue() throws InterruptedException { while (queue.isEmpty()) { wait(); } T element = super.dequeue(); notifyAll(); return element; } } ``` #### Zusammenfassung der Schritte - **Erstellung von MyThreadSafeQueue** mit grundlegenden threadsicheren Operationen und einer maximalen Kapazität. - **Erstellung der QueueFullException** als benutzerdefinierte Exception. - **Erweiterung zu MyBlockingQueue**, die Operationen blockiert, wenn Bedingungen (voll/leer) nicht erfüllt sind. - **Verwendung von wait() und notifyAll()** zur effektiven Synchronisation und zum Blockieren der Threads unter bestimmten Bedingungen. ### Aufgabe 7 – Wait & notify - **a**: - Das Producer-Consumer-Prinzip sollen Sie in dieser Aufgabe am Beispiel simulierter Münzwürfe und der Auswertung der relativen Häufigkeiten praktisch umsetzen. - Erstellen Sie die Klasse ProducerConsumerDemo und versehen Sie sie mit einer Instanzvariable vom Typ BlockingQueue. Diese Queue soll zur Kommunikation zwischen Threads verwendet werden. Sie kann anderen Threads über deren Konstruktor oder setterMethoden bekannt gemacht werden. - Implementieren Sie eine Klasse Producer, die von Thread erbt und in ihrer run()-Methode zufällig eines der Stringliterale „Kopf“ oder „Zahl“ mit einer Wahrscheinlichkeit von 50% erzeugt und in die Queue einreiht. Dieses Verhalten soll ausgeführt werden, bis der Thread interrupted wird. - Implementieren Sie dann die Klasse Consumer, die die Ergebnisse der Münzwurfsimulation aus der Queue ausliest und die relative Häufigkeit des Auftretens von „Kopf“ auf der Konsole ausgibt. Auch dieses Verhalten soll ausgeführt werden, bis der Thread interrupted wird. - Fügen Sie eine main()-Methode zur Klasse ProducerConsumerDemo hinzu, in der Producer und Consumer instanziiert und gestartet werden. - **b**: - Lassen sie den Producer nach Hinzufügen von „Kopf“ oder „Zahl“ zur Queue für 10ms schlafen. - Passen Sie die main-Methode in der Klasse ProducerConsumerDemo so an, dass Consumer und Producer nach dem Starten 10 Sekunden ausgeführt werden, bevor die Ausführung durch ein Interrupt gestoppt wird. - Fügen Sie Zählervariablen in die Klassen Producer und Consumer ein, um die Anzahl an Durchläufen der run()-Methoden zu überprüfen. Geben Sie diese Werte beim Beenden der Threads aus. - Was stellen Sie im Bezug auf die Anzahl der Ausführungen fest? Stehen die Werte in einem angemessenen Verhältnis? Wie kann die Effizienz durch das Nutzen von wait() und notifyAll() verbessert werden? - Passen Sie Consumer und Producer auf Basis Ihrer Erkenntnisse an und überprüfen Sie anhand der Zählervariablen, ob Sie eine Effizienzsteigerung erzielen konnten. #### Teil a) Implementierung der Producer-Consumer-Logik mit einer BlockingQueue Hier implementieren wir die Klassen `Producer` und `Consumer`, die über eine `BlockingQueue` kommunizieren. ##### Schritt 1: Klasse `ProducerConsumerDemo` Diese Klasse initialisiert und startet die Threads. ```java import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; public class ProducerConsumerDemo { public static void main(String[] args) throws InterruptedException { BlockingQueue<String> queue = new LinkedBlockingQueue<>(); Producer producer = new Producer(queue); Consumer consumer = new Consumer(queue); producer.start(); consumer.start(); // Lassen Sie Producer und Consumer für 10 Sekunden laufen Thread.sleep(10000); producer.interrupt(); consumer.interrupt(); producer.join(); consumer.join(); System.out.println("Producer Durchläufe: " + producer.getCounter()); System.out.println("Consumer Durchläufe: " + consumer.getCounter()); } } ``` ##### Schritt 2: Klasse `Producer` Der Producer erzeugt zufällig "Kopf" oder "Zahl" und fügt diese zur Queue hinzu. ```java import java.util.concurrent.BlockingQueue; import java.util.Random; public class Producer extends Thread { private final BlockingQueue<String> queue; private int counter = 0; public Producer(BlockingQueue<String> queue) { this.queue = queue; } @Override public void run() { Random random = new Random(); String[] options = {"Kopf", "Zahl"}; try { while (!Thread.currentThread().isInterrupted()) { String item = options[random.nextInt(2)]; queue.put(item); Thread.sleep(10); // Schlafen für 10ms counter++; } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } public int getCounter() { return counter; } } ``` ##### Schritt 3: Klasse `Consumer` Der Consumer liest die Ergebnisse aus der Queue und berechnet die relative Häufigkeit von "Kopf". ```java import java.util.concurrent.BlockingQueue; public class Consumer extends Thread { private final BlockingQueue<String> queue; private int headsCount = 0; private int totalCount = 0; public Consumer(BlockingQueue<String> queue) { this.queue = queue; } @Override public void run() { try { while (!Thread.currentThread().isInterrupted()) { String item = queue.take(); if (item.equals("Kopf")) { headsCount++; } totalCount++; System.out.println("Relative Häufigkeit von 'Kopf': " + (double) headsCount / totalCount); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } public int getCounter() { return totalCount; } } ``` #### Teil b) Analyse und Verbesserung Die initiale Implementierung zeigt, dass Producer und Consumer etwa gleich oft laufen. Der Producer schläft nach jedem Einfügen für 10 ms, was eine gewisse Begrenzung der Geschwindigkeit darstellt, während der Consumer ständig läuft und auf neue Elemente wartet. Die Effizienz kann verbessert werden, indem man wait() und notifyAll() verwendet, um unnötige CPU-Zyklen zu vermeiden, indem der Consumer schlafen geht, wenn die Queue leer ist, und nur geweckt wird, wenn neue Elemente verfügbar sind. ##### Verbesserte Implementierung unter Verwendung von wait() und notifyAll() Diese Änderungen müssten im Rahmen des Designs der BlockingQueue selbst umgesetzt werden oder durch die Verwendung einer eigenen Implementierung der Queue, die solche Methoden unterstützt. Da `LinkedBlockingQueue` bereits effizient mit Locks arbeitet und das busy-waiting Problem minimiert, sind weitere Verbesserungen in Bezug auf wait() und notify() in dieser spezifischen Implementierung möglicherweise nicht notwendig. Jedoch, falls eine eigene Queue-Implementierung verwendet würde, könnte der Consumer nach dem Prüfen einer leeren Queue in einen Wartezustand versetzt werden, bis der Producer neue Daten eingefügt hat und ihn aufweckt. ### Aufgabe 8 – Files & Streams - Sie sind Kleinunternehmer und Ihre Kundenverwaltungssoftware erlaubt den Export Ihrer Kundendatei als Textdatei (.txt). Sie möchten die Daten in einer Exceltabelle darstellen ohne sie manuell eintragen zu müssen. Dafür bietet sich das CSV-Format (comma-separated values) an. Erstellen sie ein Java-Programm, das die im ILIAS-System bereitgestellte Datei input.txt nutzt um ein CSV-File zu erstellen, das von Excel als Tabelle dargestellt wird. #### Vorgehen: - Um die Daten aus einer Textdatei (.txt) in eine CSV-Datei (.csv) zu konvertieren, die dann leicht in Excel importiert werden kann, können Sie ein einfaches Java-Programm schreiben. Der folgende Ansatz nimmt an, dass die Daten in der input.txt bereits in einer strukturierten Form vorliegen, z.B. mit einem bestimmten Trennzeichen zwischen den Datenfeldern. #### Schritte zur Erstellung des Programms: - **Einlesen der Textdatei**: Verwenden Sie BufferedReader zum Einlesen der Textdatei. - **Schreiben in eine CSV-Datei**: Nutzen Sie PrintWriter oder FileWriter zum Schreiben der Daten in eine CSV-Datei. - **Datenverarbeitung**: Lesen Sie jede Zeile der Eingabedatei, transformieren Sie diese falls nötig und schreiben Sie sie ins CSV-Format. - **Fehlerbehandlung**: Implementieren Sie geeignete Fehlerbehandlungsmechanismen. ```java import java.io.*; public class TxtToCsvConverter { public static void convertTxtToCsv(String inputFile, String outputFile) { try (BufferedReader reader = new BufferedReader(new FileReader(inputFile)); PrintWriter writer = new PrintWriter(new FileWriter(outputFile))) { String line; while ((line = reader.readLine()) != null) { String csvLine = line.replace(" ", ","); // Ersetzt Leerzeichen durch Kommas writer.println(csvLine); } System.out.println("Conversion completed successfully!"); } catch (IOException e) { System.out.println("An error occurred during file processing: " + e.getMessage()); } } public static void main(String[] args) { String inputFile = "input.txt"; // Pfad zur Eingabedatei String outputFile = "output.csv"; // Pfad zur Ausgabedatei convertTxtToCsv(inputFile, outputFile); } } ``` ##### Erläuterung des Codes - **BufferedReader & FileReader**: Diese Klassen werden verwendet, um die Eingabedatei zeilenweise zu lesen. - **PrintWriter & FileWriter** : Diese Klassen helfen beim Schreiben in die Ausgabedatei. - **String-Ersetzung: line.replace(" ", ",")** ersetzt jedes Leerzeichen in der gelesenen Zeile durch ein Komma. Wenn das Trennzeichen in Ihrer input.txt etwas anderes ist (z.B. ein Tabulator oder ein anderes spezielles Zeichen), sollten Sie das entsprechend anpassen. ### Aufgabe 9 – Zeitserver - Implementieren Sie mit Hilfe von Sockets eine Serveranwendung, die das aktuelle Datum, sowie die Uhrzeit an einen Client zurückliefert. Das Format der Antwort soll dem im folgenden Beispiel verwendeten entsprechen: 26.01.2015, 13:42:32 Implementieren sie zudem einen Client, der die aktuelle Zeit vom Server abfragt und auf der Konsole ausgibt. #### Vorgehen Für diese Aufgabe werden zwei Anwendungen benötigt: ein Server, der das aktuelle Datum und die Uhrzeit zurückliefert, und ein Client, der diese Daten anfordert und anzeigt. Java bietet mächtige Möglichkeiten für Netzwerkverbindungen durch Sockets, die hier genutzt werden können. #### Schritt 1: Der Zeitserver Der Server wird eine ServerSocket-Instanz verwenden, um auf eingehende Verbindungsanfragen zu hören. Bei einer Anfrage sendet der Server das aktuelle Datum und die Uhrzeit im gewünschten Format an den Client. #### Schritt 2: Der Client Der Client wird eine Socket-Verbindung zum Server herstellen, das Datum und die Uhrzeit empfangen und dann auf der Konsole ausgeben. #### **Beispielcode für den Server** ```java import java.io.IOException; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.text.SimpleDateFormat; import java.util.Date; public class TimeServer { public static void main(String[] args) { int port = 5000; // Port, auf dem der Server lauscht try (ServerSocket serverSocket = new ServerSocket(port)) { System.out.println("Server started. Listening on port " + port); while (true) { try (Socket clientSocket = serverSocket.accept(); PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true)) { String dateTime = new SimpleDateFormat("dd.MM.yyyy, HH:mm:ss").format(new Date()); out.println(dateTime); } catch (IOException e) { System.out.println("Error handling client: " + e.getMessage()); } } } catch (IOException e) { System.out.println("Could not listen on port " + port + ": " + e.getMessage()); } } } ``` #### **Beispielcode für den Client** ```java import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.Socket; public class TimeClient { public static void main(String[] args) { String hostname = "localhost"; // Server-Hostname oder IP int port = 5000; // Port, auf dem der Server läuft try (Socket socket = new Socket(hostname, port); BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) { String serverTime = in.readLine(); System.out.println("Current server time: " + serverTime); } catch (IOException e) { System.out.println("Cannot connect to server: " + e.getMessage()); } } } ``` ##### Erläuterung des Codes **Server:** - Der Server verwendet ServerSocket zum Lauschen auf einem bestimmten Port. - Für jede eingehende Verbindung wird ein Socket für die Clientverbindung erstellt. Ein PrintWriter wird genutzt, um das formatierte Datum und die Uhrzeit über den Socket an den Client zu senden. **Client:** - Der Client verbindet sich mit dem Server über einen Socket. - Ein BufferedReader liest die Daten, die vom Server über den Socket gesendet werden. - Das erhaltene Datum und die Uhrzeit werden auf der Konsole ausgegeben. ## Aufgaben zu Algorithmen und Datenstrukturen ### Aufgabe 1 – SelectionSort - Implementieren Sie SelectionSort Algorithmus für ein Array. - Erstellen Sie hierzu eine Klasse SortAlgorithms, die eine statische Methode selectionSort anbietet. Diese Methode soll ein Array vom Typ int[] sortieren, das als Parameter übergeben wird. Ein Rückgabetyp ist nicht erforderlich, da die Methode auf einem Referenzdatentyp arbeitet. #### Schritt 1: Implementierung der Klasse `SortAlgorithms` ```java public class SortAlgorithms { public static void selectionSort(int[] array) { if (array == null || array.length <= 1) { return; // Keine Sortierung nötig, wenn das Array null oder sehr klein ist. } int n = array.length; // Durchlaufe das Array, um das Minimum zu finden und an den Anfang zu setzen for (int i = 0; i < n - 1; i++) { // Setze das erste unsortierte Element als das minimale int minIndex = i; // Suche das kleinste Element in dem restlichen Array for (int j = i + 1; j < n; j++) { if (array[j] < array[minIndex]) { minIndex = j; // Finde das neue Minimum } } // Tausche das gefundene Minimum mit dem ersten unsortierten Element if (minIndex != i) { int temp = array[i]; array[i] = array[minIndex]; array[minIndex] = temp; } } } // Hauptmethode zum Testen der Sortierfunktion public static void main(String[] args) { int[] testArray = {64, 34, 25, 12, 22, 11, 90}; System.out.println("Ursprüngliches Array:"); for (int value : testArray) { System.out.print(value + " "); } selectionSort(testArray); System.out.println("\nSortiertes Array:"); for (int value : testArray) { System.out.print(value + " "); } } } ``` #### Funktionsweise des SelectionSort-Algorithmus 1. **Initialisierung und Validierung:** Zuerst wird überprüft, ob das Array leer oder bereits sortiert ist (d.h., es enthält ein oder kein Element). 2. **Finden des Minimums:** Für jede Position im Array (außer für die letzte) findet der Algorithmus das kleinste Element im unsortierten Teil des Arrays. 3. **Tauschen:** Das gefundene kleinste Element wird mit dem Element an der aktuellen Position getauscht. 4. **Wiederholung:** Dieser Vorgang wird wiederholt, bis das gesamte Array sortiert ist. #### Test des Algorithmus Die `main` Methode in der `SortAlgorithms` Klasse demonstriert, wie der Algorithmus ein Beispielarray sortiert. Sie gibt das Array vor und nach der Sortierung aus. Dies hilft, die korrekte Funktionalität von `selectionSort` zu verifizieren. #### Effizienz SelectionSort ist ein einfacher, aber ineffizienter Sortieralgorithmus mit einer Zeitkomplexität von O(n^2), was ihn für große Datenmengen ungeeignet macht. Er ist jedoch einfach zu implementieren und zu verstehen und funktioniert gut für kleinere Datenmengen. ### Aufgabe 2 – InsertionSort - Implementieren Sie Insertion Sort Algorithmus in der Klasse SortAlgorithms. - Erstellen Sie hierzu eine statische Methode insertionSort. Diese Methode soll ein Array vom Typ int[] sortieren, das als Parameter übergeben wird. Ein Rückgabetyp ist nicht erforderlich, da die Methode auf einem Referenzdatentyp arbeitet. - Hinweis: Es genügt wenn positive Zahlen sortiert werden können. #### Schritt 2: Ergänzung der Klasse `SortAlgorithms` um die Methode `insertionSort` ```java public class SortAlgorithms { public static void selectionSort(int[] array) { // Implementation aus der vorherigen Antwort } public static void insertionSort(int[] array) { if (array == null || array.length <= 1) { return; // Keine Sortierung nötig, wenn das Array leer oder sehr klein ist. } int n = array.length; for (int i = 1; i < n; i++) { int key = array[i]; int j = i - 1; // Verschiebe Elemente von array[0..i-1], die größer als der key sind, eine Position nach rechts while (j >= 0 && array[j] > key) { array[j + 1] = array[j]; j = j - 1; } array[j + 1] = key; } } // Hauptmethode zum Testen der Sortierfunktionen public static void main(String[] args) { int[] selectionSortArray = {64, 34, 25, 12, 22, 11, 90}; System.out.println("Ursprüngliches Array für SelectionSort:"); for (int value : selectionSortArray) { System.out.print(value + " "); } selectionSort(selectionSortArray); System.out.println("\nSortiertes Array nach SelectionSort:"); for (int value : selectionSortArray) { System.out.print(value + " "); } System.out.println("\n\nUrsprüngliches Array für InsertionSort:"); int[] insertionSortArray = {22, 11, 99, 88, 9, 7, 42}; for (int value : insertionSortArray) { System.out.print(value + " "); } insertionSort(insertionSortArray); System.out.println("\nSortiertes Array nach InsertionSort:"); for (int value : insertionSortArray) { System.out.print(value + " "); } } } ``` #### Funktionsweise des InsertionSort-Algorithmus 1. **Iteration:** Beginnend beim zweiten Element des Arrays (`i = 1`), wird jedes Element als `key` betrachtet. 2. **Einfügen des Keys:** Der Algorithmus vergleicht den `key` mit den vorherigen Elementen. Elemente, die größer als der `key` sind, werden nach rechts verschoben, um Platz für den `key` zu machen. 3. **Einsetzen des Keys:** Sobald ein Element gefunden wird, das kleiner oder gleich dem `key` ist, wird der `key` an dieser Position eingefügt. #### Effizienz InsertionSort ist effektiver als SelectionSort, besonders wenn das Array teilweise sortiert ist. Seine durchschnittliche und schlechteste Zeitkomplexität ist jedoch ebenfalls O(n^2), was ihn für sehr große Datenmengen ineffizient macht. Trotzdem ist InsertionSort aufgrund seiner adaptiven, stabilen und in-place Eigenschaften für kleinere Datenmengen oder fast sortierte Arrays eine gute Wahl. ### Aufgabe 3 – Datenstruktur DoublyLinked List - Programmieren Sie eine doppelt verkettete Liste, die Objekte des Typs String aufnimmt. - Orientieren Sie sich dazu am Aufbau und den Operationen der einfach verketteten Liste, die in der Vorlesung vorgestellt wurde. - Welche Vor- und Nachteile bringt die doppelte Verkettung mit sich? #### Implementierung einer doppelt verketteten Liste Hier ist eine einfache Implementierung einer doppelt verketteten Liste (`DoublyLinkedList`), die `String`-Objekte aufnimmt. Diese Implementierung umfasst Grundoperationen wie Einfügen am Anfang, am Ende und das Löschen von Elementen. ##### Schritt 1: Definition der Knotenklasse `Node` ```java class Node { String data; Node prev; Node next; Node(String data) { this.data = data; this.prev = null; this.next = null; } } ``` ##### Schritt 2: Implementierung der `DoublyLinkedList` Klasse ```java public class DoublyLinkedList { private Node head; private Node tail; public DoublyLinkedList() { this.head = null; this.tail = null; } // Methode zum Einfügen am Anfang public void insertAtFront(String data) { Node newNode = new Node(data); if (head == null) { tail = newNode; } else { head.prev = newNode; newNode.next = head; } head = newNode; } // Methode zum Einfügen am Ende public void insertAtEnd(String data) { Node newNode = new Node(data); if (tail == null) { head = newNode; } else { tail.next = newNode; newNode.prev = tail; } tail = newNode; } // Methode zum Löschen eines Knotens public void deleteNode(String key) { Node temp = head; while (temp != null && !temp.data.equals(key)) { temp = temp.next; } if (temp == null) return; if (temp.prev != null) { temp.prev.next = temp.next; } else { head = temp.next; } if (temp.next != null) { temp.next.prev = temp.prev; } else { tail = temp.prev; } } // Methode zur Ausgabe der Liste von vorne nach hinten public void printList() { Node current = head; while (current != null) { System.out.print(current.data + " <-> "); current = current.next; } System.out.println("null"); } } ``` #### Testen der `DoublyLinkedList` ```java public class Main { public static void main(String[] args) { DoublyLinkedList dll = new DoublyLinkedList(); dll.insertAtFront("Hello"); dll.insertAtEnd("World"); dll.insertAtFront("Start"); dll.insertAtEnd("End"); dll.printList(); dll.deleteNode("World"); dll.printList(); } } ``` #### Vorteile einer doppelt verketteten Liste 1. **Bidirektionale Navigation:** Durch die Verknüpfung in beide Richtungen (vorwärts und rückwärts) ist die Navigation durch die Liste effizienter, insbesondere wenn Elemente von beiden Enden der Liste häufig zugegriffen werden müssen. 2. **Einfacheres Einfügen und Löschen:** Im Vergleich zu einfach verketteten Listen kann das Einfügen und Löschen von Knoten an beliebiger Stelle in der Liste leichter durchgeführt werden, da man nicht notwendigerweise den vorherigen Knoten durchlaufen muss, um Verbindungen neu zu knüpfen. #### Nachteile einer doppelt verketteten Liste 1. **Höherer Speicherverbrauch:** Jeder Knoten benötigt zusätzlichen Speicherplatz für den vorherigen Zeiger, was insbesondere bei großen Datenmengen zu einem höheren Speicherbedarf führt. 2. **Komplexität:** Die Implementierung von Operationen in einer doppelt verketteten Liste ist komplizierter, insbesondere beim korrekten Umgang mit den `prev` und `next` Zeigern, was zu Fehlern führen kann. ### Aufgabe 4 – Stack mit Array - Ein Stack, oder auch Stapelspeicher, ist eine Datenstruktur, die folgende Operationen vorsieht: • push: Legt ein Element auf den Stapel • pop: Entfernt das oberste Element vom Stapel und liefert es zurück. • top: Gibt das oberste Element des Stapels zurück - Implementieren Sie einen Stack für den Datentyp Object. Verwenden Sie ein Array als interne Datenstruktur. Der Stack muss nicht mehr als 10 Elemente aufnehmen können. #### Schritt 1: Implementierung der Klasse `Stack` ```java public class Stack { private Object[] elements; private int top; private static final int MAX_SIZE = 10; public Stack() { elements = new Object[MAX_SIZE]; top = -1; } // Fügt ein Element auf den Stapel hinzu public void push(Object element) { if (top >= MAX_SIZE - 1) { throw new IllegalStateException("Stack is full"); } elements[++top] = element; } // Entfernt das oberste Element vom Stapel und gibt es zurück public Object pop() { if (top == -1) { throw new IllegalStateException("Stack is empty"); } return elements[top--]; } // Gibt das oberste Element des Stapels zurück, ohne es zu entfernen public Object top() { if (top == -1) { throw new IllegalStateException("Stack is empty"); } return elements[top]; } } ``` #### Vorteile dieser Implementierung 1. **Einfachheit:** Die Implementierung ist einfach zu verstehen und zu verwenden. 2. **Effizienz:** Operationen wie `push`, `pop` und `top` sind in konstanter Zeit \(O(1)\) möglich. #### Nachteile 1. **Feste Größe:** Der Stack ist auf 10 Elemente beschränkt, was seine Verwendung in Anwendungen mit möglicherweise mehr Elementen einschränkt. 2. **Mangel an Typsicherheit:** Da `Object` verwendet wird, gibt es keine Typsicherheit bei der Verwendung des Stacks. Es könnte nützlich sein, eine generische Version zu implementieren. ### Aufgabe 5 – Stack mit DoublyLinkedList - Implementieren Sie den Stack aus Aufgabe 4 auf Basis der doppelt verketteten Liste aus Aufgabe 3. - Wäre auch eine SinglyLinkedList für die Implementierung geeignet? #### Schritt 1: Anpassung der Node und DoublyLinkedList Klasse Hier nutzen wir die `Node`- und `DoublyLinkedList`-Klassen aus der früheren Implementierung und erweitern sie um Stack-Operationen. ```java class Node { String data; Node prev; Node next; Node(String data) { this.data = data; this.prev = null; this.next = null; } } class DoublyLinkedList { private Node head; private Node tail; public void push(String data) { Node newNode = new Node(data); if (head == null) { head = tail = newNode; } else { newNode.next = head; head.prev = newNode; head = newNode; } } public String pop() { if (head == null) throw new IllegalStateException("Stack is empty"); String data = head.data; head = head.next; if (head != null) { head.prev = null; } else { tail = null; } return data; } public String top() { if (head == null) throw new IllegalStateException("Stack is empty"); return head.data; } } ``` #### Schritt 2: Implementierung der Stack-Klasse Die `Stack` Klasse verwendet nun eine Instanz von `DoublyLinkedList` für die Stack-Operationen. ```java public class Stack { private DoublyLinkedList list; public Stack() { list = new DoublyLinkedList(); } public void push(String data) { list.push(data); } public String pop() { return list.pop(); } public String top() { return list.top(); } } ``` #### Wäre auch eine SinglyLinkedList für die Implementierung geeignet? Ja, eine SinglyLinkedList könnte ebenfalls für die Implementierung eines Stacks genutzt werden. Bei einem Stack werden Elemente nur am `head` hinzugefügt und entfernt, was mit den Operationen einer SinglyLinkedList vereinbar ist. Eine doppelt verkettete Liste bietet allerdings den Vorteil, dass beim Entfernen des obersten Elements nicht das ganze List durchlaufen werden muss, um den `prev`-Zeiger des neuen `head` zu aktualisieren. Dies ist bei einer SinglyLinkedList nicht nötig, was die Implementierung vereinfachen kann. ### Aufgabe 6 – Prüfungsverwaltung mit Sets - Sie sind für die Prüfungsorganisation in einem Fach mit verpflichtender Vorleistung zuständig. Um den manuellen Aufwand zu reduzieren, wollen sie ein kleines Java Programm entwickeln, das die Prüfungsteilnehmer verwaltet. - Folgende Informationen liegen Ihnen vor: - eine Liste der Anmeldungen im QISPOS-System - eine Liste der Anmeldungen mit einem physischen Prüfungsschein - eine Liste der Teilnehmer, die die verpflichtende Vorleistung bestanden haben. - Mit welchen Operationen können nun die folgenden Informationen bestimmt werden? - Liste der angemeldeten Studenten - Liste der für die Prüfung zugelassenen Studenten - Warum ist der Datentyp Set, der eine Menge implementiert, dafür besonders geeignet? - Erstellen sie eine Klasse Pruefungsverwaltung, die in der main-Methode zuerst die gegebenen Informationen einliest und dann die gesuchten Informationen daraus berechnet.Teilnehmer sollen durch ihren Namen identifiziert werden. Mit welcher Methode der Klasse Set kann die Frage ob ein bestimmter Teilnehmer zur Prüfung zugelassen ist beantwortet werden? - Wahrscheinlich zeigt Eclipse Ihnen Warnungen im Kontext der Verwendung des Datentyps Set an. Wie können Sie sich diese Warnungen erklären? #### Warum der Datentyp Set für die Prüfungsverwaltung geeignet ist Der `Set` Datentyp in Java ist ideal für die Verwaltung von Prüfungsteilnehmern, da er eine Menge von einzigartigen Elementen darstellt. Das bedeutet: 1. **Keine Duplikate:** Jeder Student wird nur einmal aufgeführt, auch wenn er/sie mehrfach in den Eingabelisten erscheinen könnte. 2. **Effiziente Operationen:** Operationen wie die Vereinigung, Schnittmenge und Differenz (um zugelassene Studenten zu identifizieren) sind effizient zu handhaben. #### Vorteilhafte Set-Operationen - **Vereinigung (`addAll`)**: Kann genutzt werden, um eine Gesamtliste aller angemeldeten Studenten zu erstellen. - **Schnittmenge (`retainAll`)**: Kann genutzt werden, um die Liste der für die Prüfung zugelassenen Studenten zu berechnen, basierend auf den Studenten, die sowohl angemeldet sind als auch die Vorleistung bestanden haben. - **Test auf Mitgliedschaft (`contains`)**: Ermöglicht die Überprüfung, ob ein bestimmter Teilnehmer zur Prüfung zugelassen ist. #### Implementierung der Klasse `Pruefungsverwaltung` ```java import java.util.HashSet; import java.util.Set; public class Pruefungsverwaltung { public static void main(String[] args) { // Simulierte Eingabelisten Set<String> qisposAnmeldungen = new HashSet<>(); Set<String> physischeAnmeldungen = new HashSet<>(); Set<String> bestandeneVorleistungen = new HashSet<>(); // Hinzufügen von simulierten Daten qisposAnmeldungen.add("Alice"); qisposAnmeldungen.add("Bob"); qisposAnmeldungen.add("Charlie"); physischeAnmeldungen.add("Alice"); physischeAnmeldungen.add("Charlie"); bestandeneVorleistungen.add("Alice"); bestandeneVorleistungen.add("Bob"); // Berechnen der angemeldeten Studenten (Vereinigung) Set<String> angemeldeteStudenten = new HashSet<>(qisposAnmeldungen); angemeldeteStudenten.addAll(physischeAnmeldungen); // Berechnen der für die Prüfung zugelassenen Studenten (Schnittmenge) Set<String> zugelasseneStudenten = new HashSet<>(physischeAnmeldungen); zugelasseneStudenten.retainAll(bestandeneVorleistungen); System.out.println("Angemeldete Studenten: " + angemeldeteStudenten); System.out.println("Zugelassene Studenten: " + zugelasseneStudenten); // Überprüfen, ob ein bestimmter Teilnehmer zur Prüfung zugelassen ist String testStudent = "Alice"; System.out.println("Ist " + testStudent + " zugelassen? " + zugelasseneStudenten.contains(testStudent)); } } ``` #### Umgang mit Warnungen in Eclipse Wenn Eclipse Warnungen im Zusammenhang mit der Verwendung des Datentyps `Set` anzeigt, handelt es sich wahrscheinlich um Warnungen zur generischen Typsicherheit. Diese Warnungen treten auf, weil der generische Typ (`Set<String>`) zur Kompilierzeit Typsicherheit bietet, die zur Laufzeit aufgrund von Typ-Löschung (Type Erasure) nicht garantiert werden kann. Diese Warnungen können behoben werden, indem Sie den Code sauber halten und sicherstellen, dass Sie konsistent denselben generischen Typ verwenden. Nutzen Sie auch das `@SuppressWarnings("unchecked")` Annotation sorgsam, wenn Sie sicher sind, dass Ihr Code typsicher ist. ### Aufgabe 7 – Generische Datentypen: ArrayList - Eine Implementierung einer generischen Liste, d.h. des List-Interfaces, stellt die Klasse ArrayList dar. - Erstellen Sie eine Klasse ArrayListDemo mit einer main-Methode. Deklarieren und instanziieren Sie in dieser eine ArrayList, die Objekte vom Typ String aufnehmen kann. - Fügen Sie die Namen der Tutorienteilnehmer ein. Geben sie die Teilnehmer auf der Konsole aus. Die Namen der Teilnehmer sollen durch Semikolons getrennt werden. Nutzen sie hierfür die vereinfachte for-Schleife. #### Implementierung der Klasse `ArrayListDemo` ```java import java.util.ArrayList; public class ArrayListDemo { public static void main(String[] args) { // Deklaration und Instanziierung einer ArrayList, die String Objekte aufnehmen kann ArrayList<String> teilnehmerListe = new ArrayList<>(); // Hinzufügen von Namen der Tutorienteilnehmer teilnehmerListe.add("Alice"); teilnehmerListe.add("Bob"); teilnehmerListe.add("Charlie"); teilnehmerListe.add("Diana"); // Ausgabe der Teilnehmer auf der Konsole, getrennt durch Semikolons boolean isFirst = true; for (String name : teilnehmerListe) { if (!isFirst) { System.out.print("; "); } System.out.print(name); isFirst = false; } } } ``` #### Erklärung des Codes 1. **Deklaration und Instanzierung**: Eine `ArrayList` von `String` wird erstellt. `ArrayList<String>` bedeutet, dass diese Liste speziell für die Speicherung von `String`-Objekten ausgelegt ist. Dies sorgt für Typsicherheit, da Sie nur Strings hinzufügen oder aus der Liste erhalten können, ohne dass Casts nötig sind. 2. **Hinzufügen von Elementen**: Namen werden zur Liste hinzugefügt mit der Methode `add()`. Diese Methode fügt die Elemente am Ende der Liste hinzu. 3. **Ausgabe der Liste**: In der `main` Methode wird eine vereinfachte for-Schleife verwendet, um über die Liste zu iterieren. Innerhalb der Schleife prüfe ich mit der Boolean-Variable `isFirst`, ob es sich um das erste Element handelt, um vor den folgenden Namen ein Semikolon zu setzen, außer vor dem ersten Namen. #### Vorteile der Verwendung von `ArrayList` in Java - **Flexibilität**: `ArrayLists` sind dynamisch, was bedeutet, dass sie ihre Größe automatisch anpassen, wenn Elemente hinzugefügt oder entfernt werden. - **Zugriff**: Der Zugriff auf ein Element an einer bestimmten Position ist sehr effizient (Zugriff in konstanter Zeit). - **Generische Typen**: Generische Typen bieten Typsicherheit und vermeiden das Casting, das bei Verwendung von nicht generischen Kollektionen wie `ArrayList` notwendig wäre. ### Aufgabe 8 – Typisierte Prüfungsverwaltung - Typisieren Sie die von Ihnen in Aufgabe 6 entwickelte Prüfungsverwaltung mit Hilfe generischer Sets. - Erstellen Sie hierzu die Klasse PruefungsverwaltungTypisiert. Die Einträge sollen vom Typ String sein. #### Implementierung der Klasse `PruefungsverwaltungTypisiert` ```java import java.util.HashSet; import java.util.Set; public class PruefungsverwaltungTypisiert { public static void main(String[] args) { // Erstellen von Sets mit Typisierung für String Set<String> qisposAnmeldungen = new HashSet<>(); Set<String> physischeAnmeldungen = new HashSet<>(); Set<String> bestandeneVorleistungen = new HashSet<>(); // Simulierte Daten hinzufügen qisposAnmeldungen.add("Alice"); qisposAnmeldungen.add("Bob"); qisposAnmeldungen.add("Charlie"); physischeAnmeldungen.add("Alice"); physischeAnmeldungen.add("Charlie"); bestandeneVorleistungen.add("Alice"); bestandeneVorleistungen.add("Bob"); // Berechnen der angemeldeten Studenten (Vereinigung der Sets) Set<String> angemeldeteStudenten = new HashSet<>(qisposAnmeldungen); angemeldeteStudenten.addAll(physischeAnmeldungen); // Berechnen der für die Prüfung zugelassenen Studenten (Schnittmenge) Set<String> zugelasseneStudenten = new HashSet<>(physischeAnmeldungen); zugelasseneStudenten.retainAll(bestandeneVorleistungen); System.out.println("Angemeldete Studenten: " + angemeldeteStudenten); System.out.println("Zugelassene Studenten: " + zugelasseneStudenten); // Überprüfen, ob ein bestimmter Teilnehmer zur Prüfung zugelassen ist String testStudent = "Alice"; System.out.println("Ist " + testStudent + " zugelassen? " + zugelasseneStudenten.contains(testStudent)); } } ``` #### Erklärung - **Generische Sets**: Die Sets `qisposAnmeldungen`, `physischeAnmeldungen`, und `bestandeneVorleistungen` sind als `Set<String>` typisiert. Dies stellt sicher, dass nur `String` Objekte hinzugefügt werden können, was Typfehler zur Kompilierzeit verhindert. - **Operationen**: - **Vereinigung (`addAll`)**: Die Methode `addAll()` wird verwendet, um alle Anmeldungen (sowohl QISPOS als auch physisch) in ein Set zusammenzuführen. - **Schnittmenge (`retainAll`)**: Die Methode `retainAll()` wird verwendet, um nur diejenigen Studenten in `zugelasseneStudenten` zu behalten, die sowohl angemeldet sind als auch die Vorleistung bestanden haben. - **Mitgliedschaftsprüfung (`contains`)**: Die Methode `contains()` wird genutzt, um zu überprüfen, ob ein bestimmter Student zur Prüfung zugelassen ist. #### Vorteile der Verwendung von generischen Typen - **Typsicherheit**: Das Programm ist sicherer, da es zur Kompilierzeit überprüft, ob nur Strings in die Sets eingefügt werden. - **Vermeidung von Laufzeitfehlern**: Fehler durch falsche Datentypen werden zur Kompilierzeit erkannt, wodurch Laufzeitfehler vermieden werden. - **Wartbarkeit und Lesbarkeit**: Der Code ist einfacher zu verstehen und zu warten, weil klar ist, welche Art von Daten die Sets enthalten. ### Aufgabe 9 – Iterator - Iterators bieten eine weitere Möglichkeit Collections einfach zu traversieren. Jede Instanz einer Klasse, die das Iterable Interface implementiert kann ein Iterator-Objekt zurückliefern. Alle Kindklassen von Collection implementieren Iterable. Realisieren Sie die Ausgabe aus Aufgabe 6 mit Hilfe eines Iterators in der Klasse ListIteratorDemo. ```java import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class ListIteratorDemo { public static void main(String[] args) { // Create a list of integers List<Integer> numbers = new ArrayList<>(); numbers.add(1); numbers.add(2); numbers.add(3); numbers.add(4); numbers.add(5); // Obtain the iterator from the list Iterator<Integer> iterator = numbers.iterator(); // Use the iterator to traverse the list while (iterator.hasNext()) { Integer number = iterator.next(); System.out.println(number); } } } ``` #### Explanation: - **Import Statements**: The code imports ArrayList, Iterator, and List classes from the Java Util package. - **List Initialization**: An ArrayList of integers is created and initialized with some values. - **Obtaining an Iterator**: The iterator is obtained from the list using the `iterator()` method. This method is available because ArrayList implements the Iterable interface. - **Using the Iterator**: The `hasNext()` method of the iterator checks if there are more elements in the list. If `hasNext()` returns true, `next()` is called to retrieve the next element and print it. ### Aufgabe 10 - Verändern Sie ihre Implementierung der `DoublyLinked List` aus Aufgabe 3 so, dass sie generische Datentypen verwalten kann. Erstellen sie hierzu die Klasse GenericDoublyLinkedList und GenericDoublyLinkedListEntry. Nehme Sie an es existiere die Klasse Tier. - Wie wäre eine GenericDoublyLinkList zu deklarieren, die nur Tiere und ihre Kindklassen aufnimmt? Tipp: Wildcards. #### **Step 1**: Creating the Generic Node Class** ```java Copy code class GenericDoublyLinkedListEntry<T> { T data; GenericDoublyLinkedListEntry<T> next; GenericDoublyLinkedListEntry<T> prev; public GenericDoublyLinkedListEntry(T data) { this.data = data; this.next = null; this.prev = null; } } ``` #### **Step 2** : Creating the Generic Doubly Linked List Class ```java java Copy code class GenericDoublyLinkedList<T> { private GenericDoublyLinkedListEntry<T> head; private GenericDoublyLinkedListEntry<T> tail; public GenericDoublyLinkedList() { this.head = null; this.tail = null; } public void add(T data) { GenericDoublyLinkedListEntry<T> newEntry = new GenericDoublyLinkedListEntry<>(data); if (head == null) { head = newEntry; tail = newEntry; } else { tail.next = newEntry; newEntry.prev = tail; tail = newEntry; } } public void printList() { GenericDoublyLinkedListEntry<T> current = head; while (current != null) { System.out.println(current.data); current = current.next; } } } ``` #### **Step 3**: Specializing the Generic Doubly Linked List for Tier and Its Subclasses To declare a GenericDoublyLinkedList that can accept instances of Tier and its subclasses, you can use Java's wildcard generics with an upper bound. This is how you would declare such a list: ``java GenericDoublyLinkedList<? extends Tier> animalList = new GenericDoublyLinkedList<>(); `` ##### **Example Usage** Here's how you would use these classes in a Java program. This example assumes the existence of a class Tier and a subclass Katze (cat): ```java Copy code class Tier {} class Katze extends Tier {} public class Main { public static void main(String[] args) { GenericDoublyLinkedList<Tier> myList = new GenericDoublyLinkedList<>(); myList.add(new Tier()); // Adding an instance of Tier myList.add(new Katze()); // Adding an instance of Katze, a subclass of Tier myList.printList(); } } ``` #### Explanation - **Generic Node Class**: GenericDoublyLinkedListEntry<T> can hold data of any type specified by T. - **Generic Doubly Linked List Class**: GenericDoublyLinkedList<T> provides basic functionalities like add() for adding new elements and printList() for printing all elements. - **Usage of Wildcards** : The declaration GenericDoublyLinkedList<? extends Tier> allows the list to accept objects of Tier and any of its subclasses, utilizing Java's wildcard generics with an upper bound to enforce type safety. ## Programmieren I Recap: Tutorium ### Quiz: 1. Wie viele und welche Zustände hat der Thread Lifecycle? a) (`New`, `Runnable`, `Timed Waiting`, `Waiting`, `Blocked`, `Terminated`) b) (`New`, `Ready`, `Running`, `Timed Waiting`, `Waiting`, `Blocked`, `Terminated`) c) (`New`, `Runnable`, `Ready`, `Running`, `Timed Waiting`, `Waiting`, `Blocked`, `Terminated`) d) (`New`, `Runnable`, `Terminated`) 2. Mit welcher Methode kann der aktuelle Thread auf einen bestimmten Thread warten? a) `wait()` b) `join()` c) `interrupt()` d) `notity()` 3. Was sind Streams? a) eine Sequenz von Daten b) die Möglichkeit in Java eine Netzwerkverbindung herzustellen die Möglichkeit in Java Programme parallel laufen zu lassen c) Streams sind die Möglichkeit eine Client-Server Architektur in Java zu implementieren 4. Was ist der Unterschied zwischen einem `FileReader` und einem `BufferedReader`? a) `FileReader` liest einzelne Werte ein und `BufferedReader` ganze Zeilen b) `BufferedReader` liest einzelne Werte ein und `FileReader` ganze Zeilen c) `FileReader` ist ein OutputStream und `BufferedReader` ein InputStream d) `FileReader` ist ein InputStream und `BufferedReader` ein OutputStream 5. Wie funktioniert der Verbindungsaufbau einer Client Server Architektur in Java? a) 1. Socket (Client) verbindet sich mit ServerSocket. 2. Server erstellt Socket Instanz um zu antworten. 3. Socket Instanz des Servers verbindet sich mit dem Client b) 1. Socket verbindet sich mit ServerSocket. 2. Socket Instanz des Servers verbindet sich mit Client. c) 1. Client verbindet sich durch Streams mit dem Server. 2. Server antwortet mit Outpustream und stellt die Verbindung her Lösungen: 1. a), 2. b), 3. a), 4. a), 5. a) ![image](https://hackmd.io/_uploads/B1ib7wLW0.png) Scheduling: - Organisation der Prozesse auf unterschiedliche Cores - findet auf der CPU statt

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully