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 37321×
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
|
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 ...
|