– Cześć, Amigo! Dzisiaj zapoznamy się ze strumieniami wejściowymi/wyjściowymi. Poruszyliśmy ten temat kilka dni temu, ale dziś dokładnie go zbadamy. Strumienie wejściowe/wyjściowe podzielone są na 4 kategorie:

1) Zgodnie z ich kierunkiem: strumienie wejściowe i strumienie wyjściowe

2) Według typu przesyłanych danych: te, które operują na bajtach i te, które operują na znakach.

Te podziały przedstawiono w poniższej tabeli:

Strumień wejściowy Strumień wyjściowy
Operuje na bajtach InputStream OutputStream
Operuje na znakach Reader Writer

Jeśli obiekt implementuje interfejs InputStream, wówczas umożliwia on sekwencyjny odczyt bajtów.

Jeśli obiekt implementuje interfejs OutputStream, wówczas umożliwia on sekwencyjny zapis bajtów.

Jeśli obiekt implementuje interfejs Reader, wówczas umożliwia on sekwencyjny odczyt znaków (chars).

Jeśli obiekt implementuje interfejs Writer, wówczas umożliwia on sekwencyjny zapis znaków (chars).

Strumienie wejściowe/wyjściowe - 1

Strumień wyjściowy jest jak drukarka. Tak samo jak możliwe jest wysyłanie dokumentów do drukarki, możemy też wysyłać dane do strumienia wyjściowego.

Z kolei strumień wejściowy może być porównany do skanera czy też elektrycznego gniazdka. Za pomocą skanera możemy przenieść dokumenty do naszego komputera. Możemy również podłączyć się do gniazdka elektrycznego i odbierać z niego energię elektryczną. Ze strumienia wejściowego możemy odbierać dane.

– Gdzie są one wykorzystywane?

– Te klasy używane są wszędzie w Javie. Nasz znajomy System.in jest statyczną zmienną InputStream o nazwie in w klasie System.

– Poważnie?! Więc przez cały ten czas używałem InputStream i nawet nie zdawałem sobie z tego sprawy. Czy System.out także jest strumieniem?

– Tak, System.out jest statyczną zmienną PrintStream (elementu podrzędnego OutputStream) w klasie System.

– Chcesz mi powiedzieć, że zawsze korzystałem ze strumieni i nawet o tym nie wiedziałem?

– Tak i to pokazuje nam, jak wygodne w użyciu są te strumienie. Po prostu bierzesz jeden i go stosujesz.

– Ale nie możesz tego powiedzieć o System.in. Musieliśmy ciągle dodawać do niego BufferedReader lub InputStreamReader.

– To prawda. Jednak były ku temu powody.

Jest wiele typów danych i wiele sposobów na pracę z nimi. Tak więc liczba standardowych klas I/O rosła bardzo szybko, choć robiły one wszystko prawie w ten sam sposób. Aby zapobiec tej komplikacji, twórcy Javy zastosowali zasadę abstrakcji i podzielili klasy na wiele małych części.

Możesz, jeśli tego potrzebujesz, połączyć te części w spójny sposób i uzyskać bardzo złożoną funkcjonalność. Spójrz na ten przykład:

Wypisz w konsoli ciąg.
System.out.println("Cześć");
Przechowaj strumień wyjściowy konsoli w osobnej zmiennej.
Wypisz ciąg do strumienia.
PrintStream console = System.out;
console.println("Cześć");
Utwórz w pamięci dynamiczną (powiększającą się) tablicę bajtów.
Połącz ją z nowym strumieniem wyjściowym (obiektem PrintStream).
Wypisz ciąg do strumienia.
ByteArrayOutputStream stream = new ByteArrayOutputStream();
PrintStream console = new PrintStream(stream);
console.println("Cześć");

– Szczerze mówiąc, to jest jak zestaw klocków Lego. Tylko, że nie jest dla mnie jasne, co robią poszczególne fragmenty kodu.

– Na razie się tym nie przejmuj. Wszystko w swoim czasie.

Oto, co chcę, żebyś zapamiętał: Jeśli klasa implementuje interfejs OutputStream, możesz zapisać do niego bajty. Prawie tak samo jak przy wysyłaniu danych do konsoli. To, co z nimi robi, to jego sprawa. Przy pracy z naszym „zestawem Lego” nie obchodzi nas przeznaczenie poszczególnych części. Natomiast zależy nam na tym, aby duży wybór części pozwalał nam budować różne fajne rzeczy.

– Dobrze. Więc od czego zaczynamy?