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:

  1. otevření streamu,
  2. čtení/zápis dat z/do streamu,
  3. 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();

courses/b0b36pjv/tutorials/08/soubory.txt · Last modified: 2018/02/06 08:43 (external edit)