Datový proud

Datové proudy (anglicky stream; dále jen „proudy“) jsou sekvence dat. Proud je definován svým vstupem a výstupem, těmi mohou být například soubor na disku, zařízení (vstupní klávesnice nebo myš, výstupní displej) nebo jiný program. Primitivní proudy pouze přenášejí binární data, specializované proudy umí s přenášenými daty manipulovat. Tyto manipulační schopnosti se liší podle typu přenášených dat.

Datové proudy mohou být potenciálně nekonečné (tzv. codata), čímž se liší od běžných konečných dat.

Přehled

Proudy vznikly jako náhrada rigidního propojení procesů a zařízení v Unixu v 80. letech 20. století. Před zavedením proudů se programy musely explicitně připojovat k zařízením a programům. Programátor musel brát v úvahu různá rozhraní, různou reprezentaci dat. Datové proudy umožnily od těchto problémů abstrahovat vytvořením jednotného rozhraní.

Dennis Ritchie popisuje proudy jako plně duplexní (obousměrné) spojení mezi uživatelským procesem a zařízením popř. pseudozařízením. Skládá se z několika lineárně spojených zpracovávajících modulů; je analogický k Shell pipeline, přičemž data mohou proudit oběma směry. Moduly v proudu mezi sebou komunikují prostřednictvím zpráv. Na konci proudu, který je blíže procesu, je sada funkcí, které zprostředkovávají rozhraní se zbytkem systému. Požadavky uživatele na zápis a ovládání vstupů a výstupů jsou transformovány ve zprávy a odeslány proudu, požadavky na čtení přebírají data z proudu a předávají je uživateli. Na opačném konci proudu se nachází ovladač zařízení. Data přicházející z proudu jsou předávána zařízení a znaky a stavy zaznamenané zařízením jsou zabaleny do zpráv a odeslány přes proud směrem k uživatelskému programu. Další moduly, které zapojeny do proudu, mohou s procházejícími daty manipulovat.

Každý modul zapojený v proudu je složen ze dvou front, každou pro jeden směr. Kromě dat jsou s frontami spojeny i procedury put (vlož), která vkládá data do fronty, a service (obsluž), která je volána kdykoli jsou k dispozici data ke zpracování.

Proudy byly implementovány v UNIXu v roce 1984. Zároveň byly v jazyce C implementovány potřebné funkce a typy, které umožnily s datovými proudy pracovat.[1]

Standardní proudy

Podrobnější informace naleznete v článku Standardní proudy.

Systém vytváří ke spouštěným procesům tři popisovače, ke kterým se programy připojují prostřednictvím proudů. Jeden standardní vstup (0), jeden standardní výstup (1) a jeden chybový výstup (2). Pokud je proces spuštěn v terminálu, standardním vstupem je text napsaný do okna terminálu. U textových zpráv z programu je terminálové okno naopak příjemcem. Tyto proudy je možno přesměrovat. Procesu je možno operátorem ‚<‘ nastavit standardní vstup tak, aby četl například ze souboru.

more < vstup.txt

Analogicky lze výstup programu přesměrovat operátorem ‚>‘. Pro přesměrování standardního chybového výstupu je třeba uvést před operátorem číslo popisovače, tedy 2.

ping seznam.cz > ping_seznam.txt

Proudy v Javě

V jazyku Java existuje několik tříd, které se zabývají přenosem různých typů dat. Většina z nich se nachází v balíčku (package) java.io. Třídy pro práci se soubory se nacházejí v balíčku java.nio.file.[2]

Bajtové proudy

Všechny vstupní třídy jsou potomky třídy InputStream a výstupní třídy potomky OutputStream. Nejzákladnějším typem proudu je bajtový proud. Tento proud čte nebo zapisuje data na nízké úrovni po 8bitových svazcích. Doporučuje se tento typ proudu nepoužívat, protože existují specializovanější alternativy (viz dále).

Příklad

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

[[public]] [[Třída (programování)|class]] ZkopírujBajty {
    public static [[void]] main(String[] args) throws [[Výjimka (programování)|IOException]] {

        FileInputStream in = null;
        FileOutputStream out = null;

        try {
            in = new FileInputStream("vstup.txt"); //otevři vstupní proud
            out = new FileOutputStream("vystup.txt"); //otevři výstup.proud
            int c; //právě čtený bajt; pokud je -1, je konec souboru

            while ((c = in.read()) != -1) { //dokud jsou data na vstupu
                out.write(c); //pošli je na výstup
            }
        } finally {
            if (in != null) { //pokud je otevřený vstupní proud
                in.close(); //uzavři ho
            }
            if (out != null) { //pokud je otevřený výstupní proud
                out.close(); //uzavři ho
            }
        }
    }
}

Uzavírání proudů

V tomto případě je čten obsah souboru vstup.txt bajt po bajtu a ukládán do souboru vystup.txt. Oba proudy je třeba po použití uzavřít, protože těchto proudů může operační systém poskytnout omezené množství.

Znakové proudy

Java používá pro vnitřní reprezentaci znaků kódování Unicode. Znakové proudy s tím počítají a dokážou toto kódování při zápisu a čtení překládat na jiná. Všechny vstupní třídy jsou potomky třídy Reader a všechny výstupní třídy jsou potomky třídy Writer.

Příklad

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class ZkopírujZnaky {
    public static void main(String[] args) throws IOException {

        FileReader in = null;
        FileWriter out = null;

        try {
            in = new FileReader("vstup.txt"); //otevři vstupní proud
            out = new FileWriter("výstup.txt"); //otevři výstupní proud

            int c; //právě čtený znak (16 bitů), -1 znamená konec souboru
            while ((c = in.read()) != -1) { //dokud jsou data na vstupu
                out.write(c); //zapiš znak na výstup
            }
        } finally {
            if (in != null) { //pokud je otevřený vstupní proud
                in.close(); //uzavři ho
            }
            if (out != null) { //pokud je otevřený výstupní proud
                out.close(); //uzavři ho
            }
        }
    }
}

Proudy s vyrovnávací pamětí

Běžně je každý požadavek na zápis nebo čtení zpracováván přímo operačním systémem. To může vést k neefektivitě programu, protože každý takový požadavek může potřebovat např. přistupovat k disku nebo síťovým zdrojům, což jsou časově relativně náročné operace. Vyrovnávací paměť (buffer) je místo v operační paměti vyhrazené pro dočasné uchování dat před jejich přesunem na jiné místo. Pouze pokud je vyrovnávací paměť prázdná, použije se běžný přístup. Proudy lze „naučit“ používat vyrovnávací paměť využitím návrhového vzoru dekorátor. Proud, který čte ze souboru a využívá vyrovnávací paměť je možno vytvořit následovně:

inputStream = new BufferedReader(new FileReader("vstup.txt"));

Nejprve se vytvoří proud pro čtení ze souboru (FileReader) a ten poté „obalí“ proud pracující s vyrovnávací pamětí (BufferedReader). Obdobně pracuje i výstupní proud:

outputStream = new BufferedWriter(new FileWriter("characteroutput.txt"));

V Javě existují celkem 4 třídy, které obalují proudy: BufferedInputStream, BufferedOutputStream, BufferedReader, BufferedWriter.[3]

Vyprázdnění vyrovnávací paměti

Je dobrým zvykem vyprazdňovat vyrovnávací paměť (zapsat změněná data) na kritických místech. Tato operace uloží celý obsah vyrovnávací paměti na cíl proudu (soubor, zařízení atd.) Běžně se používá anglický výraz „flush“.

Dekorátor

Podrobnější informace naleznete v článku Dekorátor.

Dekorátor je návrhový vzor, který se používá k obohacení existujících objektů o přidanou funkcionalitu. Funkce, které neupravuje, deleguje na obalený objekt. V angličtině se člověk může setkat s označením „wrapper“. Uplatnění tento vzor najde třeba při tvorbě GUI, kdy lze například podkladovou třídu

Okno o = new Okno();

obohatit o titulek tak, že

o = new OknoSTitulkem(o);

a dále třeba o posuvník

o = new OknoSPosuvníkem(o);

Datové proudy

Java obsahuje třídy pro pohodlné čtení a zápis primitivních datových typů a typu String. Nejrozšířenější třídy jsou DataInputStream a DataOutputStream. String je ukládán v kódování UTF-8. Údaje takto uložené např. do souboru nejsou uživatelsky přívětivé a s výjimkou řetězců čitelné. Metody pro čtení jsou readDouble, readInt, readUTF. Obdobně metody pro zápis mají předponu write.

Objektové proudy

Většina standardních tříd implementuje rozhraní Serializable (serializovatelný), které je nezbytné pro jejich podporu objektovými proudy. Objektové proudy rozšiřují datové proudy, takže objektové proudy umí pracovat i s primitivními datovými typy. Nové metody jsou readObject a writeObject. Pokud metoda readObject vrátí jiný než očekávaný objekt, vyhodí výjimku typu ClassNotFoundException.

Pokud se objekt neskládá jen z primitivních typů ale i z referencí na další objekty, je potřeba zachovat tyto reference. Proto je při zápisu objektu uložit i všechny objekty, na které má daný objekt odkaz. Podobně se bude chovat čtecí proud, který se bude snažit zrekonstruovat celou takovou síť objektů.[4]

Odkazy

Reference

  1. RITCHE, Dennis. A Stream Input-Output System [online]. AT&T, October, 1984. Dostupné online.
  2. Oracle. Lesson: Basic I/O [online]. Oracle, 2015. Dostupné online.
  3. Oracle. Buffered Streams [online]. Oracle, 2015. Dostupné online.
  4. Oracle. Object Streams [online]. Oracle, 2015. Dostupné online.

Literatura

This article is issued from Wikipedia. The text is licensed under Creative Commons - Attribution - Sharealike. Additional terms may apply for the media files.