# 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 ```<CR>``` (znak '\r' v Javě, dekadické číslo znaku 13) a ```<LF>``` (znak '\n' v Javě, dekadické číslo znaku 10)
	- Konkrétní značka závisí na systému
		- Windows
			- ```<CR><LF>```
			- Pořadí je důležité, ```<LF><CR>``` není chápána jako značka konce řádku 
		- Linux
			- ```<LF>```
		- Mac OS/OS X
			- Dříve ```<CR>``` (do Mac OS verze 9)
			- Novější verze ```<LF>```
		- 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<String>``` – 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ě

