Koncepce vstupně/výstupních operací v Javě
-
V/V operace jsou založeny na vstupně/výstupních proudech (streams).
-
Tím pádem je možno značnou část logiky programu psát nezávisle na tom, o který konkrétní typ V/V zařízení jde.
-
Současně s tím jsou díky tomu V/V operace plně platformově nezávislé.
typ dat |
vstupní |
výstupní |
binární |
InputStream |
OutputStream |
znakové |
Reader |
Writer |
Vstupy a výstupy v Javě
Je toho příliš mnoho?
API proudů
Skládání vstupně/výstupních proudů
Proudy jsou koncipovány jako "stavebnice" — lze je spojovat za sebe a tím přidávat vlastnosti:
// casual input stream
InputStream is = System.in;
// bis enhances stream with buffering option
BufferedInputStream bis = new BufferedInputStream(is);
Neplést si streamy (proudy dat) s lambda streamy! |
Návrhový vzor Decorator
-
Jedná se o použití návrhové vzoru Decorator:
-
"Nízkoúrovňové" třídy (např.
FileReader
) odpovídají tříděConcreteComponent
ze vzoru a poskytují základní funkcionalitu. -
"Obalující" třídy, jejichž konstruktor vyžaduje již existující proud (např.
BuferedReader
) jsou dekorátory, které se "předsadí" před původní objekt a poskytují dodatečné metody. Na pozadí přitom komunikují s původním objektem. -
Klientský kód může komunikovat jak s dekorátorem, tak přímo s původním objektem.
-
Stručné shrnutí
Closable |
bytes closable |
characters closable |
lines closable |
Input |
InputStream |
InputStreamReader |
BufferedReader |
Output |
OutputStream |
OutputStreamWriter |
BufferedWriter |
Základem znakových vstupních proudů je abstraktní třída Reader (pro výstupní Writer).
Konverze binárního proudu na znakový
-
Ze vstupního binárního proudu InputStream (čili každého) je možné vytvořit znakový Reader.
-
Ale pozor. Jedná se dvě různé hierarchie. Nelze tedy například vytvořit binární proud a konvertovat ho na buffered reader:
FileInputStream is = new FileInputStream("file.txt");
BufferedReader reader = new BufferedReader(is); // Syntax error - incompatible type of is
Konverze binárního proudu na znakový (pokr.)
Musí se použít k tomu určená třída InputStreamReader
(obdobně pro výstupní proudy OutputStreamWriter
).
// binary input stream
InputStream is = ...
// character stream, decoding uses standard charset
Reader reader = new InputStreamReader(is);
// charsets are defined in java.nio package
Charset charset = java.nio.Charset.forName("ISO-8859-2");
// character stream, decoding uses ISO-8859-2 charset
Reader reader2 = new InputStreamReader(is, charset);
-
Podporované názvy znakových sad naleznete na webu IANA Charsets .
Na zjištění, jestli je možné z čtenáře číst, se používa metoda reader.ready() .
|
-
InputStreamReader
(iOutputStreamWriter
) implementuje návrhový vzor Bridge:-
Hierarchie tříd
InputStream
představuje implementaci čtení binárních dat ze vstupních proudů. -
InputStreamReader
pak převádí tato binární data do abstrakce textových dat.
-
Konverze znakového proudu na "buffered"
InputStreamReader isr = new InputStreamReader(is);
// takes another Reader and makes it bufferable
BufferedReader br = new BufferedReader(isr);
// BufferedReader supports read by line
String firstLine = br.readLine();
String secondLine = br.readLine();
Znakové výstupní proudy
-
Jedná se o protějšky k vstupním proudům, názvy jsou konstruovány analogicky (např. FileReader → FileWriter).
-
Místo generických metod read mají write(…).
OutputStream os = System.out;
os.write("Hello World!");
// we have to use generic newline separator
os.write(System.lineSeparator());
// bw has special method for that
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
bw.newline();
Zavírání proudů a souborů
-
Soubory zavíráme vždy.
-
Proudy nezavíráme.
-
Když zavřeme
System.out
, metodaprintln
pak přestane vypisovat text.
Povinné zavírání proudů
-
Při otevření souboru (a konverzi na proud) se musíme postarat o dodatečné uzavření souboru.
-
Před Java 7 se to muselo dělat blokem finally:
public String readFirstLine(String path) throws IOException {
BufferedReader br = new BufferedReader(new FileReader(path));
try {
return br.readLine();
} finally {
if (br != null) br.close();
}
}
Nově sa dá použít tzv. try-with-resources:
public String readFirstLine(String path) throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
return br.readLine();
}
}
Více proudů
Pomocí try-with-resources lze ošetřit i více proudů současně — zavřou se pak všechny.
try (
ZipFile zf = new ZipFile(zipFileName);
BufferedWriter writer = new BufferedWriter(outputFilePath, charset)
) {
...
}
Obecně lze do hlavičky try-with-resources dát nejen proud, ale cokoli, co implementuje java.io.Closeable. |
Repl.it demo k vstupům a výstupům
Výpis textu PrintStream a PrintWriter
- PrintStream
-
je typ proudu standardního výstupu System.out (a chybového System.err).
-
Vytváří se z binárního proudu, lze jím přenášet i binární data.
-
Většina operací nevyhazuje výjimky, čímž uspoří neustálé hlídání (try-catch).
-
Na chybu se lze zeptat pomocí checkError().
-
- PrintWriter
-
pro znaková data
-
Vytváří se ze znakového proudu, lze specifikovat kódování.
-
Příklad s nastavením kódování:
PrintWriter writer = new PrintWriter(new OutputStreamWriter(output, "UTF-8"));
Načítání vstupů (například z klávesnice)
-
Třída
java.io.Scanner
- pro čtení z obecného proudu (ale i stdin) -
Nebo třída
java.io.Console
- přímo vždy z konzoly
Čtení z konzoly je typické pro aplikace spouštěné z příkazové řádky a není tudíž vždy možné - např. když spouštíme na serveru, v cloudu… |
Repl.it demo ke třídě Scanner
Repl.it demo ke třídě Console
Serializace objektů I
Postupy ukládání a rekonstrukce objektů:
- serializace
-
postup, jak z objektu vytvořit sekvenci bajtů perzistentně uložitelnou na paměťové médium (disk) a později restaurovatelnou do podoby výchozího javového objektu.
- deserializace
-
je právě zpětná rekonstrukce objektu
Aby objekt bylo možno serializovat, musí implementovat prázdné rozhraní java.io.Serializable. |
Serializace objektů II
-
Proměnné objektu, které nemají být serializovány, musí být označeny modifikátorem, klíčovým slovem, transient.
-
Pokud požadujeme "speciální chování" při (de)serializaci, musí objekt definovat metody:
private void writeObject(ObjectOutputStream stream) throws IOException
private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException
ObjectInputStream je proud na čtení serializovaných objektů.
Návrhový vzor Memento
-
(De)serializace souvisí návrhovým vzorem Memento:
-
Vzor umožňuje odložit si aktuální stav objektu a později ho obnovit.
-
(De)serializace v Javě tak může sloužit ke snadné implementaci tohoto vzoru.
-