Vstupy a výstupy v Javě Tomáš Pitner, Radek Ošlejšek, Marek Šabo 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ě Zdroj: http://www.tutorialspoint.com/java/ Je toho příliš mnoho? 1 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! 2 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ů. 3 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ř. 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, 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: 4 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. 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 5 • 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")); 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 Java Oracle Tutorial — essential Java I/O 6