Programming-Java

Tasks studies - laboratory

View project on GitHub

Lab12 - Obsługa strumieniu w języku Java

Strumień (stream) to sekwencja danych, najczęściej bajtów. Pochodzenie oraz typ sekwencji danych zależny jest od danego środowiska. Podstawowe typy strumieniu związane są z operacjami wejściawyjścia. W języku Java do obsługi operacji wejścia utworzono klasą InputStream, natomiast do operacji wyjścia OutputStrem. Strumienie związane są z typem obszaru (urządzenia), z którego sekwencja danych jest odczytywana lub zapisywana. Takimi urządzeniami mogą być:

  • pamięć operacyjna,
  • dyski twarde,
  • ekran,
  • drukarka,
  • sieć, itd.

Do typów danych, które są wykorzystywane przez strumienie do przesyłania informacji należą:

  • byte,
  • String,
  • Object.
1.AudioInputStream
2.ByteArrayInputStream
3. FileInputStream
4. FilterInputStream
  1.BufferedInputStream
  2.CheckedInputStream
  3.CipherInputStream
  4.DataInputStream
  5.DigestInputStream
  6. InflaterInputStream
  7.LineNumberInputStream
  8. ProgressMonitorInputStream
  9. PushbackInputStream
5. InputStream
6.ObjectInputStream
7. PipedInputStream
8. SequenceInputStream
9. StringBufferInputStream
1.ByteArrayOutputStream
2. FileOutputStream
3. FilterOutputStream
  1.BufferedOutputStream
  2.CheckedOutputStream
  3.CipherOutputStream
  4.DataOutputStream
  5.DeflaterOutputStream
  6.DigestOutputStream
  7. PrintStream
4.ObjectOutputStream
5.OutputStream
6. PipedOutputStream

W języku java strumienie obsługiwane są przez klasy OutputStream oraz InputStream reprezentują je jako sekwencje bajtów - elementów typu byte. Najczęściej jednak zachodzi potrzeba formatowania danych strumienia. Dokonuje się tego w języku Java przy wykorzystaniu różnych klas formatujących, które dziedziczą po OutputStream i InputStream. Przykładowo pisanie tekstu w konsoli realizowane jest przy użyciu System.out.println(), przy czym out jest obiektem klasy PrintStream, która stanowy klasę formatującą bajty sekwencji pochodzących z OutputStream na tekst. W java zaimplementowano również klasy Reader oraz Writer, które stanowią analogię do klas InputStream oraz OutputStream, przy czym służą one do obsługi danych tekstowych (String). Obsługa wejścia – klasa InputStream Klasa InputStream jest klasą abstrakcyjną. Zawiera podstawowe metody, które umożliwiają odczytywanie oraz kontrolę bajtów ze strumienia. Ponieważ jest abstrakcyjną nie można dynamicznie utworzyć obiektu tej klasy, lecz można go uzyskać przez odwołanie się do standardowego wejścia zainicjowanego zawsze w polu in klasy System, czyli System.in. Istnieją także inne możliwości uzyskania obiektu klasy InputStream np. wywołanie metod zwracających referencję do obiektu tego typu tj. np.: metoda getInputStream() zdefiniowana w klasie Socket. Metoda read() - czytająca kolejny bajt ze strumienia wejściowego, jest jedyną metodą abstrakcyjną w klasie InputStrem i to ona czyni ją klasą abstrakcyjną. Pozostałe metody umożliwiają:

-odczyt bajtów do zdefiniowanej tablicy

int read(byte b[]);
int read(byte b[], int offset, int length);
  • pominięcie określonej liczby bajtów w odczycie: long skip(long n);

  • kontrolę stanu strumienia (czy są dane):
    int available();
    
  • tworzenie markerów:
    boolean markSupported(); // kontrola czy tworzenie markerów
    // jest możliwe
    synchronized void mark(int readlimit);
    synchronized void reset();
    
  • zamknięcie strumienia
    void close();
    

Prawie wszystkie metody oprócz markSupported() i mark() mogą generować wyjątki, które muszą być zadeklarowany lub obsługiwany w kodzie programu. Dla strumieni podstawową klasą wyjątków jest IOException. Obsługa wejścia – klasa OutputStream Podobnie do klasy InputStream zdefiniowana jest klasa OutputStream, dotycząca obsługi wyjścia. Klasa analogicznie do poprzednio omawianej jest abstrakcyjna ze względu na jedyną metodę abstrakcyjną metodą write(), która zapisuje kolejne bajty do strumienia. Do podstawowych metod OutputStream klasy zaliczyć można:

  • zapisują dane z tablicy b do strumienia wyjścia
    void write(byte[] b, int off, int len);
    void write(byte[] b);
    
  • zamknięcie strumienia
    void close() ;
    
  • przesuwanie buforowanych danych do strumienia
    void flush();
    

    Poniższy program ukazuje proste zastosowanie strumieni. ``` import java.io.*;

public class Echo{ public static void main(String args[]){ byte b[] = new byte[100]; try{ System.in.read(b); System.out.write(b); System.out.flush(); } catch (IOException ioe){ System.out.println(“Błąd wejścia-wyjścia”); } } }


Obsługa plików
Klasa `File` służy do opisu abstrakcyjnej reprezentacji ścieżek dostępu do plików oraz katalogów.
Ścieżki dostępu dzielą się ze względu na zasięg na względne (relatywne - podają ścieżkę do pliku
względem bieżącego katalogu) i bezwzględne (absolutne - podają ścieżkę do pliku względem
głównego korzenia systemu plików w danym systemie operacyjnym).
Można dokonać również podziału ze względu na środowisko, dla którego są definiowane, co
praktycznie dzieli ścieżki dostępu na zdefiniowane dla systemu Unix oraz MS Windows.

- absolutna ścieżka dostępu:
**UNIX:** /utl/software/java/projects
**MS Windows:** c:\utl\softare\java\projects

- relatywna ścieżka dostępu:
**UNIX:** java/projects
**MS Windows:** java\projects.

Przy tworzeniu obiektu klasy `File` dokonywana jest konwersja łańcucha znaków na abstrakcyjną
ścieżkę dostępu do pliku. Metody `klasyFile` umożliwiają kontrolę podanej ścieżki i plików (np.
`isFile()`, `isDirectory()`, `isHidden`, `canRead()`, itd.) oraz dokonywania konwersji (np. `getPath()`,
`getParent()`, `getName()`, `toURL()`, itp.) jak też wykonywania prostych operacji (`list()`, `mkdir()`, itp.).
Zapis tekstowy ścieżki dostępu dla środowiska MS Windows musi zawierać podwójny separator.
`c:\\java\\projekty\\strumienie`.

File plik = new File(“c:\dane.txt”); File katalog = new File(“c:\moje dokumenty”); File plik2 = new File(katalog, “nowy plik.txt”);

import java.io.*;

public class PobierzDane{ public static void main(String args[]){ File f = new File(“DANE1”); if (f.mkdir()) { File g = new File (“.”); String s[] = g.list(); for (int i =0; i<s.length; i++){ System.out.println(s[i]); } } else { System.out.println(“Błąd operacji I/O”); } } }

Znakowe strumienie plikowe

a) Służą do przesyłania znaków w standardzie Unicode o 16-bitowych kodach,

b) Mogą odczytywać (klasa FileReader) lub zapisywać (klasa FileWriter) pojedyncze znaki,

c) Często wewnętrznie obsługują przekodowanie znaków związane z niestandardowymi alfabetami.

<br>![cmd_gcc](/Lab12/images/picture1.png)

**Odczyt z pliku tekstowego:**

Strumień `FileReader` odczytuje pojedyncze znaki ze strumienia znakowego otwartego pliku.
Wybrane metody:

int read() – odczyt jednego znaku (liczby int) int read(char[] blok) – odczyt tablicy znaków void close() – zamknięcie strumienia FileReader plik = new FileReader(“plikwe.txt”); char znak; do { znak = (char) plik.read(); } while (znak != -1); plik.close();




Wybrane metody

void write(int znak) – zapis jednego znaku void write(char[] blok) – zapis tablicy znaków void write(String napis) – zapis napisu void append(char znak) – dołączenie znaku na końcu pliku void flush() – przesuwanie buforowanych danych do strumienia void close() – zamknięcie strumienia FileWriter plik = new FileWriter (“plik.wy”); //utworz. plik.write(‘A’); // zapis do strumienia plik.close(); // zamknięcie strumienia

Strumienie buforujące:
Bezpośredni odczyt i zapis pojedynczych znaków jest nieefektywny:
przykład:odczytaj 1 000 000 znaków za pomocą pętli
W takiej sytuacji wykorzystuje się strumienie buforujące, operujące na innych, prostszych
strumieniach. Posiadają one możliwość buforowania danych, tak aby można było przetwarzać
większe ich informacji.

<br>![cmd_gcc](/Lab12/images/picture2.png)

public class Kopiowanie { public static void main(String[] args) { try { FileReader we = new FileReader(“C:/plik1.txt”); BufferedReader buforWe = new BufferedReader(we); FileWriter wy = new FileWriter(“C:/plik2.txt”); BufferedWriter buforWy = new BufferedWriter(wy); String linia; while ((linia = buforWe.readLine()) != null) { buforWy.write(linia); } buforWe.close(); buforWy.close(); } catch (IOException ex) { System.err.println(“Błąd: “ + e); } } }


Binarne strumienie plikowe
Binarne strumienie służą do odczytu (klasa FileInputStream) i zapisu (klasa FileOutputStream)
danych binarnych. Podstawową jednostką zapisu jest bajt (8 bitów).
W strumieniach binarnych, inaczej niż w tekstowych, nigdy nie dochodzi do przekodowania
danych. Są one zapisywane dokładnie w takiej postaci, w jakiej je przekazano.

FileInputStream in = new FileInputStream(“plik.we”); FileOutputStream out = new FileOutputStream(“plik.wy”);

Odczyt z pliku binarnego:
Strumień FileInputStream odczytuje ze strumienia binarnego otwartego w pliku pojedyncze bajty
Wybrane metody:

int read() – odczyt jednego znaku (liczby int) int read(byte[] blok) – odczyt tablicy bajtów void close() – zamknięcie strumienia FileInputStream plik = new FileInputStream(“plikwe.dat”); int bajt; do { bajt = plik.read(); } while (bajt != -1); // odczyt dopóki nie wystąpi koniec pliku plik.close(); // zamknięcie strumienia public class OdczytBinarny { public static void main(String[] args) { try { FileInputStream we = new FileInputStream(“x”); byte bajt; while ((bajt = we.read()) != -1 { // … } strumienWe.close(); } catch (IOException ex) { System.err.println(“Błąd: “ + e); } } }

Zapis do pliku binarnego:
Strumień `FileOutputStream` zapisuje do strumienia binarnego otwartego w pliku pojedyncze bajty
Wybrane metody:

void write(int znak) – zapis jednego bajtu void write(byte[] blok) – zapis tablicy bajtów void flush() – przesuwanie buforowanych danych do strumienia void close() – zamknięcie strumienia FileWriter plik = new FileWriter (“plikwy.dat”); plik.write(‘A’); // zapisany zostaje kod znaku ‘A’ plik.close(); public class ZapisBinarny { public static void main(String[] args) { try { FileOuputStream wy = new FileOutputStream(“plik.wy”); for (int i = 0; i < 255; i++) { strumienWy.write(i); } strumienWy.close(); } catch (IOException ex) { System.err.println(“Błąd: “ + e); } } }

Buforowanie dostępu do pliku binarnego:
Podobnie jak w przypadku strumieni znakowych, strumienie binarne też mogą być buforowane.
Odpowiadają za to klasy BufferedInputStream i BufferedOutputStream.
Zapis buforowany `(BufferedOutputStream)`

int write(byte[] bufor, int poczatek, int długosc)

Odczyt buforowany (BufferedInputStream)

int read(byte[] bufor, int poczatek, int długosc)

<br>![cmd_gcc](/Lab12/images/picture3.png)

public class BuforOdczytBinarny { public static void main(String[] args) { try { FileInputStream we = new FileOutputStream(“pl.wy”); BufferedInputStream buforWe = new BufferedInputStream(we); char[] bufor = new char[1024]; while ((int liczba = buforWe.read(bufor, 0, bufor.length)) != -1) { // tu można coś zrobić z zawartością bufora } buforWe.close(); } catch (IOException ex) { System.err.println(“Błąd: “ + e); } } } while ((int liczba = strumien.read(bufor, 0, bufor.length) ) != -1)

liczba faktycznie odczytanych bajtów
bufor, jego początek i długość
-1 oznacza koniec pliku
Niemal wszystkie operacje wejścia-wyjścia zgłaszają wyjątek `IOException`, dlatego wykonanie
większości z nich wymaga objęcia w klauzulę try...catch
Strumienie poza `java.io`
W Javie istnieją również inne strumienie zdefiniowane poza pakietem `java.io`. Na przykład w
pakiecie `java.util.zip` zdefiniowano szereg klas strumieni do obsługi kompresji w formie ZIP i GZIP.
Do zawartych tam podstawowych klasy strumieni zalicza się:
- CheckedInputStream
- CheckedOutputStream
- DeflaterOutputStream
- GZIPInputStream
- GZIPOutputStream
- InflaterInputStream
- ZipInputStream
- ZipOutputStream

Przykładowy kod realizujący kompresję pliku metodą GZIP:

import java.io.; import java.util.zip.; public class Kompresja { public static void main(String args[]){ String s; byte b[] = new byte[100]; for (int i=0; i<100; i++){ b[i]=(byte) (i/10); } try{ FileOutputStream o = new FileOutputStream(“plik2.txt”); o.write(b); o.flush(); o.close(); FileOutputStream fos = new FileOutputStream(“plik2.gz”); GZIPOutputStream z = new GZIPOutputStream(new BufferedOutputStream(fos)); z.write(b,0,b.length); z.close(); } catch (Exception e){} } } ``` W programie tworzona jest tablica bajtów wypełniana kolejnymi wartościami od 1 do 10. Następnie tablica ta przesyłana do strumieni wyjściowych raz bezpośrednio do pliku, drugi raz poprzez kompresję metodą GZIP. W wyniku działania programu uzyskuje się dwa pliki: bez kompresji plik2.txt i z kompresją plik2.gz.

Zadania:

Zadanie 1.

Utwórz program umożliwiający odczytanie pliku z rozszerzeniem class. Wypisz 20 pierwszych bajtów na konsolę z zapisie HEX.

Zadanie 2.

Utwórz aplikację, czytającą dowolny plik tekstowy (linia po linii) i wypisującą zawartość tego pliku na konsoli.

Zadanie 3.

Zmodyfikuj poprzedni program, w taki sposób aby pytał o nazwę pliku do wypisania oraz czy wypisywać kolejny plik. Obsłuż mogące się pojawiać wyjątki.

Zadanie 4.

Rozszerz klasę KompresjaGZIP z wykładu o kod potwierdzający, czy rzeczywiście kompresja jest odwracalna. Do weryfikacji użyj sumy kontrolnej CRC32.

Zadanie 5.

Utwórz program kopiujący znak po znaku pliki tekstowe. Za kopiowanie powinna odpowiadać wydzielona metoda echo(Reader, Writer). Nie obsługuj żadnych wyjątków, ale pamiętaj o właściwym zamykaniu obu plików.

Zadanie 6.

Rozszerz poprzednie zadanie tak, aby czas kopiowania był mierzony i wypisywany na konsolę. Sprawdź jaki wpływ na efektywność ma użycie buforowania.