====== Práce se soubory ====== Pro čtení a zápis z/do souboru slouží streamy (proudy). Chceme-li data číst, použijeme vstupní proud, chceme-li zapisovat, použijeme výstupní proud. Proud si lze představit jako potrubí, z něhož tečou data. Informace k nám přicházejí v pořadí, v němž jsou zapsány v souboru. Jiné pořadí není možné. Streamy tedy umožňují pouze sekvenční zpracování. ==== Vstup ze souboru ==== Souborům je věnováno speciální cvičení, zde si pouze ukážeme, jak jednoduše lze z **textového** souboru přečíst data, která si tam připravíme příslušnou metodou. Pozor, desetinné číslo se musí vložit s desetinnou čárkou či tečkou, v závislosti na OS. Pokud bychom využili metodu **printf**, bude vše v pořádku, protože se přizpůsobí. Metoda **println** nezoohledňuje národní zvyklosti ve formátování dat (desetinná čísla, datum, měna, ...). Zápis dat: public static void zapisDat() { //zapíšeme textově data try { Writer out = new BufferedWriter(new OutputStreamWriter( new FileOutputStream("soubor.txt"), "UTF8")); out.write("Pepa 22 68,5"+System.lineSeparator()); out.close(); } catch (UnsupportedEncodingException e) { } catch (IOException e) { } } **Funkce System.lineSeparator() vraci znak konce radku odpovidajici aktualnimu operacnimu systemu, na kterem bezi JVM.** Klauzule **try-catch** slouží k zachycení a ošetření výjimek (závad programu, za které programátor nemůže). V bloku **try** (hlídač) mohou nastat např. výjimky typu //UnsupportedEncodingException// (kódování znaků není podporováno), //IOException// (chyba při práci se souborem - nedostatečná práva pro zápis, apod.). V blocích **catch** (řešitel) by měly být tyto závady ošetřeny. Výjimkám bude později věnována celá přednáška. A jedna metoda pro načtení dat: public static void nactiData() { try { Scanner s = new Scanner(new FileInputStream("soubor.txt"), "UTF8"); System.out.println("Jméno: "+s.next()+" věk: "+s.nextInt()+" váha: " + s.nextDouble()); s.close(); } catch (IOException e) { } } ===== Práce s proudem ===== Práce s proudem (streamem) vypadá takto: - otevření streamu, - čtení/zápis dat z/do streamu, - zavření streamu. Třídy pro práci se proudy jsou v balíku java.io. Proudy se dělí na bytové a znakové. Bytové proudy umožňují čtení a zápis po bytech, zatímco znakové pracují se znaky, tj. typem char, který je zobrazen na 16 bitech - Java používá pro kódování znaků kód UCS2. U standardních proudů lze podle názvu poznat, zda jsou bytové či znakové. Bytové mají v názvu Stream, znakové buď Reader, jde-li o vstupní proud, nebo Writer, jde-li o proud výstupní. Vstupní a výstupní bytové proudy jsou odlišeny také: vstupní mají v názvu Input, výstupní Output. Protože proudy mohou pracovat i s jinými zdroji dat, nejen se soubory, rozlišují se proudy podle toho, s jakým zdrojem dat pracují. Souborové proudy poznáme podle toho, že mají v názvu File. Takže např. FileInputStream je bytový proud pro čtení ze souboru a FileWriter je znakový proud pro zápis do souboru. Příklad čtení z bytového proudu: FileInputStream fis = new FileInputStream("input.bin"); int i; while ((i = fis.read()) != -1) System.out.println(i); fis.close(); Příklad zápisu do bytového proudu: FileOutputStream fos = new FileOutputStream("output.bin"); for (int i=0; i < 10; i++) fos.write(i); fos.close(); Čtení po znacích ze znakového proudu: FileReader fr = new FileReader("input.txt"); int c; while ((c = fr.read()) != -1) System.out.print((char) c); fr.close(); Zápis řetězce do znakového proudu: FileWriter fw = new FileWriter("output.txt"); fw.write(args[0]); fw.close(); ===== Spojování proudů, třídy DataInputStream a DataOutputStream ===== Proudy lze vzájemně spojovat, tj. výstup jednoho proudu lze napojit na vstup druhého proudu. Podle tohoto hlediska lze proudy rozdělit na 2 skupiny: proudy pro přímé napojení na zdroj dat (v našem případě soubor) a proudy pro zpracování výstupu z jiného proudu. Chceme-li propojit dva proudy, předáme první proud jako parametr do konstruktoru druhého proudu. Např. DataInputStream a DataOutputStream slouží pro čtení a zápis primitivních datových typů: double price = ...; int unit = ...; DataOutputStream dos = new DataOutputStream( new FileOutputStream("invoice.txt")); dos.writeDouble(price); dos.writeChar('\t'); dos.writeInt(units); dos.close(); DataInputStream dis = new DataInputStream( new FileInputStream("invoice.txt")); price = dis.readDouble(); dis.readChar(); unit = dis.readInt(); dis.close(); ==== Třídy InputStreamReader a OutputStreamWriter ==== Zvláštní postavení mají proudy InputStreamReader a OutputStreamWriter, které slouží ke konverzi bytů na znaky a opačně. InputStreamReader čte byty z bytového streamu, konvertuje je na znaky a ty předává na výstup. Do proudu typu OutputStreamWriter zapisujeme znaky, proud je konvertuje na byty a ty předává na výstup. ==== Kódování nejen češtiny ==== Při vytváření těchto proudů můžeme říci jaké kódování se má použít pro konverzi: FileInputStream fis = new FileInputStream("test.txt"); InputStreamReader isr = new InputStreamReader(fis, "UTF8"); Zde používáme UTF8, další možnosti jsou např. ISO8859_2 či Cp1250. Pokud žádné kódování nezadáme, použije se kódování, které je nastaveno jako defaultní (jaké to je závisí na operačním systému a jeho lokalizaci, v XP MS Windows lokalizovaných do našeho národního prostředí můžete očekávat Cp1250, Solaris ve škole bude mít UTF8 nebo ISO8859_2). Pokud přenášíte soubory mezi různými operačními systémy, musíte zajistit jejich konverzi (pokud nepoužíváte UTF8), nebo při načítání musíte specifikovat kódování. Tyto problémy nebudete mít pokud nebudete používat národní znaky. ==== Bufferované proudy ==== Další skupinou proudů jsou bufferované proudy, které umožňují číst několik bytů či znaků najednou. Např. ke čtení textu po řádcích slouží proud BufferedReader: String s; InputStreamReader isr = new InputStreamReader(System.in); BufferedReader br = new BufferedReader(isr); while ((s = br.readLine()) != null) { System.out.println(s); } br.close(); ==== Třída File ==== Pro manipulaci se soubory a adresáři slouží třída File. Umožňuje přejmenování a smazání souboru, ověření, zda soubor existuje a zjištění informací o souboru, jako např. zda můžeme do souboru zapisovat či zda jej můžeme číst. Jde-li o adresář, máme možnost zjistit jeho obsah: String dirName = ...; File dir = new File(dirName); String[] files = dir.list(); for (int i=0; i < file.length; i++) System.out.println(files[i]); ==== Cesta k aktuálnímu adresáři ==== Pokud budete chtít specifikovat jméno souboru a jeho umístění relativně vzhledem k umístění programu, je třeba se dotázat operačního systému na adresář, odkud je Váš program spouštěn. Předpokládejme, že náš soubor vstup.txt je uložen v adresáři adresar. Tyto příkazy by měly zajistit správné nastavení cesty v Solarisu, GNU Linuxu i v MS Windows. String userDir = System.getProperty("user.dir"); String fileSeparator = System.getProperty("file.separator"); celaCesta = userDir +fileSeparator+ "adresar"+fileSeparator+"vstup.txt"; Proměnná userDir reprezentuje adresář odkud je náš projekt spouštěn, proměnná fileSeparator reprezentuje symbol oddělovače souborů - hodnota závislá na operačním systému. ===== Třída RandomAccessFile ===== Dále máme k dispozici třídu RandomAccessFile, která umožňuje přistupovat k datům v souboru v libovolném pořadí(random, zde neznamená náhodné, protože nechceme náhodně vybraná data, ale jedna konkrétní z námi určené pozice). byte[] b = "approved".getBytes(); RandomAccessFile raf = new RandomAccessFile("raf.txt","rw"); raf.seek(24); raf.write(b); raf.close();