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