LINUXSOFT.cz Přeskoč levou lištu

ARCHIV



   

> 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 37205×

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ů

28.11.2018 23:56 /František Kučera
Prosincový sraz spolku OpenAlt se koná ve středu 5.12.2018 od 16:00 na adrese Zikova 1903/4, Praha 6. Tentokrát navštívíme organizaci CESNET. Na programu jsou dvě přednášky: Distribuované úložiště Ceph (Michal Strnad) a Plně šifrovaný disk na moderním systému (Ondřej Caletka). Následně se přesuneme do některé z nedalekých restaurací, kde budeme pokračovat v diskusi.
Komentářů: 1

12.11.2018 21:28 /Redakce Linuxsoft.cz
22. listopadu 2018 se koná v Praze na Karlově náměstí již pátý ročník konference s tématem Datová centra pro business, která nabídne odpovědi na aktuální a často řešené otázky: Jaké jsou aktuální trendy v oblasti datových center a jak je optimálně využít pro vlastní prospěch? Jak si zajistit odpovídající služby datových center? Podle jakých kritérií vybírat dodavatele služeb? Jak volit vhodné součásti infrastruktury při budování či rozšiřování vlastního datového centra? Jak efektivně datové centrum spravovat? Jak co nejlépe eliminovat možná rizika? apod. Příznivci LinuxSoftu mohou při registraci uplatnit kód LIN350, který jim přinese zvýhodněné vstupné s 50% slevou.
Přidat komentář

6.11.2018 2:04 /František Kučera
Říjnový pražský sraz spolku OpenAlt se koná v listopadu – již tento čtvrtek – 8. 11. 2018 od 18:00 v Radegastovně Perón (Stroupežnického 20, Praha 5). Tentokrát bez oficiální přednášky, ale zato s dobrým jídlem a pivem – volná diskuse na téma umění a technologie, IoT, CNC, svobodný software, hardware a další hračky.
Přidat komentář

4.10.2018 21:30 /Ondřej Čečák
LinuxDays 2018 již tento víkend, registrace je otevřená.
Přidat komentář

18.9.2018 23:30 /František Kučera
Zářijový pražský sraz spolku OpenAlt se koná již tento čtvrtek – 20. 9. 2018 od 18:00 v Radegastovně Perón (Stroupežnického 20, Praha 5). Tentokrát bez oficiální přednášky, ale zato s dobrým jídlem a pivem – volná diskuse na téma IoT, CNC, svobodný software, hardware a další hračky.
Přidat komentář

9.9.2018 14:15 /Redakce Linuxsoft.cz
20.9.2018 proběhne v pražském Kongresovém centru Vavruška konference Mobilní řešení pro business. Návštěvníci si vyslechnou mimo jiné přednášky na témata: Nejdůležitější aktuální trendy v oblasti mobilních technologií, správa a zabezpečení mobilních zařízení ve firmách, jak mobilně přistupovat k informačnímu systému firmy, kdy se vyplatí používat odolná mobilní zařízení nebo jak zabezpečit mobilní komunikaci.
Přidat komentář

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ář

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

> Poslední diskuze

31.7.2023 14:13 / Linda Graham
iPhone Services

30.11.2022 9:32 / Kyle McDermott
Hosting download unavailable

13.12.2018 10:57 / Jan Mareš
Re: zavináč

2.12.2018 23:56 / František Kučera
Sraz

5.10.2018 17:12 / Jakub Kuljovsky
Re: Jaký kurz a software by jste doporučili pro začínajcího kodéra?

Více ...

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