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é.

Table 1. Vstupně/výstupní proudy

typ dat

vstupní

výstupní

binární

InputStream

OutputStream

znakové

Reader

Writer

Vstupy a výstupy v Javě

Je toho příliš mnoho?

streams-joke

API proudů

  • Téměř vše ze vstupních/výstupních tříd a rozhraní je v balíku java.io.

  • Počínaje Java 1.4 se rozvíjí alternativní balík java.nio (New I/O), zde se ale budeme věnovat klasickým I/O z balíku java.io.

  • Blíže viz dokumentace API balíků java.io, java.nio.

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!

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.

// 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 .

  • Obdobně pro výstupní proudy — lze vytvořit Writer z OutputStream.

Na zjištění, jestli je možné z čtenáře číst, se používa metoda reader.ready().

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();

Konverze souboru na proud

Soubor se dá lehce transformovat na proud znaků.

File file = new File("some_file.txt");

// file converted to InputStream
FileInputStream fis = new FileInputStream(file);

// file converted to OutputStream
FileOutputStream fis = new FileOutp2utStream(file);

Soubor se otevřel, proto je nutno ho pak zavřít! (později)

Znakové výstupní proudy

  • Jedná se o protějšky k vstupním proudům, názvy jsou konstruovány analogicky (např. FileReaderFileWriter).

  • 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, metoda println 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ů.

Odkazy