1. Strumienie danych

Każdy program rzadko istnieje samodzielnie. Zwykle w jakiś sposób wchodzi w interakcje ze „światem zewnętrznym”. Może to być odczytywanie danych z klawiatury, wysyłanie wiadomości, pobieranie stron z Internetu lub odwrotnie, przesyłanie plików na zdalny serwer.

Wszystko to możemy nazwać jednym słowem – proces wymiany danych między programem a światem zewnętrznym. Nie jest to jednak jedno słowo.

Sam proces wymiany danych można podzielić na dwa typy: odbieranie danych i wysyłanie danych. Na przykład odczytujesz dane z klawiatury za pomocą obiektu Scanner- to jest odbieranie danych. I wyświetl dane na ekranie za pomocą polecenia System.out.println()- to jest wysyłanie danych.

Termin wątek jest używany do opisania procesu wymiany danych w programowaniu. Skąd taka nazwa?

W prawdziwym życiu może to być strumień wody lub strumień ludzi (strumień ludzki). W programowaniu przepływ odnosi się do przepływu danych.

Nici to wszechstronne narzędzie. Pozwalają programowi odbierać dane z dowolnego miejsca (strumienie przychodzące) i wysyłać dane w dowolne miejsce (strumienie wychodzące). Dzielą się one na dwa rodzaje:

  • Strumień przychodzący (wejście): używany do odbierania danych
  • Strumień wychodzący (Output): używany do wysyłania danych

Aby wątki były „dotykalne”, programiści Java napisali dwie klasy: InputStreami OutputStream.

Klasa InputStreamposiada metodę read(), która pozwala na odczytywanie z niej danych. A klasa OutputStreamma metodę write(), która pozwala zapisywać do niej dane. Mają inne metody, ale o tym później.

Strumienie bajtów

Co to za dane iw jakiej formie można je odczytać? Innymi słowy, jakie typy danych są obsługiwane przez te klasy?

Och, to są klasy ogólne i jako takie obsługują najpopularniejszy typ danych, byte. OutputStreamBajty (i tablice bajtów) mogą być zapisywane do , a InputStreambajty (lub tablice bajtów) mogą być odczytywane z obiektu. Wszystkie — nie obsługują żadnych innych typów danych.

Dlatego takie strumienie są również nazywane strumieniami bajtów .

Cechą strumieni jest to, że dane z nich można odczytywać (zapisywać) tylko sekwencyjnie. Nie można odczytać danych ze środka strumienia bez odczytania wszystkich danych przed nim.

Oto jak czytanie z klawiatury działa w całej klasie Scanner: odczytujesz dane z klawiatury sekwencyjnie: linia po linii. Przeczytaj wiersz, przeczytaj następny wiersz, przeczytaj następny wiersz i tak dalej. Dlatego nazywa się metodę czytania linii nextLine()(dosłownie - „następna linia”).

Zapisywanie danych do strumienia OutputStreamrównież odbywa się sekwencyjnie. Dobrym przykładem jest wyjście ekranowe. Wyprowadzasz linię, a następnie kolejną i kolejną. To jest wyjście szeregowe. Nie możesz wypisać pierwszej linii, potem dziesiątej, a potem drugiej. Wszystkie dane są zapisywane w strumieniu wyjściowym tylko sekwencyjnie.

Strumienie znaków

Niedawno dowiedziałeś się, że łańcuchy są drugim najpopularniejszym typem danych i rzeczywiście tak jest. Wiele informacji przekazywanych jest w postaci znaków i całych linii. Komputer byłby świetny w przesyłaniu wszystkiego jako bajtów, ale ludzie nie są tak doskonali.

Programiści Javy wzięli to pod uwagę i napisali jeszcze dwie klasy: Readeri Writer. Klasa Readerjest odpowiednikiem klasy InputStream, tylko jej metoda read()odczytuje nie bajty, a znaki - char. Klasa Writerodpowiada klasie OutputStreami podobnie jak klasa Reader, działa ze znakami ( char), a nie bajtami.

Jeśli porównamy te cztery klasy, otrzymamy następujący obraz:

Bajty (bajty) Symbole (znaki)
Odczytywanie danych
InputStream
Reader
Rejestracja danych
OutputStream
Writer

Praktyczne użycie

InputStreamKlasy , OutputStream, Readeri same w sobie Writernie są używane jawnie: nie są dołączone do żadnych zewnętrznych obiektów, z których można odczytywać dane (lub do których można zapisywać dane). Jednak te cztery klasy mają wiele następców, które mogą wiele zdziałać.


2. KlasaInputStream

Klasa InputStreamjest interesująca, ponieważ jest klasą nadrzędną dla setek klas podrzędnych. Sam w sobie nie ma żadnych danych, ale ma metody, które mają wszystkie jego klasy pochodne.

Obiekty strumieniowe na ogół rzadko przechowują dane same w sobie. Strumień jest narzędziem do odczytu/zapisu danych, ale nie do przechowywania. Chociaż są wyjątki.

Metody klasy InputStreami wszystkich jej klas potomnych:

Metody Opis
int read()
Odczytuje jeden bajt ze strumienia
int read(byte[] buffer)
Odczytuje tablicę bajtów ze strumienia
byte[] readAllBytes()
Odczytuje wszystkie bajty ze strumienia
long skip(long n)
Przekazuje nbajt w strumieniu (odczytuje i odrzuca)
int available()
Sprawdza, ile bajtów pozostało w strumieniu
void close()
Zamyka strumień

Omówmy pokrótce te metody:

metodaread()

Metoda read()odczytuje jeden bajt ze strumienia i zwraca go. Możesz być zdezorientowany typem wyniku - int, ale zrobiono to, ponieważ typ intjest standardem dla wszystkich liczb całkowitych. Pierwsze trzy bajty typu intbędą miały wartość zero.

metodaread(byte[] buffer)

Jest to druga modyfikacja metody read(). Pozwala InputStreamna jednoczesny odczyt z tablicy bajtów. Tablica do przechowywania bajtów musi być przekazana jako parametr. Metoda zwraca liczbę — liczbę faktycznie odczytanych bajtów.

Załóżmy, że masz 10-kilobajtowy bufor i odczytujesz dane z pliku przy użyciu rozszerzenia FileInputStream. Jeśli plik zawiera tylko 2 kilobajty, wszystkie dane zostaną umieszczone w tablicy buforów, a metoda zwróci liczbę 2048 (2 kilobajty).

metodareadAllBytes()

Bardzo dobra metoda. Po prostu odczytuje wszystkie dane od InputStreammomentu ich wyczerpania i zwraca je jako tablicę jednobajtową. Bardzo przydatny do czytania małych plików. Duże pliki mogą fizycznie nie mieścić się w pamięci, a metoda zgłosi wyjątek.

metodaskip(long n)

Ta metoda pozwala pominąć pierwsze n bajtów z pliku InputStream. Ponieważ dane są odczytywane ściśle sekwencyjnie, ta metoda po prostu odczytuje pierwsze n bajtów ze strumienia i odrzuca je.

Zwraca liczbę bajtów, które faktycznie zostały pominięte (jeśli strumień zakończył się, zanim nbajt został przewinięty).

metodaint available()

Metoda zwraca liczbę bajtów, które pozostały jeszcze w strumieniu

metodavoid close()

Metoda close()zamyka strumień danych i zwalnia powiązane z nim zasoby zewnętrzne. Po zamknięciu strumienia nie można już z niego odczytać żadnych danych.

Napiszmy przykładowy program, który kopiuje bardzo duży plik. Nie można go wczytać w całości do pamięci za pomocą metody readAllBytes(). Przykład:

Kod Notatka
String src = "c:\\projects\\log.txt";
String dest = "c:\\projects\\copy.txt";

try(FileInputStream input = new FileInputStream(src);
FileOutputStream output = new FileOutputStream(dest))
{
   byte[] buffer = new byte[65536]; // 64Kb
   while (input.available() > 0)
   {
      int real = input.read(buffer);
      output.write(buffer, 0, real);
   }
}



InputStreamdo odczytu z pliku
OutputStreamdo zapisu do pliku

Bufor do którego odczytamy dane
Dopóki w strumieniu są dane

Wczytaj dane do bufora
Zapisz dane z bufora do drugiego strumienia

W tym przykładzie użyliśmy dwóch klas: FileInputStream- dziedziczącej InputStreamdo odczytu danych z pliku oraz klasy FileOutputStream- dziedziczącej OutputStreamdo zapisu danych do pliku. O drugiej klasie porozmawiamy nieco później.

Innym interesującym punktem jest zmienna real. Kiedy ostatni blok danych zostanie odczytany z pliku, łatwo może się okazać, że jego długość jest mniejsza niż 64Kb. Dlatego nie cały bufor musi być również zapisywany na wyjściu, ale tylko jego część: pierwsze realbajty. To jest dokładnie to, co jest zrobione w metodzie write().



3. KlasaReader

Klasa Readerjest kompletnym odpowiednikiem klasy InputStream, z jedną tylko różnicą: działa ze znakami - chara nie z bajtami. Klasa Reader, podobnie jak sama klasa InputStream, nie jest nigdzie używana: jest klasą nadrzędną dla setek klas potomnych i ustala dla nich wspólne metody.

Metody klasy Reader(i wszystkie jej klasy potomne):

Metody Opis
int read()
Odczytuje jeden charze strumienia
int read(char[] buffer)
Odczytuje tablicę char's ze strumienia
long skip(long n)
Pomija n charelementy w strumieniu (czyta i odrzuca)
boolean ready()
Sprawdza, czy w strumieniu nadal coś zostało
void close()
Zamyka strumień

Metody są bardzo podobne do metod klasowych InputStream, chociaż istnieją niewielkie różnice.

metodaint read()

Ta metoda odczytuje ze strumienia pierwszego chari zwraca go. Typ charrozwija się do type int, ale pierwsze dwa bajty wyniku są zawsze równe zero.

metodaint read(char[] buffer)

Jest to druga modyfikacja metody read(). Pozwala czytać z Readertablicy znaków jednocześnie. Tablica znaków musi być przekazana jako parametr. Metoda zwraca liczbę — liczbę faktycznie odczytanych znaków.

metodaskip(long n)

Ta metoda pozwala pominąć npierwsze znaki z pliku Reader. Działa dokładnie tak samo jak podobna metoda klasy InputStream. Zwraca liczbę znaków, które faktycznie zostały pominięte.

metodaboolean ready()

Zwraca, truejeśli w strumieniu znajdują się bajty, które nie zostały jeszcze odczytane.

metodavoid close()

Metoda close()zamyka strumień danych i zwalnia powiązane z nim zasoby zewnętrzne. Po zamknięciu strumienia nie można już z niego odczytać żadnych danych.

Napiszmy program, który kopiuje plik tekstowy do porównania:

Kod Notatka
String src = "c:\\projects\\log.txt";
String dest = "c:\\projects\\copy.txt";

try(FileReader reader = new FileReader(src);
FileWriter writer = new FileWriter(dest))
{
   char[] buffer = new char[65536]; // 64Kb
   while (reader.ready())
   {
      int real = reader.read(buffer);
      writer.write(buffer, 0, real);
   }
}



Readerdo odczytu z pliku
Writerdo zapisu do pliku

Bufor do którego odczytamy dane
Dopóki w strumieniu są dane

Wczytaj dane do bufora
Zapisz dane z bufora do drugiego strumienia