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