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

> Java (16) - I/O operace II.

Zajímavých streamů je mnohem víc, než jsme si ukázali minule. Proto nyní dojde i na některé další. Přijde řada i na tvorbu vlastních streamů pro specifické účely.

23.6.2005 07:00 | Lukáš Jelínek | Články autora | přečteno 33522×

Další zajímavé streamy

Ve standardních knihovnách se nachází řada zajímavých streamů, na které stojí za to prozkoumat. Tedy vzhůru do toho, podívejme se na některé z nich!

PrintStream

PrintStream je výstupní stream, který nemá svůj vstupní protějšek a slouží k tisku uživatelsky srozumitelných dat různým způsobem. Může být napojen přímo na výstupní soubor nebo (protože je to filtrový stream) na libovolný jiný výstupní stream.

Pozn.: Napojení PrintStream na soubor lze používat od JDK verze 1.5, u dřívějších verzí je nutno použít přístup přes FileOutputStream.

Charakteristickou vlastností třídy PrintStream je, že nevyhazuje výjimky IOException - chyby jsou v podstatě ignorovány (lze je ale zjistit voláním metody checkError()). Proto se hodí hlavně tam, kde fungování streamu není z hlediska funkce celého programu důležité (logování, informativní výpisy apod.).

Ještě důležitější je ovšem, že PrintStream poskytuje přímou podporu převodu různých primitivních typů na textovou reprezentaci, a následně na proud bajtů. Při tomto převodu se uplatní kódová tabulka platformy nebo (pokud byl zavolán příslušný konstruktor) kódování poskytnuté vytvářené instanci streamu.

Třída samozřejmě disponuje metodami write(), které se chovají tak, jak je pro výstupní streamy obvyklé. Hlavní síla je však v metodách print() a println() - rozdíl je pouze ten, že druhá z metod navíc vloží konec řádku. Právě tyto metody provádějí výše uvedené konverze. Od verze 1.5 lze použít i volání printf() s podobným chováním, jako má stejnojmenná funkce v jazyce C (stejnou službu ale poskytne i metoda format()).

Konstruktoru lze poskytnout argument říkající, že se má provádět tzv. autoflush (automatický zápis výstupního bufferu). Pokud je tato funkce zapnuta, buffer se zapíše ihned po zavolání některé metody println(), po zápisu znaku pro odřádkování anebo po zápisu pole bajtů. Stream vytvořený přímým napojením na soubor má funkci autoflush vypnutou. Více ukáže následující příklad:

PrintStream ps = null;
try {
    ps = new PrintStream("vystup.txt");
} catch (FileNotFoundException e) {
    System.err.println("Vystupni soubor nelze otevrit");
    ps = System.out;  // použije se standardní výstup
}

ps.println("nejaky text");
ps.printf("%X", new Integer(120));  // vypíše hexadecimální číslo
...
ps.println();

ps.close();

Konstruktor je v příkladu uzavřen do bloku try - to je nezbytné kvůli výjimce FileNotFoundException, kterou konstruktor může vyhodit. Pokud k vyhození dojde (nastane problém s otevřením souboru), použije se v příkladu standardní výstupní stream. Zbylá část programu už nemusí mít (a nemá) kontrolu výjimek.

Zvláštním případem jsou dva standardní (systémové) streamy: standardní výstup (System.out) a standardní chybový výstup (System.err). Tyto streamy (v příkladu jsou použity) jsou v každém programu k dispozici a navenek (z pohledu operačního systému) se chovají úplně stejně jako jejich céčkovské obdoby. Pro úplnost uvádím, že je k dispozici i standardní vstup (System.in), na který se díváme přes rozhraní InputStream.

PipedInputStream/PipedOutputStream a PipedReader/PipedWriter

Tyto dva páry streamů představují tzv. roury (pipe), což jsou vlastně vzájemně propojené streamy. Vezmeme jeden vstupní a jeden výstupní stream, propojíme je, a s každým koncem pracujeme úplně stejně, jako by to byl normální vstupní, resp. výstupní stream. K čemu je to dobré?

Nejčastějším použitím je komunikace mezi vlákny (o vláknech samotných bude řeč někdy později), kdy se v jednom vlákně vytvářejí nějaká data a současně se ve druhém tato data zpracovávají. Je to výhodné hlavně proto, že se nemusíme starat o synchronizaci přístupu k datům, práce se streamy je velmi jednoduchá, můžeme využít veškeré možnosti nabízené streamy a při změně způsobu práce není třeba příliš do programu zasahovat.

Tyto streamy se vytvářejí tak, že vytvoříme jeden z nich a druhému ho předáme jako argument v konstruktoru. Jinou cestou je vytvořit je nezávisle a pak na některém z nich zavolat metodu connect(). Viz příklad:

// první možnost
PipedInputStream is = new PipedInputStream();
PipedOutputStream os = new PipedOutputStream(is);

// druhá možnost
PipedOutputStream os = new PipedOutputStream();
PipedInputStream is = new PipedInputStream(os);

// třetí možnost
PipedReader pr = new PipedReader();
PipedWriter pw = new PipedWriter();
pr.connect(pw);

ByteArrayInputStream/ByteArrayOutputStream

Jedná se o dvojici streamů, které pracují nad polem bajtů. Je to podobné jako u známých tříd StringReader/StringWriter (a dalších podobných, kam patří třeba StringBufferInputStream nebo CharArrayWriter). Máme nějakou oblast v paměti, kam se zapisují (resp. odkud se čtou) data streamu. S těmito daty pak můžeme naložit dle libosti.

Výstupní stream funguje tak, že si spravuje vlastní buffer, a dokud se tento nevymaže, stále se zapisováním plní. Pokud potřebujeme jeho obsah, získáme kopii dat (ne tedy přístup k původnímu bufferu) zavoláním toByteArray. To je třeba si dobře uvědomit kvůli výkonnostním úvahám! Data lze získat i ve formě textového řetězce (voláním toString()).

Vstupní stream naopak pracuje vždy s bufferem pevné velikosti. Přečíst lze jen tolik bajtů, kolik jich v bufferu je.

Serializace/deserializace dat

Velice často máme nějak uložená data (v primitivních nebo složitějších datových typech) a potřebujeme je uložit nebo přenést na jiné místo. Musíme to udělat tak, aby se v jiném čase nebo na jiném místě data správně zrekonstruovala do původní podoby. Těmto činnostem říkáme serializace a deserializace.

Serializace je konverze obecných dat (nějakým způsobem uložených) na proud bajtů tak, aby je šlo následně snadno zrekonstruovat. Naopak deserializace je právě rekonstrukce proudu bajtů na data použitelná v programu. Java k těmto činnostem poskytuje výraznou podporu.

Serializace/deserializace primitivních typů

Celý mechanismus okolo serializace/deserializace je docela složitý, proto bych se nyní chtěl zaměřit jen na to, co je důležité pro základní práci. Protože v Javě nikdy nevíme, jak jsou jednotlivé datové typy uloženy (i když třeba známe jejich číselné rozsahy), nelze jednoduše rozsekat třeba long na 8 bajtů (většinou by to sice šlo, ale ztrácíme tím plnou přenositelnost - obecně se totiž může stát, že "předpoklady" nejsou zcela naplněny), natož něco kopírovat rovnou (pořadí bajtů!). Naštěstí se zrovna o toto nemusíme starat.

Máme totiž dvě třídy, DataInputStream a DataOutputStream, které potřebné konverze bezpečně udělají za nás. Streamy mají metody pro uložení/načtení všech primitivních datových typů. Pozor samozřejmě na to, v jakém pořadí se data ukládají. Tento způsob serializace neumožňuje jednotlivé typy zpětně identifikovat! Příklad naznačí, jak se s uvedenými třídami pracuje:

int i = 165;
float f = 0.35;

try {
    DataOutputStream os = new DataOutputStream(new FileOutputStream("soubor.dat"));
    os.writeInt(i);    // bezpečné uložení hodnoty typu int
    os.writeFloat(f);  // bezpečné uložení hodnoty typu float
    os.close();
} catch (IOException e) {
    ...
}

Serializace/deserializace objektů

Trochu složitější je to s instancemi objektů. Ale i tady máme podobné prostředky - v podobě tříd ObjectInputStream a ObjectOutputStream. Ty nejenže ukládají a načítají instance objektů, ale poradí si i s primitivními typy (takže pokud je používáme, nemusíme už používat třídy DataInputStream/DataOutputStream).

Nelze ukládat všechny objekty. Nutnou podmínkou je, aby implementovaly rozhraní Serializable (pokud se pokusíme serializovat nevyhovující objekt, dočkáme se výjimky NotSerializableException). Protože se instance serializuje i se všemi odkazovanými objekty, musí být i tyto serializovatelné, anebo označené modifikátorem transient (tedy že nebudou uloženy).

Narozdíl od primitivních typů, u objektů lze při deserializaci zjistit jejich typ (a nejen to, k úspěšné deserializaci musí být k dispozici příslušná třída - jinak to skončí výjimkou ClassNotFoundException; případné poškození dat vyvolá zase jiné výjimky). Metoda readObject() sice vrací referenci na typ Object, ale třídu si můžeme zjistit voláním getClass() na vrácené instanci nebo jiným způsobem, a následně přetypovat podle potřeby. Více opět napoví příklad:

ArrayList list = new ArrayList();  // vytvoříme seznam
list.add("nejaky text");           // vložíme hodnoty
list.add(new Double(1.655));
list.add(new Integer(123));

try {
    ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("seznam.dat"));
    os.writeObject(list); // celý seznam se bezpečně uloží
    os.close();
} catch (IOException e) {
    ...
}

Zde je dobře vidět, že můžeme snadno uložit nebo přenést celý kontejner i s obsahem. Jen pozor na to, že všechny obsažené objekty musí být serializovatelné. K procesu serializace se ještě někdy později vrátíme a podíváme se na něj podrobněji - toto by jako úvod stačilo.

Vytváření vlastních streamů

Někdy potřebujeme stream, který má nějaké speciální vlastnosti. Proto si můžeme (pokud nám žádný z dostupných streamů nevyhovuje) vytvořit vlastní stream, do kterého přidáme potřebné funkce. Nejlepší je rozšířit nějaký už existující stream.

Ukážeme si to na streamu filtrového typu. Požadujeme, aby stream sledoval četnost jednotlivých bajtů (tedy hodnoty 0-255). Nový stream odvodíme od třídy FilterInputStream předefinováním potřebných metod. Mohlo by to vypadat třeba takto:

public class CounterInputStream extends FilterInputStream {
    private long cnt [] = new long[256];  // pole pro uložení četností
    
    public FilterInputStream(InputStream in) {
        super(in); // konstruktor pouze zavolá předka
    }
    
    // Metoda resetuje počitadla četností
     public void resetCounters() {
        for (int i=0; i<256; i++) {
            cnt[i] = 0;
        }
    }
    
    // Vrací četnost daného bajtu.
    // Meze indexu se netestují.
    public long getCount(int index) {
        return cnt[index];
    }
    
    // Základní metoda - přečtení bajtu
    public int read() throws IOException {
        int b = super.read(); // přečte se bajt
        if (b >= 0) cnt[b]++;  // pokud je platný, inkrementuje se počitadlo
        return b;
    }
    
    // Metoda pro čtení bloku bajtů
    public int read(byte[] b, int off, int len) throws IOException {
        int r = super.read(b, off, len);
        if (r > 0) {
            for (int i=0; i<r; i++) {
                cnt[b[i]]++; // není třeba testovat platnost
            }
        }
        return r;	
    }
}

Z metod read() předka předefinováváme pouze ty dvě uvedené, třetí může zůstat v původní podobě (volá totiž jednu z těch zbývajících). Uvedená implementace má jednu zásadní vlastnost, a sice tu, že při návratu na označenou pozici (metodou reset() - pokud to samozřejmě podřízený stream umožňuje) se znovu čtené bajty opět započítají.

Zde je jednoduchý příklad použití vytvořeného streamu:

try {
    CounterInputStream cis = new CounterInputStream(System.in);
    int b = 0;
    for (int i=0; i<100, b>=0; i++) {
        b = cis.read();
        if (b >= 0) {
            ...     // nějaká činnost
        }
    }
    cis.close();
    System.out.println("Cetnost hodnoty 54 je " + cis.getCount(54));
} catch (IOException e) {
    ...
}

Příklad ukazuje analýzu dat načítaných ze standardního vstupu. Po skončení čtení (přečte se 100 bajtů, při chybě už se dál nečte) se vypíše četnost hodnoty 54.

Práce se soubory

Dostali jsme se na konec úvodní části o výměně dat mezi programem a vnějším prostředím. Příště se vrhneme na důležitou věc, které se při psaní aplikací nikdo nevyhne, a to je práce se soubory. Prozkoumáme, jak jsou řešeny takové operace, jako je mazání nebo přejmenování souborů, jak se vytvářejí dočasné soubory, a v neposlední řadě, jak je řešena rozdílnost různých platforem, na kterých Java může běžet.

Verze pro tisk

pridej.cz

 

DISKUZE

chybka v poslednim prikladu 12.3.2006 13:12 azero
float 14.8.2006 13:33 Martin Landa




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

15.5.2017 23:50 /František Kučera

Máš rád svobodný software a hardware nebo se o nich chceš něco dozvědět? Zajímá tě DIY, CNC, SDR nebo morseovka? Přijď na sraz spolku OpenAlt, který se bude konat ve čtvrtek 18. května od 18:00 v Radegastovně Perón (Stroupežnického 20, Praha 5).


Přidat komentář

12.5.2017 16:42 /Honza Javorek
PyCon CZ, česká konference o programovacím jazyce Python, se po dvou úspěšných ročnících v Brně bude letos konat v Praze, a to 8. až 10. června. Na konferenci letos zavítá např. i Armin Ronacher, známý především jako autor frameworku Flask, šablon Jinja2/Twig, a dalších projektů. Těšit se můžete na přednášky o datové analytice, tvorbě webu, testování, tvorbě API, učení a mentorování programování, přednášky o rozvoji komunity, o použití Pythonu ve vědě nebo k ovládání nejrůznějších zařízení (MicroPython). Na vlastní prsty si můžete na workshopech vyzkoušet postavit Pythonem ovládaného robota, naučit se učit šestileté děti programovat, efektivně testovat nebo si v Pythonu pohrát s kartografickým materiálem. Kupujte lístky, dokud jsou.
Přidat komentář

2.5.2017 9:20 /Eva Rázgová
Putovní konference československé Drupal komunity "DrupalCamp Československo" se tentokrát koná 27. 5.2017 na VUT FIT v Brně. Můžete načerpat a vyměnit si zkušenosti z oblasti Drupalu 7 a 8, UX, SEO, managementu týmového vývoje, využití Dockeru pro Drupal a dalších. Vítáni jsou nováčci i experti. Akci pořádají Slovenská Drupal Asociácia a česká Asociace pro Drupal. Registrace na webu .
Přidat komentář

1.5.2017 20:31 /Pavel `Goldenfish' Kysilka
PR: 25.5.2017 proběhne v Praze konference na téma Firemní informační systémy. Hlavními tématy jsou: Informační systémy s vlastní inteligencí, efektivní práce s dokumenty, mobilní přístup k datům nebo využívání cloudu.
Přidat komentář

15.4.2017 15:20 /František Kučera
Máš rád svobodný software a hardware nebo se o nich chceš něco dozvědět? Zajímá tě IoT a radiokomunikace? Přijď na sraz spolku OpenAlt, který se bude konat ve středu 19. dubna od 18:30 v Šenkovně (Sokolská 60, Praha 2).
Přidat komentář

5.3.2017 19:12 /Redakce Linuxsoft.cz
PR: 23. března proběhne v Praze konferenci na téma Cloud computing v praxi. Hlavními tématy jsou: Nejžhavější trendy v oblasti cloudu a cloudových řešení, Moderní cloudové služby, Infrastruktura současných cloudů, Efektivní využití cloudu, Nástrahy cloudových řešení a jak se jim vyhnout.
Přidat komentář

27.2.2017 22:12 /František Kučera
Pozvánka na 137. sraz OpenAlt – Praha: Tentokrát jsme si pro vás připravili neobvyklou akci. Ve středu 1.3. v 17:30 nás přivítá sdružení CZ.NIC ve svých prostorách v Milešovské ulici číslo 5 na Praze 3, kde si pro nás připravili krátkou prezentaci jejich činnosti. Následně navštívíme jejich datacentrum pod Žižkovskou věží. Provedou nás prostory, které jsou běžnému smrtelníkovi nedostupné!
Po ukončení prohlídky se všchni odebereme do hostince U vodoucha, Jagelonská 21, Praha 3 pochutnat si na některém z vybraných piv či dát si něco na zub. Rezervaci máme od 19:30, heslo je OpenAlt.
Ale pozor! Do prostor datového centra máme omezený přístup, dostane se tam pouze 10 lidí! Takže kdo přijde dříve, ten má přednost, a občanky s sebou! Kdo nebude chtít na prohlídku datového centra, může se pomalu přesunout do hostince U vodoucha a u nepřeberné nabídky piv počkat na ostatní.
Přidat komentář

18.1.2017 0:49 /František Kučera
Členové a příznivci spolku OpenAlt se pravidelně schází v Praze a Brně. Fotky z pražských srazů za uplynulý rok si můžete prohlédnout na stránkách spolku. Příští sraz se koná už 19. ledna – tentokrát je tématem ergonomie ovládání počítače – tzn. klávesnice, myši a další zařízení. Také budete mít příležitost si prohlédnout pražský hackerspace Brmlab.
Přidat komentář

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

> Poslední diskuze

11.5.2017 23:32 / lelo
Re: Problém se správcem balíčků

11.5.2017 5:45 / davd mašek
Re: Problém se správcem balíčků

10.5.2017 22:54 / lelo
Re: Problém se správcem balíčků

10.5.2017 22:19 / davd mašek
Problém se správcem balíčků

17.4.2017 19:15 / Jakub shoop
chyba

Více ...

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