LINUXSOFT.cz Přeskoč levou lištu
Uživatel: Heslo:  
   CZUKPL

> Java (15) - I/O operace I.

Jednou z klíčových činností v programech je výměna dat s okolním světem. V Javě to jde velice snadno a elegantně.

1.6.2005 15:00 | Lukáš Jelínek | Články autora | přečteno 45874×

Základní informace o streamech

Vstupně/výstupní operace lze v Javě realizovat několika způsoby. My se nyní zaměříme na ten základní způsob, tedy práci se streamy. Téměř každý, kdo programuje v nějakém jazyce, bude pojem stream znát. A právě v Javě se tomuto slovu dostává plného významu.

Stream si lze představit jako trubku, jejíž konec máme k dispozici a můžeme "čerpat" data z něho (tedy číst) nebo naopak do něho (tj. zapisovat). Streamů existuje (z hlediska implementace) celá řada, z pohledu uživatele se však všechny streamy daného směru (tedy "dovnitř" nebo "ven") chovají skoro stejně - většinu funkcionality mají společnou. Pro streamy je charakteristická především jejich sekvenčnost. I když to neplatí úplně stoprocentně (existují streamy se "zpětným chodem"), s daty se pracuje v konkrétním neměnném pořadí.

Streamy v Javě

V Javě je každý stream reprezentován jako objekt, tedy instance nějaké třídy. Balík java.io obsahuje hierarchii základních tříd, se kterými si pro běžné operace vystačíme (další streamy najdeme i v jiných standardních balících). Pokud budeme potřebovat něco speciálního, není žádný problém si vhodnou třídu vytvořit (resp. odvodit od nějaké existující).

Známe dvě hlavní kategorie streamů: binární a textové. Liší se způsobem práce se znaky, binární streamy pracují se "surovými" bajty (tak, jak jsou), zatímco streamy textové pojímají bajty, resp. skupiny bajtů způsobem, který odpovídá nastavení prostředí. Binární vstupní streamy jsou odvozeny od abstraktní třídy InputStream, výstupní od třídy OutputStream. Textové pak od třídy Reader, resp. Writer.

Základy práce se streamy

Každý stream má svůj životní cyklus - je velmi jednoduchý a sestává z těchto etap:

  1. Vytvoření (zavolání konstruktoru) - může se vytvářet přímo tam, kde se používá, anebo ho může vytvořit nějaký jiný objekt a předat.
  2. Otevření - stream se často otvírá už při vytváření, pokud však otevřen nebyl, musí se otevřít později, jinak s ním nelze pracovat. Otevření znamená, že se alokují potřebné systémové prostředky a stream se připraví pro práci.
  3. Vlastní práce - se streamem se provádějí požadované operace, tzn. volají se jeho metody.
  4. Uzavření - toto je velmi důležité, bohužel se na to často zapomíná. I když korektně finalizovaný objekt streamu zaručuje správné zavření (u standardních streamů), podobně i normální ukončení programu, nikdy se na to nesmí spoléhat. Jednak se vyčerpávají systémové prostředky (file deskriptory apod.), a za druhé může mnoho dat zůstat nezapsaných (u výstupních streamů), a to i hodně dlouho. Streamy se prostě musí uzavírat, a to ihned, když s nimi přestáváme pracovat (s jednou výjimkou, kterou za chvíli zmíním).

Ošetření chyb

Důležitým aspektem práce se streamy je ošetření chyb, které se mohou vyskytnout. Téměř všechny chybové stavy jsou řešeny výjimkami. Tou základní je IOException, kterou mohou vyhodit naprostá většina streamových metod. Tato výjimka je synchronní (musí se tedy povinně zachytávat nebo deklarovat k předání výše), zahrnuje do sebe celou škálu podtříd (výjimek signalizujících konkrétní chybové stavy) a je společná všem streamům bez ohledu na implementaci. Kromě IOException mohou streamy produkovat i jiné výjimky, to ale závisí na jejich určení a implementaci.

Jak jsem již uvedl, výjimku IOException může vyhodit prakticky kterákoli metoda streamu. Musíme to mít na zřeteli, ani takové uzavírání streamu není "bezpečná" operace. Když se na to zapomene, připomene to rázně kompilátor.

Základní druhy streamů

Se streamy pracujeme prakticky stejně, ať už se jedná o data v souborech na disku, o síťovou komunikaci, komunikaci mezi vlákny apod. I když je tato práce téměř shodná, existují podstatné rozdíly zejména v přípravě streamů před komunikací. Podíváme se tedy blíže na jednotlivé druhy streamů.

Souborové streamy

Asi nejčastějším způsobem komunikace s vnějším prostředím je čtení a zápis souborů. Z hlediska Javy se nerozlišují vlastnosti souborového systému, se soubory se pracuje vždy stejně, jen nás zajímá, zda soubor existuje, lze z něj číst nebo do něj zapisovat.

try {
  InputStream is = new FileInputStream("soubor.dat"); // stream se hned otevře
  int i = 0;
  while ((i = is.read()) >= 0) {      // čte se, dokud není konec souboru
      ...
  }
  is.close();   // zavření souboru
} catch (IOException e) {
    ...         // zpracování výjimky
}

Příklad ukazuje základní způsob čtení ze streamu, v tomto případě souborového. Stream se otevře, v cyklu se z něho čte po bajtech (pozor - i když se čtou bajty, hodnota je typu int; je to z více důvodů, ale důležité je, že pokud je přečtena hodnota -1, bylo dosaženo konce souboru). Všimněte si, že všechny operace jsou uzavřeny do bloku try k zachycení výjimek.

Řetězcové streamy

Podobně jako třeba v C++, i v Javě lze snadno číst z řetězce a zapisovat do něj streamovým způsobem. Stream pracuje nad objektem typu StringBuffer, ke kterému můžeme získat přímý přístup - lze ale také ze streamu "vytáhnout" také instanci třídy String, ta se ale samozřejmě musí vždy vytvořit, neboť je neměnná.

Následující příklad ukazuje, jak se streamově zapisuje do řetězce. Stream je samozřejmě textově orientovaný, což je naprosto v souladu s daným účelem. Pro výstupní řetězcové streamy je charakteristické, že operace nevyhazují výjimku IOException - a to ani při pokusu dělat nějaké operace po uzavření streamu (operace uzavření totiž nic nedělá).

StringWriter sw = new StringWriter();
sw.write("abcd");
sw.write(sw.toString());   // obsahu streamu se zapíše zpět do streamu
System.out.println(sw);

Copak asi uvedený příklad dělá? Zapíše uvedený řetězec dvakrát za sebou (nejprve přímo, potom prostřednictvím metody toString() zavolané na streamu) a celý obsah vypíše na standardní výstup. Zbývá si ještě zodpovědět jednoduchou otázku, k čemu je to vlastně dobré - samozřejmě hlavně k tomu, že vytvořený stream můžeme předat k nějakému vnějšímu použití, kde nezáleží na tom, kam se data zapisují (resp. odkud se čtou).

Mezi základní streamy patří ještě některé další druhy, ale o těch si řekneme až později. Nejdřív by se totiž hodilo znát něco jiného...

Filtrové streamy

Největší množství streamů patří do obrovské množiny, které se říká filtrové streamy. Takový stream si lze představit skutečně jako nějaký kus trubky s filtrem. Nejobecněji to vypadá tak, že tento stream napasujeme na nějaký jiný stream. Data, která přes filtrový stream procházejí, mohou být různě pozměněna, stream je může všelijak zkoumat a něco počítat atd. Filtrové streamy lze prakticky libovolně řetězit za sebe (pokud na sebe navazují stejné kategorie, ve smyslu binární a textové).

Filtrových streamů je celá řada, řekneme si tedy nejprve o těch nejdůležitějších.

Bufferované streamy

Protože často pracujeme s malými objemy dat, nebývají vstupně/výstupní operace příliš operačně výkonné. Záleží na prostředcích operačního systému, druhu streamu atd., a většinou nemůžeme na nic spoléhat (píšeme platformově nezávislé programy!). Proto existují streamy, které obsahují vlastní buffer a optimalizují přístupy k datům.

Představme si, že zapisujeme data třeba po jednom bajtu - to by za normálních okolností mohlo znamenat třeba mnoho zbytečných přístupů na disk, posílání "prázdných" paketů po síti apod. Přitom obvykle není žádný důvod, aby se data okamžitě sunula někam dál. Pro optimalizaci tedy použijeme bufferovaný stream.

try {
    BufferedReader br = new BufferedReader(new FileReader("soubor.txt"));
    String s = "";
    while ((s = br.readLine()) != null) {
        ...
    }
    br.close();
} catch (IOException e) {
    ...
}

Příklad ukazuje hned několik aspektů práce s bufferovaným (textovým) streamem. Filtrové streamy obvykle vytváříme tak, že předáme jejich konstruktoru jako parametr podřízený stream, v daném případě textový souborový.

Dále je vidět, že zde můžeme číst celé řádky - to je věc specifická právě pro tento stream, ale i zde můžeme stále číst jednotlivé znaky, toto je jen usnadnění. Po skončení práce uzavřeme "nejvrchnější" stream, ten už zajistí kaskádovitě uzavření všech ostatních.

Ještě důležitá poznámka - při použití bufferovaných výstupních streamů není zaručeno, kdy se data z bufferu přesunou do navazujícího streamu. K zajištění zápisu dat z bufferu proto v případě potřeby voláme metodu flush().

Konverzní streamy

Název není úplně přesný, řeč bude pouze o konverzi mezi textovými a binárními streamy. V řadě případů totiž odněkud získáme binární stream, a přitom potřebujeme textový. Vřadíme tedy mezičlánek, který nám konverzi zajistí. Viz příklad:

try {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    int i = 0;
    while (i >= 0) {
        i = br.read();
        if (i >= 0) {
            char c = (char) i;
            ...
        }
    }
    br.close();
} catch (IOException e) {
    ...
}

V příkladu je použit standardní vstup (čili obvykle klávesnice), který však je binární. Protože potřebujeme znaky, musíme provést převod, a to nejlépe hned "na cestě", pomocí konverzního streamu.

Streamy pro kompresi/dekompresi dat

Jako takovou třešničku na dortu, která ukáže, co se také se streamy dá dělat, si nyní vyzkoušíme dekompresi dat a zároveň výpočet kontrolního součtu. Na samém konci budeme číst po řádcích textová data. Stačí jen streamy zřetězit za sebe...

try {
    CheckedInputStream cis = 
            new CheckedInputStream(new GZipInputStream(new FileInputStream("data.gz")));
    BufferedReader br = new BufferedReader(new InputStreamReader(cis));
    String s = "";
    while ((s = br.readLine()) != null) {
        ...
    }
    System.out.println("Kontrolni soucet je: " + cis.getValue());
    br.close();
} catch (IOException e) {
    ...
}

Jak snadné... A tohle zdaleka není všechno, co streamy dovedou. Příště se podíváme na některé další druhy streamů (těch zajímavých je ještě řada), řekneme si něco o tom, jak efektivně a bezpečně přenášet různá data, a také jak si vyrobit vlastní stream pro specifické účely.

Verze pro tisk

pridej.cz

 

DISKUZE

Streamy 1.6.2005 22:11 Petr Zajíc




Příspívat do diskuze mohou pouze registrovaní uživatelé.
> Vyhledávání software
> Vyhledávání článků

12.8.2018 16:58 /František Kučera
Srpnový pražský sraz spolku OpenAlt se koná ve čtvrtek – 16. 8. 2018 od 19:00 v Kavárně Ideál (Sázavská 30, Praha), kde máme rezervovaný salonek. Tentokrát jsou tématem srazu databáze prezentaci svého projektu si pro nás připravil Standa Dzik. Dále bude prostor, abychom probrali nápady na využití IoT a sítě The Things Network, případně další témata.
Přidat komentář

16.7.2018 1:05 /František Kučera
Červencový pražský sraz spolku OpenAlt se koná již tento čtvrtek – 19. 7. 2018 od 18:00 v Kavárně Ideál (Sázavská 30, Praha), kde máme rezervovaný salonek. Tentokrát bude přednáška na téma: automatizační nástroj Ansible, kterou si připravil Martin Vicián.
Přidat komentář

18.6.2018 0:43 /František Kučera
Červnový pražský sraz spolku OpenAlt se koná již tento čtvrtek – 21. 6. 2018 od 18:00 v Kavárně Ideál (Sázavská 30, Praha), kde máme rezervovaný salonek. Tentokrát na téma: F-Droid, aneb svobodný software do vašeho mobilu. Kromě toho budou k vidění i vývojové desky HiFive1 se svobodným/otevřeným čipem RISC-V.
Přidat komentář

23.5.2018 20:55 /Ondřej Čečák
Od pátku 25.5. proběhne na Fakultě informačních technologií ČVUT v Praze openSUSE Conference. Můžete se těšit na spostu zajímavých přednášek, workshopů a také na Release Party nového openSUSE leap 15.0. V na stejném místě proběhne v sobotu 26.5. i seminář o bezpečnosti CryptoFest.
Přidat komentář

20.5.2018 17:45 /Redakce Linuxsoft.cz
Ve čtvrtek 31. května 2018 připravuje webový magazín BusinessIT ve spolupráci s Best Online Média s.r.o. pátý ročník odborné konference Firemní informační systémy 2018. Akce proběhne v kongresovém centru Vavruška (palác Charitas), Karlovo náměstí 5, Praha 2 (u metra Karlovo náměstí) od 9:00 hod. dopoledne do cca 15 hod. odpoledne. Konference je zaměřena na efektivní využití firemních informačních systémů a na to, jak plně využít jejich potenciál. Podrobnější informace na webových stránkách konfrence.
Přidat komentář

14.5.2018 7:28 /František Kučera
Květnový pražský sraz spolku OpenAlt se koná již tento čtvrtek – 17. 5. 2018 od 18:00 v Kavárně Ideál (Sázavská 30, Praha), kde máme rezervovaný salonek. Tentokrát na téma: Audio – zvuk v GNU/Linuxu.
Přidat komentář

7.5.2018 16:20 /František Kučera
Na stránkách spolku OpenAlt vyšla fotoreportáž Pražské srazy 2017 dokumentující srazy za uplynulý rok. Květnový pražský sraz na téma audio se bude konat 17. 5. 2018 (místo a čas ještě upřesníme).
Přidat komentář

17.4.2018 0:46 /František Kučera
Dubnový pražský sraz spolku OpenAlt se koná již tento čtvrtek – 19. 4. 2018 od 18:00 v Kavárně Ideál (Sázavská 30, Praha), kde máme rezervovaný salonek. Tématem tohoto srazu bude OpenStreetMap (OSM) aneb svobodné mapy.
Přidat komentář

   Více ...   Přidat zprávičku

> Poslední diskuze

20.2.2018 18:48 / Ivan Majer
portal

20.2.2018 15:57 / Jan Havel
Jak využíváte služby cloudu v podnikání?

16.1.2018 1:08 / Ivan Pittner
verejna ip od o2 ubuntu

15.1.2018 17:26 / Mira Harvalik
Re: Jak udělat HTML/Javascript swiping gallery do mobilu?

30.12.2017 20:16 / Michal Knoll
odmocnina

Více ...

ISSN 1801-3805 | Provozovatel: Pavel Kysilka, IČ: 72868490 (2003-2018) | mail at linuxsoft dot cz | Design: www.megadesign.cz | Textová verze