9. hét    Input-output műveletek


Tartalom:

    9.1. Egyszerű konzol I/O műveletek
    9.2. Karakteres fájlok kezelése
        9.2.1. Karakteres fájlok olvasása
        9.2.2. Karakteres fájlok írása
    9.3. Bináris fájlok feldolgozása
    9.4. Szöveges állományok kezelése
    9.5. Állományok közvetlen elérése
    9.6. Tesztkérdések
    9.7. Feladatok


9.1. Egyszerű konzol I/O műveletek

    Minden programozási nyelv alapfeladatai közé tartozik a külvilággal való kommunikáció, amely a legtöbb esetben az adatok olvasását jelenti egy bemeneti eszközről (pl. billentyűzet), ill. az adatok írását egy kimeneti eszközre (pl. képernyő, fájl). A Java nyelv az i/o műveleteket adatfolyamokon (ún. streameken) keresztül, egységesen valósítja meg. Egy dologra kell csak figyelni, hogy bemeneti vagy kimeneti csatornát kezelünk-e. A csatornák kezelése Java osztályokon keresztül valósul meg, azonban ezek nem részei a Java alap eszközkészletének, ezért importálni kell őket a java.io csomagból.

    A konzol kezelésére a Java három adatfolyamot biztosít. Ezeket az adatfolyamokat nem kell megnyitni vagy bezárni, e műveletek automatikusan történnek.

    A standard adatfolyamok a következők:


    A konzol I/O műveletek használata esetén a
java.util csomagból importálnunk kell a megfelelő osztályokat.

    Az alábbi kis egyszerű program a standard be- és kimeneti konzol használatát mutatja be.  A program egy egész számot vár a billentyűzetről, amit utána megjelenít a képernyőn:

Forráskód: io1.java


    Az
sc nevű objektum a Scanner osztály példánya lesz, amely osztály a beolvasáshoz a System.in (billentyűzet input) paramétert használja. Az egész típusú a adattag az sc példány nextInt() metódusa által megkapja a beírt számot, a standard System.out.println() metódus pedig megjeleníti a képernyőn.

    Több szám bevitele esetén a számok közé alapesetben szóközöket kell beiktatni, amely jel a Java-ban felülbírálható.  Az alábbi program az előző továbbfejlesztése. Több (akárhány!) egész számot vár - vesszővel elválasztva - a billentyűzetről, majd kiírja a darabszámukat és az összegüket:

Forráskód: io_token.java


    Ebben az esetben a teljes input sort beolvassuk a
sor nevű stringbe, majd a StringTokenizer osztály segítségével a megadott határolójel mentén adategységekre (tokenekre) feldarabolva feldolgozzuk azt. Nézzünk egy programfutási eredményt is:

 

9.2. Karakteres fájlok kezelése

    A Java a fájlokat is streamként kezeli, ezért most is importálni kell a java.io csomag néhány osztályát. Az i/o típusok karakteresek (a Java az UTF-8-as Unicode karakterkódolást használja) és binárisak (8 bites bájtformátum) lehetnek.

    A karakteres állományok kezelését három fő részre osztjuk:

     A megnyitás művelete során valamely stream osztály objektuma jön létre (példányosítással), amelyhez hozzárendeljük a megnyitandó állományt. A fájlkezelő műveleteket try-catch-finally kivételkezelő blokkszerkezetbe kell foglalni, mert sok probléma adódhat használatuk során (pl. nem létező állomány, betelt háttértár, stb.). A következő példaprogramok mindegyikében lesz ilyen szerkezet, de a jobb áttekinthetőség miatt e forráskódok csak a minimálisan kötelező kivételkezelést tartalmazzák. A kivételkezelésről bővebben a 14. heti tananyagban lesz szó.

 

9.2.1. Karakteres fájlok olvasása

    Karakteres állományok olvasására a FileReader osztály read() metódusa áll rendelkezésre. A metódus használatához implementálni kell a FileNotFoundException (a fájl nem található) kivételkezelő osztályt, és ennek ősét, az IOException (általános i/o hiba) osztályt is.

    A következő példa egy szöveges állományból egyenként beolvassa a karaktereket, majd megjeleníti őket a képernyőn. Ha nem jelölünk ki pontos elérési utat, akkor a fájlnak a Java projekt főkönyvtárában kell lennie. A fájl végét a -1-es értékkel érzékeli a read() metódus.

Forráskód: io.java


     A forrásállomány (
szoveg.txt, letöltés után nevezzük át szöveg.txt-re!) az UTF-8-as Unicode kódolású szöveg mentését is lehetővé tevő Jegyzettömb programmal készült. A 2. és 3. sorban használt kis- és nagybetűs tesztszövegben (árvíztűrő tükörfúrógép) minden magyar ékezetes betű szerepel, így könnyen ellenőrizhető a fájlkezelő metódusok betűhelyes működése.

    A forrásállomány Jegyzettömb által mutatott tartalma :


    A képernyőn megjelenő szöveg:


9.2.2. Karakteres fájlok írása

    Karakteres állományok írására a FileWriter osztály write() metódusa áll rendelkezésre. Ehhez a metódushoz az IOException (általános i/o hiba) osztályt kötelező implementálni.

    Az alábbi program egy string típusú változóban tárolt kétsoros szöveget - karakterenként feldolgozva - ír ki egy szöveges állományba. Ha nem jelölünk ki pontos elérési utat, akkor a fájl a Java projekt főkönyvtárában jön létre. Mivel könnyen lekérdezhető a string hossza, a karakterek feldolgozása nem okoz gondot.

Forráskód: io2.java


    Nézzük a létrejött állomány (
mentes.txt, eredeti neve mentés.txt) tartalmát! Szerencsére a fájlba írás karakterkódolásával semmi gond, és a Jegyzettömb is helyesen jeleníti meg:

 

9.3. Bináris fájlok feldolgozása

    A bináris adatokat tartalmazó állományok kezelését a FileInputStream és a FileOutputStream osztályok biztosítják. Mindkét osztályt importálni kell a java.io csomagból. Metódusaik a szokásos read(), write() és close(). Használatuk hasonló a karakteres állományoknál látottakhoz.

    Hozzunk létre egy binary.dat nevű bináris állományt! Bájtjainak értéke decimálisan 65-től 90-ig terjedjen!

Forráskód: io_bin.java


    Bővítsük előző programunk Main metódusát! A keletkezett állományt olvassuk be, és írjuk ki a bájtjai által reprezentált karaktereket a képernyőre szóközzel elválasztva! Ne feledjük, hogy az importáló utasításokat bővíteni kell a java.io.FileInputStream osztállyal!

Forráskód: io_bin.java


    A képernyőn a várt eredmény látható:

 

9.4. Szöveges állományok kezelése

    Szöveges állományok feldolgozása gyakran nem karakterenként, hanem nagyobb részenként, pl. soronként történik. Ehhez a Java ún. pufferelt fájlkezelési támogatást nyújt. Külön osztályok támogatják a byte- és string-szintű műveleteket:

Byte-szintű osztályok:

String-szintű osztályok:

    Számunkra most a stringek kezelése fontosabb, ezért nézzünk rá egy egyszerű példát! Írjunk ki egy sima szöveges állományba (text.txt) néhány UTF-8-as kódolású sort, majd az állományt visszaolvasva jelenítsük meg azokat a képernyőn is!

    Írás a PrintWriter osztály println() metódusával:

Forráskód: io_txt.java


    Olvasás a
BufferedReader osztály readLine() metódusával:

Forráskód: io_txt.java


    Figyeljük meg, hogy a
BufferedReader osztály nem tud egy fájlt közvetlenül megnyitni, hanem csak a FileReader osztály egy példányát. Az eredmény meggyőző:

 

9.5. Állományok közvetlen elérése

    Az előző alfejezetek adatfolyam-kezelő műveletei mind soros hozzáférésűek voltak, tehát az állományok írása ill. olvasása az elejüktől a végükig sorban történt. Azonban lehetőség van arra is, hogy egy fájl tartalmához közvetlenül is hozzáférhessünk. Ezt a java.io csomag RandomAccessFile osztálya biztosítja. Ez az osztály egyszerre alkalmas olvasásra és írásra is, csupán a példányosítás során egy paraméterrel jelezni kell, hogy milyen műveletet kívánunk alkalmazni az objektumra:

    Az így megnyitott állományokon használhatók a szokásos read() és write() metódusok, valamint a közvetlen elérést támogató metódusok:

    Fontos tudni, hogy a read() és write() metódusok meghívása a fájlmutató (aktuális pozíció a fájlon belül - file pointer) értéket automatikusan megnöveli 1 egységgel, tehát nem szükséges manuálisan léptetni. Ezek a metódusok és a close() (fájl bezárása) is megkövetelik az IOException kivételkezelő osztály használatát.

    Hozzunk létre egy állományt (random.txt), amely az angol ABC nagybetűit tartalmazza!

Forráskód: io_random.java


    Majd minden ötödik karaktert - a fájlban elfoglalt pozíciójukkal együtt - jelenítsük meg a képernyőn!

Forráskód: io_random.java


    A
raf.length() metódussal lehet lekérdezni a fájl méretét. Figyeljük meg, hogy a kiírás során a getFilePointer() pozíciólekérdező metódus megelőzi az olvasó metódust, így a helyes értéket adja, de mégis meg kell növelni az értékét 1-gyel, mivel a pozíciók számozása 0-tól kezdődik. Az eredmény:


    A kimenet utolsó sorát az alábbi kiegészítés állítja elő. A fájl minden ötödik karakterét cseréljük le annak kisbetűs változatára, majd jelenítsük meg a fájl összes karakterét!

Forráskód: io_random.java


    A betűcserét végrehajtó ciklus minden ötödik betűre pozícionál, majd az ott található betű kódjához 32-t adva éri el, hogy kisbetű lesz belőle. A kiolvasás eltolja az aktuális pozíciót, ezért szükséges visszalépni egyet. A betűk képernyőn való megjelenítését megelőzi a fájl kezdőpozíciójába ugrás, majd egy sima (fájlvégjel-figyeléses) feldolgozó ciklus kiírja a karaktereket.

 

9.6. Tesztkérdések

 

9.7. Feladatok

9.7.1. Kérjünk be két egész számot a billentyűzetről, és írjuk ki a képernyőre a szorzatukat!

Megoldás

 

9.7.2. A FileWriter és a FileReader osztályok segítségével írjuk ki az abc.txt fáljba az angol abc kisbetűit! Utána olvassuk be a fájlt, és az abc-t - nagybetűsre alakítva - írjuk ki a képernyőre!

Megoldás

 

9.7.3. A PrintWriter és a BufferedReader osztályokat felhasználva írjuk ki a hét napjait soronként a napok.txt szöveges fájlba! Ezután olvassuk be a fájlt, és a napokat egy sorban, vesszővel elválasztva jelenítsük meg a képernyőn!

Megoldás

 

9.7.4. A RandomAccessFile osztály metódusai segítségével cseréljük le az abc.txt fájl minden 3. betűjét nagybetűsre, majd a teljes fájlt írjuk ki a képernyőre!

Megoldás