# Souborový vstup a výstup - Dosud jsme vstupní data četli z klávesnice (standardní vstup) a vypisovali výstupní data na obrazovku (standardní výstup) - Je však běžné, že programy čtou a zapisují vstupní a výstupní data z a do souborů ### Proudový vstup a výstup dat - Na vstupní a výstupní operace lze nahlížet tak, že se jedná o proud dat (stream) - To má tu výhodu, že zdroj dat (source – v případě vstupu) nebo cíl dat (target – v případě výstupu) mohou být různých typů (např. klávesnice/obrazovka nebo vstupní/výstupní soubor) a program tento proud dat zpracovává stejným způsobem - Tj. můžeme pracovat stejně s různými zdroji a cíli dat, liší se pouze počáteční inicializace zdroje a/nebo cíle #### Vlastnosti proudu dat - Základní vlastnost – „teče spojitě vpřed“ - Není možné se v něm vracet, nebo přeskakovat dopředu - Tyto možnosti ale prakticky nejsou potřeba příliš často - Typické zdroje dat jsou - Klávesnice, soubor a síťové spojení - Typické cíle dat jsou - Obrazovka, soubor a síťové spojení - Klávesnici a obrazovku jsme používali ve všech příkladech uvedených doposud - Na soubory se zaměříme nyní - Síťové spojení bude probráno v navazujících předmětech ### Typy souborů - **Soubory** se dají rozdělit na **textové** a **binární** - Oba typy souborů **obsahují pouze binární čísla** - V textovém souboru jsou jednotlivé byty interpretovány jako znaky v pevně daném kódování - V binárním souboru je význam jednotlivých bytů dán aplikací, která soubor zapisuje a/nebo čte - I binární soubor lze zobrazit jako textový, ale typicky uvidíme jen změť znaků, ze které se nic užitečného nedozvíme #### Textové soubory - Připomínají vstup z klávesnice - Jsou **čitelné pro člověka** (obsahují prostý text) - POZOR! - Nejde o soubory textových procesorů (např. MS Word), které sice obsahují mj. text, ale kromě něj i jeho formátování, styly, obrázky, atd. - Jde o soubory čitelné (zobrazitelné) obecným textovým editorem (např. PSPad, Notepad++, Poznámkový blok) - Jsou organizovány po řádcích - Ve skutečnosti je v souboru jen posloupnost znaků (do důsledku binárních čísel), která do řádků organizovaná není => je potřeba konce řádků vyznačit - Pro vyznačení se používají značky konce řádku - Značky se skládají z jednoho či dvou znaků (bytů) označovaných `````` (znak '\r' v Javě, dekadické číslo znaku 13) a `````` (znak '\n' v Javě, dekadické číslo znaku 10) - Konkrétní značka závisí na systému - Windows - `````` - Pořadí je důležité, `````` není chápána jako značka konce řádku - Linux - `````` - Mac OS/OS X - Dříve `````` (do Mac OS verze 9) - Novější verze `````` - V Javě stačí zapsat znak '\n' a Java doplní konec řádku podle toho, na jakém operačním systému je spuštěna - Textový soubor zapsaný v jednom operačním systému nemusí mít správně zobrazené řádky v jiném operačním systému - Záleží na konkrétním programu, který otevírá textový soubor, většina textových editorů umí správně zobrazit konce řádků ze všech běžných operačních systémů - Typická univerzální přípona je ```.txt``` - Pouze dodržovaná konvence - Textové soubory mohou mít i mnoho jiných specifických přípon podle toho, o jaký soubor se jedná - Zdrojové soubory programovacích jazyků - ```.java, .cpp, .c, .h, .pas, .js, .php``` a další #### Binární soubory - Pro běžného uživatele jsou mnohem častější - Běžné binární soubory - Obrázky - ```.jpg, .png, .gif, .bmp``` a další - Hudba - ```.mp3, .ogg, .wav``` a další - Video - ```.avi, .mp4, .mkv, .mov``` a další - Komprimované archivy - ```.zip, .rar, .7z``` a další - Dokument MS Word, MS Excel - ```.doc, .docx, .xls, .xlsx``` - Soubory ```.docx``` a ```.xlsx``` jsou ve skutečnosti komprimované složky obsahující množství ```xml``` souborů, obrázky a další data - Nejsou čitelné pro člověka a nejsou uspořádané po řádcích - Pro jejich prohlížení a editaci jsou potřeba specializované programy (např. přehrávače médií pro hudbu a video) - Jejich vnitřní organizace je daná formátem souboru - Formát souboru je indikován příponou - Tu ale lze libovolně měnit (je to jen součást názvu souboru), proto nemusí odpovídat skutečnému obsahu souboru - Nesoulad přípony a skutečného obsahu souboru někdy působí potíže prohlížečům daného formátu (soubor nemusí jít otevřít) - Jak formát vypadá – tj. co který byte znamená a jaké jsou povolené hodnoty – závisí na tvůrci formátu #### Výhody a nevýhody textových a binárních souborů - Výhody textových souborů - Jsou čitelné pro člověka - Člověk dokáže informaci přečíst, ale to nutně nemusí znamenat, že jí porozumí - Např. řádka ```21, 37, 54, diference 0.3888``` je sice čitelná, ale těžko říci, co znamená - Je možné je číst a upravovat obecným textovým editorem - Nevýhody textových souborů - Stejné množství informace většinou zabírá více místa - Pomalejší zpracování (čtení/zápis kvůli převodu z/do čitelné formy) - Výhody binárních souborů - Paměťově úspornější - Rychlejší zpracování - Nevýhody binárních souborů - Pro každý formát či skupinu formátů je potřeba speciální prohlížeč - Nečitelnost v případě nedokumentovaných proprietárních formátů - Tuto vlastnost někteří výrobci považují za výhodu - V dalším textu se budeme zabývat proudovým čtením a zápisem textových souborů, pokud nebude řečeno jinak ### Využití prostředků pro standardní vstup/výstup - Již dříve jsme využívali přesměrování standardního vstupu a/nebo výstupu pomocí prostředků operačního systému - Programy vytvořené pro práci se standardním vstupem a výstupem fungovaly i po přesměrování vstupu a/nebo výstupu bez problémů - Prostředky použité pro čtení standardního vstupu a zápis do standardního výstupu lze tedy použít i pro práci se soubory - Pro práci se soubory obecně platí (bez ohledu na to, jaký prostředek použijeme) - Soubor (vstupní i výstupní) je nutné před použitím otevřít - Soubor se připraví pro čtení a/nebo zápis, konkrétní soubor na disku se „spojí“ s prostředkem pro práci se souborem - Většinou se provede už při vytvoření instance prostředku pro práci se souborem - Soubor (vstupní i výstupní) je vhodné po posledním použití uzavřít - Konkrétní soubor na disku se „odpojí“ od prostředku pro práci se souborem - Provede se voláním metody ```close()``` nebo automaticky při využití konstrukce ```try```-with-resources - Všechny otevřené soubory jsou automaticky uzavřeny JVM po ukončení programu, takže nehrozí, že by zůstaly otevřené - Při čtení ze souboru se z hlediska obsahu souboru nic nestane, pokud soubor neuzavřeme - Souborů, které mohou být najednou otevřeny, je však omezený počet - Pokud je soubor otevřen, typicky do něj nelze zapisovat jinou aplikací - Při zápisu do souboru se může stát, že se data nezapíší všechna, pokud soubor neuzavřeme - Téměř vždy je nutné ošetřit výjimky (typicky odvozené od ```IOException```) - Čtení a zápis do souboru je operace, která může často selhat z nejrůznějších příčin, které často nejsou ovlivnitelné naším programem - Proto je ošetření nutné (```IOException``` je kontrolovaná výjimka a je vyhazována většinou metod pro práci se soubory) #### Použití třídy Scanner pro čtení ze souboru - Třídu ```Scanner``` lze přímo použít pro čtení ze souboru - Stačí do konstruktoru uvést soubor místo standardního vstupu (```System.in```) - Nelze uvést pouze název souboru, je potřeba vytvořit instanci rozhraní ```Path``` - ```Scanner s = new Scanner(Paths.get("vstup.txt"));``` - POZOR! - Instance rozhraní ```Path``` se vytváří metodou třídy ```get()``` třídy ```Paths``` - Je nutné ošetřit ```IOException``` - Test konce souboru - Protože se často může stát, že nevíme, kolik hodnot či řádek je ve čteném souboru uvedeno, lze využít metody pro testování, zda se v souboru nachází další hodnota daného typu - Pokud ne, lze skončit - Metody ```hasNextTyp()``` - Např. metoda ```hasNextInt()``` - Vrací ```true```, pokud je ve vstupním souboru další celé číslo - Např. metoda ```hasNextDouble()``` - Vrací ```true```, pokud je ve vstupním souboru další reálné číslo - Např. metoda ```hasNext()``` - Vrací ```true```, pokud je ve vstupním souboru další řetězec - Např. metoda ```hasNextLine()``` - Vrací ```true```, pokud je ve vstupním souboru další řádka #### Použití třídy ```PrintStream``` pro zápis do souboru - Třída ```PrintStream``` poskytuje známé metody ```println()```, ```print()``` a ```format()``` pro zápis formátovaných dat do souboru - V referenční proměnné ```System.out``` se skrývá instance třídy ```PrintStream``` - Jeden z konstruktorů umožňuje vytvořit novou instanci třídy ```PrintStream``` ze zadaného názvu souboru - Místo souboru můžeme použít i standardní výstup - Stačí použít ```System.out``` (viz první zakontovaná řádka v metodě ```main()```) a odstranit konstrukci ```try – catch``` ### Využití prostředků balíku ```java.nio.file``` - Prostředky pro jednoduché čtení a zápis z/do souborů poskytuje třída ```Files``` z balíku ```java.nio.file``` - Nahrazuje funkcionalitu z balíku ```java.io```, který je v Javě od začátku - Mnoho tříd z balíku ```java.io``` však stále využívá - Obsahuje snadno použitelné statické metody (podobně jako třída ```Math```) #### Rychlé čtení s využitím třídy BufferedReader - Scanner funguje dobře, ale pokud by byl vstupní soubor větší, trvalo by jeho načítání nezanedbatelnou dobu - Je třeba si uvědomit, že práce (tj. čtení a zápis) s vnější pamětí (tj. typicky s pevným diskem, kde jsou soubory uloženy) je řádově pomalejší než práce s vnitřní pamětí => rychlost čtení a zápisu je vhodné řešit ve většině případů, nejen při práci s extrémně velkými soubory - Využití třídy ```BufferedReader``` - Tzv. bufferovaný vstup - V podstatě to znamená, že se z disku načte najednou větší část obsahu souboru do paměti a odtud se následně čtou data => podstatně rychlejší než číst data z disku po jednotlivých číslech nebo řádcích - Instanci třídy ```BufferedReader``` lze snadno získat ze třídy ```Files``` na základě zadání souboru jako instance ```Path``` - Metoda ```Files.newBufferedReader(soubor)``` - Třída ```BufferedReader``` nabízí metodu ```readLine()```, která umožňuje načíst jednu řádku ze souboru - Metoda vrací ```null```, pokud dosáhne konce souboru – využívá se, pokud nevíme, kolik řádek v souboru je (častý případ) - Nenabízí však metody pro načtení jiných datových typů jako třída ```Scanner``` Načtenou řádku je tedy nutné zpracovat (rozdělit podle nějakého znaku, převést na číslo apod.) ručně - Čtení pomocí třídy ```BufferedReader``` je výrazně rychlejší než čtení pomocí třídy ```Scanner``` #### Rychlý zápis s využitím třídy BufferedWriter - Podobně jako lze čtení ze souboru urychlit použitím třídy ```BufferedReader```, je možné zápis do souboru urychlit použitím třídy ```BufferedWriter``` - Tzv. bufferovaný výstup - V podstatě to znamená, že se data zapisují nejprve do paměti a následně se větší blok dat zapíše najednou na disk - Ze třídy ```Files``` lze snadno získat instanci ```BufferedWriter``` pro rychlý zápis do souboru pouze na základě zadání souboru jako instance ```Path``` - Třída ```BufferedWriter``` ale neobsahuje metody pro pohodlný zápis výstupu (```println()```, ```print()```, ```format()```) - Proto je vhodné zkombinovat ji se třídou ```PrintWriter```, která zmíněné metody obsahuje - Třída ```Files``` bohužel neobsahuje metodu, která by vrátila rovnou instanci třídy ```PrintWriter``` - POZOR! - Při použití třídy ```BufferedWriter``` je obzvláště důležité soubor po zapsání všech dat uzavřít - ```BufferedWriter``` zapisuje data nejprve do paměti a následně zapíše celý blok dat do souboru - Pokud soubor neuzavřeme, velmi často se stává, že poslední blok se do souboru nezapíše a soubor tak není kompletní - Pokud je dat, které zapisujeme do souboru, málo, může se stát, že soubor bude úplně prázdný - Okamžité zapsání do souboru lze vynutit metodou ```flush()``` - Příklad použití tříd ```BufferedWriter``` a ```PrintWriter``` - Rozdíl mezi ```PrintWriter``` a ```PrintStream``` - V Javě je několik tříd pro práci se vstupy a výstupy končící na …(```Input/Output```)```Stream``` a několik tříd pro práci se vstupy výstupy končící na …```Reader/Writer``` - Třídy ```…Reader/Writer``` pracují se znaky - Znak v Javě zabírá dva byty, ale většina textových souborů takto uložena není - Znaky se do souborů typicky ukládají jako jeden či více bytů podle použitého kódování - Při čtení nebo zápisu provádějí třídy ```…Reader/Writer``` konverzi - Třídy …(```Input/Output```) ```Stream``` pracují s byty - Jsou určeny pro práci s byty, které však také mohou představovat znaky - Metody určené pro formátovaný výstup (např. metody ```println()```, ```print()``` a ```format()``` třídy ```PrintStream```) rovněž provádějí konverzi, ostatní metody ne - Umožňují i zápis a čtení jednoho či více bytů přímo (bez konverze) - Proč je standardní výstup instance třídy ```PrintStream``` (určená pro práci s byty), i když standardní výstup evidentně pracuje se znaky - Historické důvody, v Javě 1.0 třídy ```…Reader/Writer``` neexistovaly - Zachováno kvůli zpětné kompatibilitě - Jsou i situace, kdy je vhodné, aby standardní vstup a/nebo výstup pracoval s byty a ne se znaky #### Načtení všech řádek souboru - Určeno pro malé soubory - Rychlost načítání je řádově srovnatelná s třídou ```BufferedReader``` - Metoda ```Files.readAllLines(soubor)``` - Metoda zajistí otevření i uzavření souboru samostatně, je potřeba pouze odchytit případnou ```IOException``` - Metoda vrací seznam řádek (```List``` – ne pole) souboru zadaného jako instance ```Path``` - Jednotlivé řádky v seznamu jsou přístupné pomocí metody instance seznamu ```get(index)``` - Indexy jsou stejné jako u pole – 0 až délka seznamu - 1 - Délku seznamu vrátí metoda instance seznamu ```size()``` - Seznam se dá procházet, stejně jako pole, cyklem ```for – each``` ### Práce s binárními soubory - Práce s binárními soubory je v Javě velice podobná práci s textovými soubory - Používáme třídy končící na …(```Input/Output```) ```Stream``` - Naprostá většina metod těchto tříd neprovádí konverzi na znaky (s výjimkou metod ```println()```, ```print()``` a ```format()``` třídy ```PrintStream```) - Byty se načtou/zapíšou tak, jak jsou (na rozdíl od tříd končících na ```…Reader/Writer```) #### Zápis do binárního souboru - Je možné zapisovat přímo jednotlivé byty - Základní třída ```OutputStream``` - Její instanci lze získat metodou ```Files.newOutputStream(soubor)``` - Je možné zapisovat hodnoty základních datových typů - Třída ```DataOutputStream``` - Obsahuje metody ```writeTyp(hodnota)``` pro zápis všech základních datových typů - Např. ```writeDouble(5.0)``` - Do jednoho souboru je tak možné zapsat hodnoty různých datových typů - Pořadí a počty jednotlivých hodnot je třeba řádně zdokumentovat, aby bylo možné takový binární soubor číst - Pro urychlení je možné použít třídu ```BufferedOutputStream``` stejným způsobem jako ```BufferedWriter``` - Třída ```Files``` však neposkytuje metodu, která by vracela instanci ```BufferedOutputStream``` - Je potřeba vytvořit ```BufferedOutputStream``` ručně #### Čtení z binárního souboru - Je možné číst přímo jednotlivé byty - Základní třída ```InputStream``` - Její instanci lze získat metodou ```Files.newInputStream(soubor)``` - Je možné číst hodnoty základních datových typů - Třída ```DataInputStream``` - Obsahuje metody ```readTyp()``` pro čtení všech základních datových typů - Např. ```readDouble()``` - Odpovídají metodám ```writeTyp()``` třídy ```DataOutputStream``` - Pro urychlení je možné použít třídu ```BufferedInputStream``` - Třída ```Files``` však neposkytuje metodu, která by vracela instanci ```BufferedInputStream``` - Je potřeba vytvořit ```BufferedInputStream``` ručně