17 KiB
17 KiB
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
- Není možné se v něm vracet, nebo přeskakovat dopředu
- 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)
- POZOR!
- 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)
- Značky se skládají z jednoho či dvou znaků (bytů) označovaných
- 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>
- Dříve
- 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
- Windows
- 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ší
- Zdrojové soubory programovacích jazyků
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
- Obrázky
- 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
- Formát souboru je indikován příponou
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á
- Např. řádka
- Člověk dokáže informaci přečíst, ale to nutně nemusí znamenat, že jí porozumí
- Je možné je číst a upravovat obecným textovým editorem
- Jsou čitelné pro člověka
- 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í konstrukcetry
-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)
- Soubor (vstupní i výstupní) je nutné před použitím otevřít
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řídyget()
třídyPaths
- Instance rozhraní
- POZOR!
- Je nutné ošetřit
IOException
- Nelze uvést pouze název souboru, je potřeba vytvořit instanci rozhraní
- 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
- Vrací
- Např. metoda
hasNextDouble()
- Vrací
true
, pokud je ve vstupním souboru další reálné číslo
- Vrací
- Např. metoda
hasNext()
- Vrací
true
, pokud je ve vstupním souboru další řetězec
- Vrací
- Např. metoda
hasNextLine()
- Vrací
true
, pokud je ve vstupním souboru další řádka
- Vrací
- Např. metoda
- 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
Použití třídy PrintStream
pro zápis do souboru
- Třída
PrintStream
poskytuje známé metodyprintln()
,print()
aformat()
pro zápis formátovaných dat do souboru - V referenční proměnné
System.out
se skrývá instance třídyPrintStream
- 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 konstrukcitry – catch
- Stačí použít
- Místo souboru můžeme použít i standardní výstup
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íkujava.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á
- Mnoho tříd z balíku
- Obsahuje snadno použitelné statické metody (podobně jako třída
Math
)
- Nahrazuje funkcionalitu z balíku
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řídyFiles
na základě zadání souboru jako instancePath
- Metoda
Files.newBufferedReader(soubor)
- Metoda
- Třída
BufferedReader
nabízí metodureadLine()
, 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ě
- Metoda vrací
- Čtení pomocí třídy
BufferedReader
je výrazně rychlejší než čtení pomocí třídyScanner
- Tzv. bufferovaný vstup
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řídyBufferedWriter
- 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 instanciBufferedWriter
pro rychlý zápis do souboru pouze na základě zadání souboru jako instancePath
- 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řídyPrintWriter
- Třída
- Tzv. bufferovaný výstup
- 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ři použití třídy
- Příklad použití tříd
BufferedWriter
aPrintWriter
- Rozdíl mezi
PrintWriter
aPrintStream
- 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()
aformat()
třídyPrintStream
) rovněž provádějí konverzi, ostatní metody ne - Umožňují i zápis a čtení jednoho či více bytů přímo (bez konverze)
- V Javě je několik tříd pro práci se vstupy a výstupy končící na …(
- 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
- Historické důvody, v Javě 1.0 třídy
Načtení všech řádek souboru
- Určeno pro malé soubory
- Rychlost načítání je řádově srovnatelná s třídou
BufferedReader
- Rychlost načítání je řádově srovnatelná s třídou
- 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 instancePath
- 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
- Jednotlivé řádky v seznamu jsou přístupné pomocí metody instance seznamu
- Metoda zajistí otevření i uzavření souboru samostatně, je potřeba pouze odchytit případnou
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()
aformat()
třídyPrintStream
)- Byty se načtou/zapíšou tak, jak jsou (na rozdíl od tříd končících na
…Reader/Writer
)
- Byty se načtou/zapíšou tak, jak jsou (na rozdíl od tříd končících na
- Naprostá většina metod těchto tříd neprovádí konverzi na znaky (s výjimkou metod
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)
- Její instanci lze získat metodou
- Základní třída
- 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)
- Např.
- 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
- Obsahuje metody
- Třída
- Pro urychlení je možné použít třídu
BufferedOutputStream
stejným způsobem jakoBufferedWriter
- Třída
Files
však neposkytuje metodu, která by vracela instanciBufferedOutputStream
- Je potřeba vytvořit
BufferedOutputStream
ručně
- Třída
Č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)
- Její instanci lze získat metodou
- Základní třída
- 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řídyDataOutputStream
- Např.
- Obsahuje metody
- Třída
- Pro urychlení je možné použít třídu
BufferedInputStream
- Třída
Files
však neposkytuje metodu, která by vracela instanciBufferedInputStream
- Je potřeba vytvořit
BufferedInputStream
ručně
- Třída