FAV-ZCU/KIV PPA1/17. Souborový vstup a výstup.md

258 lines
17 KiB
Markdown
Raw Permalink Normal View History

# 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
2022-12-21 16:07:34 +01:00
- 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
2022-12-21 16:07:34 +01:00
- 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
2022-12-21 16:07:34 +01:00
- 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
2022-12-21 16:07:34 +01:00
- 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
2022-12-21 16:07:34 +01:00
- 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ě