Programowanie w języku Java WYKŁAD dr inż. Piotr Zabawa Certyfikowany Konsultant IBM/Rational e-mail: pzabawa@pk.edu.pl www: http://www.pk.edu.pl/~pzabawa 31.03.2014
WYKŁAD 6 Operacje we/wy
Operacje we/wy Biblioteki standardowe ze strumieniami: java.io java.nio oraz zmiany wprowadzone w ramach Java 8.
Operacje we/wy Ze względu na dużą różnorodność klas w bibliotekach we/wy intensywnie wykorzystywany jest wzorzec projektowy Dekorator. Pozwala on na składanie obiektów tej biblioteki w ogromnej ilości kombinacji. Zostanie to zilustrowane przykładami.
Operacje we/wy Strumień jest pojęciem abstrakcyjnym mającym następujące cechy: strumień związany jest ze źródłem lub odbiornikiem danych źródło lub odbiornik mogą być dowolne: plik, pamięć, URL, gniazdo, potok... strumień służy do zapisywania-odczytywania informacji - dowolnych danych program: kojarzy strumień z zewnętrznym źródłem/odbiornikiem, otwiera strumień, dodaje lub pobiera dane ze strumienia, zamyka strumień. przy czytaniu lub zapisie danych z/do strumienia mogą być wykonywane dodatkowe operacje (np. buforowanie, kodowanie-dekodowanie, kompresjadekompresja) w Javie dostarczono klas, reprezentujących strumienie. Hierarchia tych klas pozwala na programowanie w sposób abstrahujący od konkretnych źródeł i odbiorników.
Operacje we/wy Wprowadzono w bibliotece we/wy różne rodzaje klas i interfejsów: Strumieniowe Dodatkowe
Operacje we/wy Kierunki operacji strumieniowych: Wejściowe Wyjściowe Rodzaje strumieni: Bajtowe (1 bajt) Znakowe (2 bajty, bo Unicode) Dodatkowe zagadnienia: Serializacja Operacje na plikach
Operacje we/wy Wierzchołek hierarchii klas strumieniowych przedstawiono w tabelce poniżej. Wejście Wyjście Strumienie bajtowe InputStream OutputStream Strumienie znakowe Reader Writer Te abstrakcyjne klasy oferują podstawowe funkcjonalności: read() write() skip(), mark(), reset() close()
Operacje we/wy Strumienie: Bajtowe: (Stream) Wejściowe (InputStream, FilterInputStream,...) Wyjściowe (OutputStream, FilterOutputStream,...) Znakowe (Reader, Writer) Wejściowe (InputStreamReader,...) Wyjściowe (OutputStreamReader,...)
Operacje we/wy - klasy przedmiotowe Strumieniom trzeba prędzej czy później wskazać konkretne źródło lub odbiornik. Można je sklasyfikować następująco klasy przedmiotowe: Pamięć Potok Źródło/odbiornik Strumienie bajtowe Strumienie znakowe FileReader, FileWriter ByteArrayInputStream, ByteArrayOutputStream StringBufferInputStream PipedInputStream, PipedOutputStream CharArrayReader, CharArrayWriter StringReader, StringWriter PipedReader, PipedWriter - FileInputStream, FileOutputStream Klasy przedmiotowe mogą dotyczyć innych zasobów są dostępne w innych pakietach, np. sieciowych.
Operacje we/wy - klasy przetwarzające W czasie wykonywania operacji na strumieniach mogą być wykonywane dodatkowe operacje wzbogacające funkcjonalność dotychczasowych klas o dodatkowe zobowiązania.
Rodzaj przetwarzania Strumienie bajtowe Strumienie znakowe Buforowanie Filtrowanie Konwersja: bajty-znaki Konkatenacja Serializacja obiektów Konwersje danych BufferedInputStream, BufferedOutputStream FilterInputStream, FilterOutputStream SequenceInputStream ObjectInputStream, ObjectOutputStream DataInputStream, DataOutputStream BufferedReader, BufferedWriter FilterReader, FilterWriter InputStreamReader, OutputStreamWriter Zliczanie wierszy LineNumberInputStream LineNumberReader Podglądanie PushbackInputStream PushbackReader Drukowanie PrintStream PrintWriter
Operacje we/wy strumienie bajtowe Strumienie bajtowe traktują dane jak zbiór ośmiobitowych bajtów. Wszystke strumienie bajtowe dziedziczą z klas: InputStream (dane przychodzące do programu) lub OutputStream (dane wychodzące z programu). Strumienie zawsze należy zamykać! Strumienie bajtowe reprezentują niskopoziomowy dostęp do danych.
Operacje we/wy strumienie bajtowe Szczególnym rodzajem strumieni bajtowych są strumienie binarne - koniec strumienia rozpoznaje się jako wyjątek EOFException. Klasyfikacja strumieni binarnych: strumienie danych (zapis/odczyt surowych danych) DataInputStream DataOutputStream strumienie obiektowe (serializacja/deserializacja obiektów) ObjectInputStream ObjectOutputStream
Operacje we/wy strumienie bajtowe Strumienie bajtowe wejściowe InputStream przodek hierarchii, istotne metody: int read(), int read(byte[]), int read(byte[], int, int) PipedInputStream służy do komunikacji wewnątrz programu ByteArrayInputStream strumień czytający z tablicy FilterInputStream przodek strumieni wprowadzających dodatkową funkcjonalność DataInputStream operujące na typach prymitywnych BufferedInputStream strumień buforowany GZIPInputStream strumień dekompresujący w locie dane przez niego przechodzące SequenceInputStream skleja kolejne strumienie
Operacje we/wy strumienie bajtowe Strumienie bajtowe wyjściowe OutputStream przodek hierarchii, istotne metody: write(int), write(byte[]), write(byte[],int,int) PipedOutputStream służy do komunikacji wewnątrz programu ByteArrayOutputStream strumień piszący do tablicy FilterOutputStream przodek strumieni wprowadzających dodatkową funkcjonalność DataOutputStream operujące na typach prymitywnych BufferedOutputStream strumień buforowany GZIPOutputStream strumień kompresujący w locie dane przez niego przechodzące FileOutputStream strumień piszący do pliku
Operacje we/wy strumienie bajtowe Klasy buforowanych strumieni bajtowych: BufferedInputStream BufferedOutputStream Pozwalają one na zapis/odczyt całymi liniami (buforowanie).
Operacje we/wy - strumienie znakowe Strumienie znakowe automatycznie konwertują dane tekstowe do stosowanego natywnie w Java formatu Unicode. Konwersja jest dokonywana w oparciu o ustawienia regionalne komputera, na którym uruchomiono JVM (Wirtualną Maszynę Javy) lub jest oprogramowana przez programistę. Strumienie znakowe dziedziczą z klas: Reader (dane przychodzące do programu) lub Writer (dane wychodzące z programu).
Operacje we/wy - strumienie znakowe Strumienie znakowe wejściowe Reader przodek hierarchii; istotne metody: write(int), write(byte[]), write(byte[],int,int) BufferedReader buforowany strumień znakowy: podstawowa metoda String readline() LineNumberReader strumień zliczający numery linii CharArrayReader strumień piszący do tablicy znaków PipedReader analogiczny do PipedOutputStream InputStreamReader połączenie strumienie znakowych i bajtowych FileReader strumień skierowany do pliku
Operacje we/wy - strumienie znakowe Strumienie znakowe wyjściowe Writer przodek hierarchii; istotne metody: write(int), write(char[]), write(string) BufferedWriter buforowany strumień znakowy CharArrayWriter strumień piszący do tablicy znaków PipedWriter analogiczny do PipedOutputStream OutputStreamWriter połączenie strumienie znakowych i bajtowych FileWriter strumień skierowany do pliku PrintWriter podobny do PrintOutputStream, ale nie dysponuje metodami do obsługi byte[]
Operacje we/wy - strumienie znakowe Klasy buforowanych strumieni znakowych: BufferedReader BufferedWriter Pozwalają one na zapis/odczyt całymi liniami (buforowanie).
Operacje we/wy - strumienie znakowe Przykłady: Stdin String s; BufferedReader br = new BufferedReader( new InputStreamReader(System.in)); while ( (s = br.readline())!= null ) { } System.out.println(s); Strumienie znakowe buforowane umożliwiają odczytywanie tekstu wierszami.
Operacje we/wy - strumienie znakowe Stdout String fname = "c:\\data\\output.txt"; Writer writer = new OutputStreamWriter(new FileOutputStream(fname)); writer.write("hello World"); writer.close();
Operacje we/wy - strumienie znakowe Przykład dekorowania: String s; BufferedReader br = new BufferedReader( new InputStreamReader( new GZIPInputStream( new FileInputStream( new File( data.txt.gz ))))); while ( (s = br.readline())!= null ) { } System.out.println(s);
Operacje we/wy Strumienie działające na plikach: FileInputStream FileOutputStream FileReader FileWriter
Operacje we/wy Znaczenie klasy File operacje na plikach i na katalogach. Problem rekurencyjnego nawigowania po systemie plików.
Operacje we/wy Pliki o dostępie swobodnym: RandomAccessFile Nie są one strumieniami i nie należą do omawianej hierarchii.
Operacje we/wy Zastosowania serializacji: komunikacja pomiędzy obiektami/aplikacjami poprzez gniazdka (sockets), zachowanie obiektu (jego stanu i właściwości) do późniejszego odtworzenia i wykorzystania przez tę samą lub inną aplikację. Ma zastosowanie w aplikacjach korporacyjnych. W czasie serializacji zapisywany jest rekurencyjnie stan obiektów składowych, jednak pod warunkiem, że implementują one interfejs Serializable.
Operacje we/wy Serializacja związana jest ze strumieniami obiektowymi. Klasa serializowana musi implementować interfejs Serializable. Jeśli w trakcie serializacji/deserializacji trzeba wykonać nietypowe operacje, to należy zaimplementować metody: private void writeobject(java.io.objectoutputstream out) throws IOException; private void readobject(java.io.objectinputstream in) throws IOException, ClassNotFoundException;
Operacje we/wy przy serializacji nie są zapisywane pola statyczne oraz pola deklarowane ze specyfikatorem transient; specyfikatora transient używamy więc wobec elementów informacji o obiekcie, których nie chcemy poddawać utrwaleniu. pełniejszą kontrolę nad sposobem serializacji możemy zyskać definiując odpowiednie metody w klasie obiektu serializowanegoo, metody te winny mieć następujące sygnatury: całkowitą kontrolę nad formatem i sposobem serializacji zyskujemy poprzez implementację w klasie interfejsu Externalizable i dostarczenie metod writeexternal i readexternal
Operacje we/wy Serializacja i deserializacja // serializacja class X implements Serializable { int i; } ObjectOutputStream s = new ObjectOutputStream( new FileOutputStream("data.dat")); s.writeobject("today"); s.writeobject(new Date()); s.writeobject(new X()); s.flush();
Operacje we/wy // deserializacja ObjectInputStream s = new ObjectInputStream( new FileInputStream("data.dat")); String today = (String)s.readObject(); Date date = (Date)s.readObject(); X x = (X) s.readobject();
Operacje we/wy Dla obiektów typu JavaBeans istnieje także możliwość serializacji tekstowej (do plików w formacie XML) z wykorzystaniem klas XMLEncoder i XMLDecoder.
Operacje we/wy Potoki. Służą one do przesyłania danych pomiędzy równolegle działającymi wątkami. O wielowątkowości w Java będziemy mówili na jednym z dalszych wykładów. Na następnych dwóch slajdach przykład komunikacji między dwoma obiektami-wątkami.
Operacje we/wy class DataPutter extends Thread { OutputStream out; public DataPutter(OutputStream o) { out = o; } public void run() { try { for (char c = 'a'; c <= 'z'; c++) out.write(c); out.close(); } catch(ioexception exc) { return; } } }
Operacje we/wy class DataGetter extends Thread { InputStream in; public DataGetter(InputStream i) { in = i; } public void run() { try { int c; while ((c = in.read())!= -1) System.out.println((char) c); } catch(ioexception exc) { return; } } }
Operacje we/wy Dodatkowe usługi związane ze strumieniami w pakiecie: java.util Obejmują one: java.util.scanner java.util.formatter java.util.locale java.util.resourcebundle
Operacje we/wy Nowa biblioteka (od Java 1.7) we/wy: java.nio Wprowadzono w niej nieblokujące operacje we/wy. Operacje oferowane przez tę bibliotekę są znacznie szybsze niż przez java.io. Zawiera ona następujące elementy: Buffers kontenery danych (także: mapowane do pamięci) Channels mechanizmy operowania na danych Selectors mechanizmy wybierania kanałów dostępnych (gotowych do operacji we/wy)
Operacje we/wy Działania na plikach: Pakiet java.nio.file Interfejs Path reprezentuje pliki i katalogi Klasa Files operacje na plikach Obsługuje: Strumienie bajtowe Kanały Swobodny dostęp do plików za pomocą kanałów i interfejsu SeekableByteChannel Mapowanie kanałów na pamięć błyskawiczne operacje na plikach
Operacje we/wy Archiwa JAR możliwość kompresji, umieszczania w nich klas, cyfrowego podpisywania, : JarOutputStream JarInputStream Sposób tworzenia archiwum z linii komend: jar cf archiwum.jar klasa1.class klasa2.class... c tworzenie pliku (create), f zawartość archiwum zostanie zapisana do pliku archiwum.jar m do archiwum zostanie dołączony plik manifest z określonej lokalizacji, np: jar cmf plik_manifest archiwum.jar *,
Operacje we/wy W archiwum jar znajduje się katalog META-INF a w nim plik MANIFEST.MF zawierający dodatkowe informacje o archiwum. Przykładowa zawartość: Manifest-Version: 1.0 Created-By: 1.5.0-b64 (Sun Microsystems Inc.) Ant-Version: Apache Ant 1.6.5 Main-Class: pl.edu.pk.jp.runnerclass mówi, że po uruchomieniu archiwum wykonana zostanie metoda main(string[] args) zawarta w klasie RunnerClass znajdującej się w pakiecie pl.edu.pk.jp. Uruchomienie pliku jar: java -jar archiwum.jar
Java 8
Java 8 Stream API Ze względu na wzrost znaczenia możliwości sprzętowych oferowanych przez współczesne mikroprocesory postanowiono położyć nacisk na równoległość przetwarzania: Pracowano nad opartym na Javie systemie operacyjnym czasu rzeczywistego Wprowadzano w Java implementację wielowątkowości Fork Join Framework (FJF) Implementowano biblioteki standardowe Java opierając je o FJF Przykładem takiej biblioteki jest właśnie Java 8 Stream API. Skoncentrowano się w niej na przetwarzaniu danych w sposób abstrakcyjny zwalniając tym samym programistę od konieczności oprogramowania niskopoziomowej logiki wielowątkowej.
Java 8 Stream API Kolekcje a strumienie: Kolekcje predefiniowane struktury danych w pamięci zawierające elementy wyliczane przed umieszczeniem ich w kolekcji Strumienie ustalone struktury danych wyliczające elementy na żądanie Strumienie w Java 8 można postrzegać jako leniwie konstruowane kolekcje, w których wartości elementów wyznaczane są na żądanie użytkownika. Odwrotnie niż w Java 8-.
Java 8 Stream API Strumienie w Java 8 zostały zdefiniowane w pakiecie: java.util.stream Kolekcje Java 8 mają dodane metody zwracające Stream. Z punktu widzenia strumieni źródłem danych dla nich mogą być: Kolekcje Operacje we/wy Tablice Podobnie jak w przypadku funkcyjnych języków programowania Strumienie wspierają operacje agregowania przykłady: filter, map, reduce, find, match, sort Operacje te mogą być wykonywane sekwencyjnie lub równolegle.
Java 8 Stream API Dodatkowo strumienie w Java 8 wspierają: Pipelining (operacje na strumieniach zwracają strumienie możliwość składania operacji) Iteracje wewnętrzne
Java 8 Stream API Przykład wyszukanie spośród studentów takich dziesięciu, których nazwiska zaczynają się od A : List<String> names = students.stream().map(student::getname).filter(name->name.startswith("a")).limit(10).collect(collectors.tolist()); Operacje: Pośrednie (intermediate) powyżej map, filter, limit Końcowe (terminal)
Java 8 Stream API W Java 8 wprowadzono możliwość wykorzystania wyrażeń lambda. Przykład stare API. List<Block> blocks = /*... */; int sumofweights = 0; for (Block block : blocks) { if (block.getcolor() == Color.RED) { sumofweights += block.getweight(); } }
Java 8 Stream API Przykład nowe API. List<Block> blocks = /*... */; int sumofweights = blocks.stream().filter(b -> b.getcolor() == Color.RED).map(b -> b.getweight()).sum(); Można spotkać się ze słowami krytyki dotyczącymi braku bezstanowych wyrażeń lambda w Java 8 negatywnie rzutującymi na możliwą do uzyskania efektywność operacji strumieniowych.
Operacje we/wy Używanie standardowej biblioteki we/wy w każdej z postaci wymaga przechwytywania i obsługi wyjątków po stronie klienta. Dlatego na kolejnym wykładzie poznamy wyjątki. W czasie wykorzystywania operacji we/wy na zajęciach polegaliśmy na mechanizmie generowania obsługi wyjątków oferowanym przez środowisko deweloperskie.
Koniec