Strumienie i serializacja

Transkrypt

Strumienie i serializacja
Strumienie i serializacja
Prezentacja dostępna na Syriuszu:
http://sirius.cs.put.poznan.pl/~inf80156
Klasy: InputStream, OutputStream, Reader i Writer
W Javie hierarchia strumieni oparta jest o cztery klasy:
●
➢
InputStream
➢
OutputStream
➢
Reader
➢
Writer
InputStream i Reader to strumienie danych wejściowych, natomiast
OutputStream i Writer strumienie danych wyjściowych. Inny podział
dotyczy obsługi danych binarnych, która reprezentowana jest przez
InputStream i OutputStream oraz do obsługi danych znakowych
poprzez klasy Reader i Writer.
●
Typy strumieni
–
–
–
Wszystkie typy strumieni są podklasami klas: InputStream,
OutputStream, Reader i Writer
podklasy FileInputStream, FileOutputStream, FileReader i
FileWriter - służą do odczytu i zapisu plików dyskowych, jako
pierwszy parametr konstruktora przekazujemy nazwę pliku, drugi
parametr jest opcjonalny, jest to wartość logiczna informująca o
tym czy plik zostanie nadpisany, czy dane mają być dopisywane
na jego koniec
ByteArrayInputStream, ByteArrayOutputStream,
CharArrayReader i CharArrayWriter - reprezentują bufor
pamięci oparty na tablicy bajtów / znaków, przy tworzeniu obiektu
wejściowego należy podać w konstruktorze tablicę na której ma
być oparty, natomiast przy wyjściowym - początkowy rozmiar
bufora
Typy strumieni
●
●
StringBufferInputStream (nie istnieje
StringBufferOutputStream), StringReader i StringWriter bufor w pamięci oparty na napisie String, przy tworzeniu
obiektu wejściowego przekazujemy mu napis na którym ma
być oparty, a przy wyjściowym początkowy rozmiar bufora
(zaleca się używanie klas StringReader i StringWriter)
PipedInputStream, PipedOutputStream, PipedReader i
PipedWriter - służą do komunikacji międzyprocesowej, na
początku przy pomocy konstruktora bezparametrowego
tworzymy obiekt wejściowy lub wyjściowy, a następnie
przekazujemy go konstruktorowi obiektu odpowiednio
wyjściowego lub wejściowego, wówczas strumienie zostaną
połączone łączem, które będzie przesyłać dane od strumienia
wyjściowego do wejściowego
Operacje na strumieniach
●
●
●
●
Otwieranie strumienia: strumień jest automatycznie
otwierany przy tworzeniu obiektu-strumienia. istnieje
również metoda open.
Czytanie ze strumienia: metoda read() - zwraca -1 w
przypadku błędu odczytu, w przypadku sukcesu
standardowo bajt lub dwubajtowy znak, przeciążona może
zwracać inne typy (np. łańcuch znaków)
Zapisywanie do strumienia: metoda write() - standardowo
jako parametr przyjmuje bajt lub dwubajtowy znak,
przeciążona może przyjmować inne inne typy (np. łańcuch
znaków)
Zamykanie strumienia: metoda close(), poza tym otwarty
strumień zamykany jest przy niszczeniu obiektu
Operacje na strumieniach
•
Uwaga: należy pamiętać o zamykaniu strumieni
szczególnie ze względu na fakt, że dla niektórych zasobów,
takich jak np. pliki dyskowe czy połączenia sieciowe,
obowiązują limity na liczbę naraz otwartych egzemplarzy. W
celu gwarancji, że zasoby zostaną zwolnione strumień
najlepiej zamykać w bloku finally.
Operacje na strumieniach - przykład
import java.io.FileReader;
import java.io.IOException;
public class Czytaj {
public static void main(String[] args) throws IOException {
FileReader czytacz = new FileReader("/tmp/jakis_plik.txt");
try {
int i;
// metoda read() zwraca w tym wypadku dwubajtowy znak,
// jeśli odczyt sie powiódł, w przeciwnym razie -1
while ((i = czytacz.read()) != -1)
System.out.print((char) i);
} finally {
czytacz.close(); // dzięki umieszczeniu w finally
// strumień zostanie zamknięty nawet
// przy wystąpieniu wyjątku
}
}
}
Konwersje pomiedzy strumieniami
Strumień binarny można przekształcić na strumień znakowy (taka konwersja
czasami jest bardzo przydatna, np. podczas kompresji i dekompresji danych).
Służą do tego klasy:
●
InputStreamReader - czyta bajty i dekoduje je na znaki (można wybrać
kodowanie), np.:
➢
BufferedReader in
= new BufferedReader(new InputStreamReader(System.in));
OutputStreamWriter - znaki zapisane do niego są kodowane do bajtów
(zależy od kodowania)
➢
Writer out
= new BufferedWriter(new OutputStreamWriter(System.out));
Wzorzec projektowy dekorator
➢
należy on do grupy wzorców strukturalnych
pozwala na rozszerzanie funkcjonalności istniejących klas dynamicznie,
polega to na opakowaniu klasy w nową klasę dekorującą (zwykle przekazuje
się obiekt jako parametr konstruktora dekoratora).
➢
dekoratory są alternatywą dla dziedziczenia w przeciwieństwie do niego
rozszerzają klasy w czasie działania programu, a nie w czasie kompilacji
➢
nowe zachowanie dodawane jest tylko dla pojedyńczego obiektu, a nie dla
całej klasy
➢
przykładem wzorca dekoratora jest implementacja strumieni I/O
w Javie
➢
Wzorzec projektowy dekorator
Standardowe wejście / wyjście
➢
➢
➢
➢
➢
podobnie jak w systemach Unixowych, w Javie dane wejściowe dla
programu są odczytywane z jednego strumienia – standardowego
wejścia (ang. standard input), dane wyjściowe są wysyłane do
standardowego wyjścia (ang. standard output), a informacje o błędach
do standardowego wyjścia błędu (ang. standard error)
umożliwia to łączenie programów w potoki przetwarzania
w Javie dostęp do standardowego wejścia / wyjścia zapewniają zmienne
statyczne klasy System
System.out i System.err (standardowe wyjście i wyjście błędów)
opakowane są w PrintStream
System.in (standardowe wejście) to zwykły InputStream, ze względu na
fakt że ze standardowego wyjścia najczęściej czytamy dane tekstowe,
warto opakować go w BufferedReader, aby można było używać metody
readLine();
Przekierowywanie standardowego wejścia/wyjścia
Do przekierowania standardowego wyjścia, wejścia i wejścia błędu służą
metody:
●
➢
setOut(PrintStream)
➢
setIn(InputStream)
➢
setErr(PrintStream)
Kompresja? Nic prostszego!
Kompresji w Javie może dokonać w prosty sposób używając gotowych
dekoratorów z pakietu java.util.zip, wyjaśnimy to na przykładzie:
import java.io.*;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
public class KompresjaGZIP {
public static void main(String[] args) throws IOException {
String nazwaPliku = "/tmp/kompresja.gzip";
BufferedOutputStream os = new BufferedOutputStream(
new GZIPOutputStream(
new FileOutputStream(nazwaPliku)));
PrintWriter pw = new PrintWriter(
new OutputStreamWriter(os));
pw.write("Kompresja? Nic prostszego!");
pw.close();
Kompresja? Nic prostszego!
// dekompresja
BufferedInputStream is = new BufferedInputStream(
new GZIPInputStream(
new FileInputStream(nazwaPliku)));
BufferedReader br = new BufferedReader(
new InputStreamReader(is));
}
}
String s;
while ( (s = br.readLine()) != null )
System.out.println(s);
br.close();
Nowe wejście / wyjście - java.nio.*
W Javie 1.4 dodano "nowe" biblioteki wejścia/wyjścia (ang. new I/O)
zebrane w pakietach java.nio.*
●
głównym celem przy opracowywaniu nowych bibliotek było
przyspieszenie ich działania
➢
sposób organizacji wejścia/wyjścia jest podobny do stosowanego w
systemach operacyjnych
➢
posługiwanie się nowymi bibliotekami wymaga więcej pracy i
uwagi, dlatego też zaleca się stosowanie “starych” bibliotek, jeśli
szybkość działania nie ma dla nas bardzo istotna
➢
Koncepcja serializacji
●
przekształcenie obiektów w strumień
bajtów, z zachowaniem aktualnego stanu
obiektu
Koncepcja serializacji
●
●
przekształcenie obiektów w strumień
bajtów, z zachowaniem aktualnego stanu
obiektu
stosowana np. wtedy, gdy potrzebujemy
utrwalenia danych (tzw. lekka trwałość)
Koncepcja serializacji
●
●
●
przekształcenie obiektów w strumień
bajtów, z zachowaniem aktualnego stanu
obiektu
stosowana np. wtedy, gdy potrzebujemy
utrwalenia danych (tzw. lekka trwałość)
wykorzystywana w programowaniu
rozproszonym
Serializacja
●
Jest to przekształcanie obiektów na postać
binarną lub znakową
Serializacja
●
●
Jest to przekształcanie obiektów na postać
binarną lub znakową
Proces odwrotny nazywamy deserializacją
Serializacja
●
Aby obiekt był poddany automatycznej
serializacji musi implementować interfejs
java.io.Serializable.
Serializacja
●
●
Aby obiekt był poddany automatycznej
serializacji musi implementować interfejs
java.io.Serializable.
Nie posiada on żadnych metod i pełni
jedynie rolę znacznika.
Serializacja
●
●
●
Aby obiekt był poddany automatycznej
serializacji musi implementować interfejs
java.io.Serializable.
Nie posiada on żadnych metod i pełni
jedynie rolę znacznika.
Większość klas ze standardowych
bibliotek Javy, m.in. wszystkie klasy
opakowujące, kolekcje oraz napisy String
implementuje ten interfejs.
Serializacja
●
Przy serializacji obiektu, który agreguje
inne obiekty, serializacji ulega cała
hierarchia
Serializacja
●
●
Przy serializacji obiektu, który agreguje
inne obiekty, serializacji ulega cała
hierarchia
Jeżeli zmieni się klasa obiektu już
zserializowanego, to deserializacja przy
użyciu nowej implementacji jest
niemożliwa
Serializacja – użycie
●
Aby zapisać lub odczytać klasę należy
utworzyć obiekt ObjectOutputStream
a następnie skorzystać z metod:
–
void writeObject(Object)
–
Object readObject()
w celu zapisania lub odczytania obiektu.
Serializacja – przykład
FileOutputStream out = new FileOutputStream("magazyn");
ObjectOutputStream s = new ObjectOutputStream(out);
s.writeObject("Hello World");
ZAPIS
s.writeObject(new Date());
s.flush();
Serializacja – przykład
FileOutputStream out = new FileOutputStream("magazyn");
ObjectOutputStream s = new ObjectOutputStream(out);
s.writeObject("Hello World");
ZAPIS
s.writeObject(new Date());
s.flush();
FileInputStream in = new FileInputStream("magazyn");
ObjectInputStream s = new ObjectInputStream(in);
String today = (String)s.readObject();
ODCZYT
Date date = (Date)s.readObject();
Słowo kluczowe transient
●
Domyślnie serializowane są wszystkie
niestatyczne składowe każdego obiektu,
nawet jeżeli są oznaczone jako prywatne.
Słowo kluczowe transient
●
●
Atrybuty, które mają być wyłączone z
domyślnego mechanizmu serializacji należy
oznaczyć przy pomocy modyfikatora
transient.
Deserializacja pozostawia na takich
atrybutach wartość domyślną dla danego
typu, np. dla typów obiektowych jest to null.
●
Można zupełnie zrezygnować z domyślnego
mechanizmu serializacji i podać kod go zastępujący.
●
●
Można zupełnie zrezygnować z domyślnego
mechanizmu serializacji i podać kod go zastępujący.
Jeżeli klasa definiuje metody:
– private void
writeObject(ObjectOutputStream oos)
throws IOException;
–
private void
readObject(ObjectInputStream ois)
throws IOException,
ClassNotFoundException;
będą one używane do serializacji i deserializacji jej
obiektów.
Interfejs Externalizable
●
Istnieje jeszcze jeden sposób zastąpienia
domyślnego mechanizmu serializacji. Klasa może
implementować interfejs java.io.Externalizable i
definiować wymagane przez niego metody
– void writeExternal(ObjectOutput out)
throws IOException;
–
void readExternal(ObjectInput in)
throws IOException,
ClassNotFoundException;
Serializacja do postaci XML
●
●
Rolę ObjectOutputStream i ObjectInputStream
pełnią klasy XMLEncoder i XMLDecoder z
pakietu java.beans. W tym wypadku
serializowane obiekty nie muszą
implementować interfejsu Serializable.
Tworzony jest standardowy plik tekstowy XML