|
||||||||||||||||||||||||||||||||||||||||||||||||
Menu
Distributions (131)
Software (10844)
|
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.
Další zajímavé streamyVe 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! PrintStreamPrintStream 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í
Charakteristickou vlastností třídy
Ještě důležitější je ovšem, že
Třída samozřejmě disponuje metodami
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
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 ( PipedInputStream/PipedOutputStream a PipedReader/PipedWriterTyto 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 // 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
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 Vstupní stream naopak pracuje vždy s bufferem pevné velikosti. Přečíst lze jen tolik bajtů, kolik jich v bufferu je. Serializace/deserializace datVelice č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
Máme totiž dvě třídy, 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
Nelze ukládat všechny objekty. Nutnou podmínkou je, aby implementovaly
rozhraní
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 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 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 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 souboryDostali 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.
|
Search Software
Search Google
|
||||||||||||||||||||||||||||||||||||||||||||||
©Pavel Kysilka - 2003-2024 | maillinuxsoft.cz | Design: www.megadesign.cz |