257 lines
17 KiB
Markdown
257 lines
17 KiB
Markdown
# 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ě
|
||
|