Datenstrukturen

Werbung
Datenstrukturen
Schwerpunkte
Bedeutung von Datenstrukturen
Dynamische Datenstrukturen mit Java
Umgang mit Objektreferenzen als "Pointer"
Vorbereitung zum Collection Framework
Programmieren 2 - H.Neuendorf
(66)
Datenstrukturen
Zusammenfassung "gleichartiger" Daten in strukturierter Weise :
Technische Datenverwaltung + erlaubte semantische Operationen
Typischerweise wird wahlfreier Zugriff eingeschränkt - um Semantik zu realisieren
Bereits behandelte "Datenstrukturen" :
1. Klassen → Attribute enthalten Daten + Methoden regeln Zugriff
Sehr allgemein - damit beliebige konkrete Datenstrukturen darstellbar
2. Arrays → Indizierte Anordnung : Datenzugriff via Index
Wahlfreier Zugriff = Elemente in beliebiger Reihenfolge zugreifbar
aber : Einfügen zwischen belegte Plätze muss implementiert werden
Anzahl der Elemente steht fest - nicht veränderbar (statisch) !
⇒ Dynamische Datenstrukturen = Container, die sich dynamisch der wachsenden oder
verringernden Datenmenge anpassen !
Programmieren 2 - H.Neuendorf
(67)
Datenstrukturen
Stack und Queue zur Einführung
Realisation auf Basis von Arrays
Prinzip Abstrakter Datentypen
Beispiel: Abstrakter Datentyp Stack
Objekt-Pool
Programmieren 2 - H.Neuendorf
(68)
Stack
( Keller, Stapel )
Stapel von Daten (Objekten), die "übereinander liegen" :
LIFO = Last In First Out - Zuletzt abgelegtes Element wird als erstes wieder entnommen
Typische Operationen:
Stapel s, Element x :
Ablegen neuer Objekte oben auf Stapel
push
Zurückliefern + Entfernen des obersten Stapelobjekts
pop
Zurückliefern oberstes Stapelobjekt ohne Entfernen
peek
s.push( x ) ;
// Element abgelegt
x = s.pop( ) ;
// Rückgabe des entfernten obersten Elements
KEIN wahlfreier Zugriff - nur oberstes Element jeweils ansprechbar
x
s.push(x);
y
x
s.push(y);
Einfügen und Entnehmen
am selben Ende = "oben"
x
z = s.pop(); z = s.pop();
// z == y
// z == x
Entnahmereihenfolge kehrt
Einfügereihenfolge um:
"Aktuellstes" Element
immer oben
Informatik → Methodenstack / Rekursions-Zwischenergebnisse auf Stack ( kein Basisfall ⇒ stack overflow! )
Programmieren 2 - H.Neuendorf
(69)
Stack-Implementierung via Array
push : Anfügen des Elements "am Ende" des Arrays
pop :
an jeweils oberster Position
Abholen des Elements "vom Ende" des Arrays
Ausnahmesituationen : Kein Platz mehr für neue Elemente ⇒
Keine Elemente mehr im Array
Stack
- int [ ] s
- int pos
+ void push( int x )
⇒
overflow
underflow
Durch Hilfsmethoden
wie isFull() isEmpty()
… kann man Prüfung auf
RuntimeExceptions
vermeiden
Bedeutung pos :
Anzahl momentan im Stack gespeicherte Datenelemente
Zugleich Index des nächsten freien Platzes, auf den das nächste
einzufügende Datenelement zu setzen ist = aktuelle Einfüge-Position
+ int pop( )
Belegung beginnt bei Indexposition 0
// …
Wirkung von
Array s
pos = pos - 1 ;
nächster freier Platz hat Index pos -1;
dortiger Wert wird überschrieben
Statt int könnte man beliebige Objekte im Stack
ablegen durch entsprechenden Typ des Arrays
Bsp: Mitarbeiter[ ] s
push
...
pop
s[3]
s[2]
s[1]
s[0]
Programmieren 2 - H.Neuendorf
(70)
Stack-Impl. via Array
class Stack {
Stack hat "Gedächtnis" - merkt
sich zuletzt besetzten Platz
private int[ ] s ;
// privater Datenbehälter
private int pos = 0 ;
// Anzahl gespeicherter Elemente
public Stack( int size ) {
s = new int[ size ] ;
Array gewünschter
Größe anlegen
}
public void push( int x ) {
Zugriff nur über öffentliche Schnittstelle!
boolean overflow = ( pos >= s.length ) ;
Kein direkter Zugriff auf Daten !
if ( overflow ) { /* Ausnahmebehandlung */ }
s[ pos ] = x ;
pos++ ; // s[ pos++ ] = x ;
}
Prüfung, ob Array voll ist
public int pop( ) {
Wenn noch Platz, dann
Datenelement einfügen
boolean underflow = ( pos == 0 ) ;
Zähler hochzählen
pos-- ;
if ( underflow ) { /* Ausnahmebehandlung */ }
return s[ pos ] ;
}
public int peek( ) { return s[ pos-1] ; }
Prüfung, ob Array leer ist
public boolean isEmpty( ) { return pos == 0 ; }
Wenn nicht leer, dann
Datenelement zurückliefern
Zähler vermindern
public boolean isFull( ) { return pos >= s.length ; }
public void clear( ) { pos = 0 ; }
}
Programmieren 2 - H.Neuendorf
(71)
Queue ( Schlange, Puffer )
Daten-Röhre : An einem Ende rein, am andern raus ...
FIFO = First In First Out - Zuerst abgelegtes Element als erstes wieder entnommen
Typische Operationen : Anfügen neuer Objekte am Queue-Ende
Queue q, Element x :
put
Zurückliefern + Entfernen vorderstes Objekt
get
Zurückliefern vorderstes Objekt ohne Entfernen
peek
q.put( x ) ;
// Element abgelegt
x = q.get( ) ;
// Rückgabe + Entfernen vorderstes Element
KEIN wahlfreier Zugriff - nur vorderstes Element ansprechbar !
x
xy
x
q.put(x);
y
x
q.put(y);
q.put(z);
z
y
z
e = q.get();
// e == x
x
y
z
Head
Tail
Entnahmestelle
Einfügestelle
Einfügen und Entfernen an
verschiedenen Enden !
Entnahmereihenfolge identisch
zu Einfügereihenfolge
z
e = q.get();
// e == y
Warteschlangen dienen der Entkopplung.
Zwischengeschalteter Puffer zwischen Erzeuger
(Einfügen am tail) und Verbraucher (Entnahme
am head) von Daten, Nachrichten etc.
Keine direkte Interaktion.
Voraussetzung ist korrekte Synchronisation.
Informatik / Modellierung : Warteschlangen, Pipes, Message- / Task-Queues …
Programmieren 2 - H.Neuendorf
(72)
Queue-Implementierung via Array
put :
Anfügen Element am Array-Ende
get :
Abholen "ältestes" Elements vom am "längsten belegten" Array-Platz
Index head : erstes = "ältestes" Element ⇒
Entnahme alter Elemente aus q[ head ]
Index tail :
Einfügen neuer Elemente in q[ tail ]
nächster freier Platz
Ausnahmesituationen :
Queue
- int[ ] q
⇒
Kein Platz mehr für neue Elemente
⇒
overflow
Keine Elemente mehr im Array
⇒
underflow
0
1
2
3
4
5
6
7
4
5
6
7
q
tail
head
- int head, tail
0
- int size
1
2
3
q
+ void put( int x )
+ int get( )
head
tail
Vereinfachung: Nach Entnahme wird Inhalt nicht nach links verschoben !
Stattdessen Index head erhöht ⇒ belegter Teil wandert nach rechts ⇒
Belegter Teil erreicht Arrayende - obgleich Array nicht voll ist
Nutzung leerer Plätze am Anfang :
Zyklisches Array …
Programmieren 2 - H.Neuendorf
(73)
Queue-Implementierung via Array
0
1
2
3
4
5
6
7
q
Zyklisches Array (Ringpuffer) :
Indices head und tail laufen "im Kreis"
tail
head
0
1
2
3
4
5
6
7
Darstellbar durch Berechnung des Index tail
nach Einfügen :
tail = ( tail + 1 ) % 8 ;
q
allgemein : modulo size
head
tail
Berechnung Index head nach Entnahme :
head = ( head + 1 ) % size ;
7
0
Erkennen Ausnahmesituationen
6
a) Leere Schlange
1
underflow :
head == tail ;
b) Volle Schlange
overflow :
tail ist "rundherum gelaufen" - also wiederum head == tail.
Zur Unterscheidung von leerer Schlange wird deshalb boolsche
Variable full verwaltet :
Wenn head==tail in put( ) , dann ist Queue voll
wenn head==tail in get( ) , dann ist Queue leer
Programmieren 2 - H.Neuendorf
(74)
Queue via Array
Queue
- int[ ] q
- int head, tail
- int size
- boolean full
+ void put( int x )
+ int get( )
Zugriff nur über öffentliche
Schnittstelle!
Kein direkter Zugriff auf
Daten!
Array fester Größe ⇒
Umständliche Verwaltung der
Belegung als zyklisches Array
class Queue {
private int[ ] q ;
// privater Datenbehälter
private int head ;
// Index erstes Element
private int tail ;
// Index nächster freier Platz = Einfügeposition
private int size ;
// Queue-Größe
private boolean full ; // Kontrolle Füllstand
public Queue( int n ) {
q = new int[ n ] ;
size = n ;
tail = 0 ; head = 0 ; full = false ;
}
public void put( int x ) {
if ( isFull( ) ) throw new RuntimeException( "Full" ) ;
q[ tail ] = x ;
tail = (tail + 1) % size ;
full = ( head == tail ) ;
}
public int get( ) {
if ( isEmpty( ) ) throw new RuntimeException( "Empty" ) ;
int x = q[ head ] ;
head = (head + 1) % size ; full = false;
return x ;
}
public boolean isEmpty( ) { return head == tail && full == false ; }
public boolean isFull( ) { return full ; }
public void clear( ) { head = tail = 0 ; full = false ; }
}
Programmieren 2 - H.Neuendorf
(75)
Abstrakte Datentypen
ADT
Abstrakter Datentyp
von Implementierung unabhängige Spezifikation einer Datenstruktur
unterschiedliche Implementierungen sind möglich
Details der Implementierung interessieren Anwender nicht – sind weggekapselt
universelle, von Implementierung unabhängige Verwendung
"abstrakt" - da nach außen nur öffentliche Methoden bekannt - nicht aber konkrete Impl.-Details
Realisierung von ADTs in Java
Spezifikation (der Methoden) der Datenstruktur durch Interface
verschiedene implementierende Klassen
Unabhängigkeit von Implementierungen durch Verwendung des Interface-Typs beim Aufrufer
Instanziiert werden jedoch natürlich stets konkrete Implementierungen
Beispiel
Abstrakte Datentypen 'Stack' + 'Queue'
→
intern array-basiert oder list-basiert realisierbar
Programmieren 2 - H.Neuendorf
(76)
Abstrakte Datentypen
Bsp :
Datentyp Stack
Spezifikation (der Methoden) des Datentyps durch Interface
public interface Stack {
public void push( Object obj ) ;
public Object pop( ) ;
public Object peek( ) ;
public void clear( ) ;
public boolean isEmpty( ) ; // …
}
Wichtige Detail-Entscheidung bei Dokumentation
des Interfaces + seiner Implementierung (zB auch
im Collection Framework) :
Werden null-Werte als Datenelemente akzeptiert
– und führen dann zur Erhöhung der internen size ?
Können null-Werte von Methoden zurückgeliefert
werden ?
Zurückliefern von null sollte eher bedeuten, dass
gesuchtes Elemente nicht vorhanden ist !
Bereitstellen (mindestens) einer Implementierung
public class ArrStack implements Stack {
private Object[ ] s ;
private int pos = 0 ;
public void push( Object obj ) { // Implementierung …
}
/* … */
}
Anwendung : Methodenaufruf via Interface-Typ – sorgt für semantische Klarheit
Stack s = new ArrStack( ) ;
s.push( "Hallo" ) ;
Programmieren 2 - H.Neuendorf
(77)
Object Pool of reusable objects
mittels Array / Stack
Metapher : Pfandflaschen / Carpool
Wiederverwendung von Objektinstanzen, deren Lebenszyklus überwacht wird - weil deren
Erzeugung aufwendig ist oder deren Anzahl via Poolgröße limitiert / gesteuert werden soll
Instanzen nur zeitweise verwendet + danach zur Verwendung durch andere Clients zurück gestellt
Gebrauchtes Objekt wird bei Wiederaufnahme in Pool gesäubert, indem Zustand bereinigt wird
(Zurücksetzen in Ursprungszustand)
Pool-Überwachung ermöglicht effizienten Umgang mit Ressourcen + Aussage über Systemlast
Mögliche Probleme :
In Multithreaded-Umgebungen ist Pool ein bremsender Synchronisationspunkt
Leerer + zu voller Pool bremst Abläufe aus ⇒ Größe eventuell dynamisch anpassen
Clients müssen Objekte stets zurückgeben - auch bei Auftreten von Exceptions
Clients dürfen Referenz nach Rückgabe nicht mehr verwenden - durch gültige IDs checkbar
Clients dürfen Referenz nicht weitergeben
Clients müssen mit leerem Pool umgehen ⇒ Timeouts definieren
⇒ Stabile + sichere Realisation schwierig - nur möglich, wenn Clients unter eigener Kontrolle
Programmieren 2 - H.Neuendorf
(78)
Object Pool - via Stack
class ObjectPool {
private Stack pool ;
public ObjectPool( int size ) {
class ReusableObject {
pool = new Stack( size ) ;
private int zustand = 0 ;
while( pool.notFull( ) ) {
public int operation( ) {
pool.push( new ReusableObject( ) ) ;
}
zustand++ ;
}
return zustand ;
public ReusableObject acquire( ) {
}
if( pool.isEmpty( ) ) return null ;
public void clean( ){ zustand = 0 ; }
ReusableObject r = pool.pop( ) ;
}
return r ;
}
public void returnInstance( ReusableObject r ) {
Voraussetzung ist Stack-Klasse für
Objekte vom Typ ReusableObject :
if( pool.isFull( ) ) return ;
if( r != null ) {
Klasse Stack verwaltet intern ein
Array vom Typ ReusableObject
r.clean( ) ;
pool.push( r ) ;
Oder : Generics mit entsprechendem
Interface IF : <T extends IF>
}
}
}
Programmieren 2 - H.Neuendorf
(79)
Object Pool - Client
// Client ist unabhängig von Implementierungsdetails
class Client {
private ReusableObject myInstance ;
// Nutzung :
class PoolTest {
private ObjectPool myPool ;
public static void main( String[] args ) {
public Client( ObjectPool pool ) { myPool = pool ; }
ObjectPool pool = new ObjectPool( 5 ) ;
public void setInstance( ) {
Client c1 = new Client( pool ) ;
if( myInstance == null )
c1.setInstance( ) ;
myInstance = myPool.acquire( ) ;
c1.work( ) ;
}
c1.pushBack( ) ;
public void pushBack( ) {
// …
myPool.returnInstance( myInstance ) ;
}
myInstance = null ;
}
}
public void work( ) {
if( myInstance != null ) myInstance.operation( ) ;
}
}
Programmieren 2 - H.Neuendorf
(80)
Herunterladen