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"); s.writeobject(new Date()); s.flush(); ZAPIS
Serializacja przykład FileOutputStream out = new FileOutputStream("magazyn"); ObjectOutputStream s = new ObjectOutputStream(out); s.writeobject("hello World"); s.writeobject(new Date()); s.flush(); ZAPIS FileInputStream in = new FileInputStream("magazyn"); ObjectInputStream s = new ObjectInputStream(in); String today = (String)s.readObject(); Date date = (Date)s.readObject(); ODCZYT
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