Gyakorlati alapok II.

Bemenet - kimenet

 

Csatorna-műveletek nemcsak közegészségügyiek részére

 

Az előző, A csatorna (stream) fejezetben a legfontosabb tulajdonságaik alapján katalogizáltuk a csatornákat. 4 olyan alaposztályt határozhatunk meg, amelyek képesek önálló input-output-műveletek elvégzésére:

Mindenekelőtt kiindulópontként ismételjük meg egyik, már publikált Java-kódot, amely szöveget olvas be egy fájlból:

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

import java.io.*;

public class Main {
public static void main(String[] args) {
    FileReader reader = null;
    try {
        reader = new FileReader("D:\\Kertész leszek.txt");
        BufferedReader br = new BufferedReader(reader);
        String line = br.readLine();

        while(line != null) {
            System.out.println(line);
            line = br.readLine();
        }

    } catch (FileNotFoundException fnfe) {
        System.out.println("A fájl nem található!");
        fnfe.printStackTrace();
    } catch (IOException ioe) {
        ioe.printStackTrace();
    } finally {
        try {
            if (reader != null) {
                reader.close();
            }
        } catch (IOException ioe) {
            ioe.printStackTrace();
            }
        }
    }
}

 

Végeredmény:

Kertész leszek, fát nevelek,
kelő nappal én is kelek,
nem törődök semmi mással,
csak a beojtott virággal.

Minden beojtott virágom
kedvesem lesz virágáron,
ha csalán lesz, azt se bánom,
igaz lesz majd a virágom.

Tejet iszok és pipázok,
jóhíremre jól vigyázok,
nem ér engem veszedelem,
magamat is elültetem.

Kell ez nagyon, igen nagyon,
napkeleten, napnyugaton -
ha már elpusztul a világ,
legyen a sírjára virág.

 

Több szempontból is nagyon tanulságos a példa:

  1. már említett módon az input-output-műveletek elég kényes, nagy hibaszázalékú műveletek, ezért többségüket biztonsági okokból kötelező valamilyen szintű kivételkezelés alá vonni.

  2. Az input-output-rutinokat importálnunk kell (import java.io.*;).

  3. A csatornát önálló szabványos eszközökkel objektumként először létre kell hozni (reader = new FileReader("D:\\Kertész leszek.txt");), csakis ezután érhetők el metódusai.

  4. A csatornát használata után kötelező lezárni (reader.close();), hogy felszabaduljanak a lefoglalt erőforrások.

  5. Kimeneti csatornák esetén a close() művelet egyúttal meghívja a flush() metódust is, amely a maradék, a memóriában még bufferelt (ideiglenesen tárolt) adatot is kiírja a csatornára.

  6. A close() művelet meghívása után a csatorna megszűnik. Bármilyen további, rá történő hivatkozás IOException kivételt vált ki.

Ha nem kívánunk bajlódni kivételkezeléssel, akkor már említett módon a kivételt "továbbdobhatjuk" a JVM vagy valami (valaki) más felé (throws IOException). Ez nyilvánvalóan leegyszerűsíti a kódot, ám egyúttal el is veszítjük a hiba lekezelésének lehetőségét:

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

import java.io.*;

public class Main {
public static void main(String[] args) throws IOException {
    FileReader reader = null;
    reader = new FileReader("D:\\Kertész leszek.txt");
    BufferedReader br = new BufferedReader(reader);
    String line = br.readLine();
    while(line != null) {
        System.out.println(line);
        line = br.readLine();
        }

    reader.close();
    }
}

 

Végeredmény:

Kertész leszek, fát nevelek,
kelő nappal én is kelek,
nem törődök semmi mással,
csak a beojtott virággal.

Minden beojtott virágom
kedvesem lesz virágáron,
ha csalán lesz, azt se bánom,
igaz lesz majd a virágom.

Tejet iszok és pipázok,
jóhíremre jól vigyázok,
nem ér engem veszedelem,
magamat is elültetem.

Kell ez nagyon, igen nagyon,
napkeleten, napnyugaton -
ha már elpusztul a világ,
legyen a sírjára virág.

 

Az író-olvasó műveletek -függetlenül adattípusuktól, azaz, hogy bájt-, vagy karakterszervezésűk-, belső működésük szerint háromfélék lehetnek:

  1. a művelet 1 db adategységet olvas be vagy ír ki (1 bájt vagy 1 karakter),

  2. a művelet 1 tömbnyi adategységet olvas be vagy ír ki,

  3. a művelet 1 résztömbnyi adategységet olvas be vagy ír ki, amelynek 3 bemeneti paramétere van: tömb, résztömb kezdőpozíciója és a résztömb hossza.

Bájtszervezésű Reader-csatorna esetén:

 

Bájtszervezésű Writer-csatorna esetén:

Karakterszervezésű Reader-csatorna esetén:

 

Karakterszervezésű Writer-csatorna esetén:

A Java-API-ban a fent ismertetett Reader-Writer-tömbműveletek a következő hivatalos formátumban állnak rendelkezésre, például:

Hibás paraméterezés esetén a Reader-tömbműveletek a rájuk jellemző kivételeket válthatják ki, amelyek tipikusan vagy az IndexOutOfBoundsException vagy a NullPointerException, Writer-műveletek pedig az IndexOutOfBoundsException nevű kivételt.

 

www.informatika-programozas.hu - Ezt most meg kell tanulni!

 

A Reader-műveleteknél meg kell különböztetnünk üres és véget ért csatornákat. Az üres csatorna nem tartalmaz adatot, de még vár újabb adatok érkezésére. Ezzel ellentétben a véget ért csatorna már elküldte a lezáró vezérjelet, amely jelzi, hogy több adat nem fog érkezni.

 

Most nézzük meg egy ellentétes irányú, de szintén karakter típusú adatcsatorna működését (Writer). Az alábbi futtatható Java-kód PrintWriter objektuma (writer) a dedikált helyen (itt a D: meghajtó) létrehoz egy Hello.txt nevű fájlt és feltölti a "Helló Világ!" szöveggel:

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

import java.io.*;

public class Main {
public static void main(String[] args) throws IOException {
    PrintWriter writer = new PrintWriter(new FileWriter("D:\\Hello.txt"));
    writer.println("Helló Világ!");
    writer.close();
    }
}

 

Végeredmény:

Hello.txt nevű fájl "Helló Világ!" szöveggel a D: meghajtón

 

Most pedig egyesítsük a Reader-, és Writer-műveleteket egy rövid, futtatható Java-kódban! Az alábbi kód átmásolja a Kertész leszek.txt nevű fájl tartalmát a létrehozott üres Hello.txt nevű fájlba:

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

import java.io.*;

public class Main {
public static void main(String[] args) throws IOException {
    FileReader reader = null;
    reader = new FileReader("D:\\Kertész leszek.txt");
    PrintWriter writer = new PrintWriter(new FileWriter("D:\\Hello.txt"));
    BufferedReader br = new BufferedReader(reader);
    String line = br.readLine();
    while(line != null) {
        writer.println(line);
        line = br.readLine();
        }
    writer.close();
    reader.close();
    }
}

 

Végeredmény:

Szövegtartalom a Hello.txt nevű fájlban a D: meghajtón:

Kertész leszek, fát nevelek,
kelő nappal én is kelek,
nem törődök semmi mással,
csak a beojtott virággal.

Minden beojtott virágom
kedvesem lesz virágáron,
ha csalán lesz, azt se bánom,
igaz lesz majd a virágom.

Tejet iszok és pipázok,
jóhíremre jól vigyázok,
nem ér engem veszedelem,
magamat is elültetem.

Kell ez nagyon, igen nagyon,
napkeleten, napnyugaton -
ha már elpusztul a világ,
legyen a sírjára virág.

 

Most pedig teszteljük le az egyik Writer-tömbműveletet! A fenti kódot úgy módosítjuk, hogy 25 karakteres blokkméretenként másolja át a szövegtartalmat. Ehhez az egyik beépített függvényt használjuk fel (read(karakterTomb, 0 blokkMeret). A függvény visszatérési értéke a karakterTombben tárolt karaktermennyiség, -1 ha üres (nincs benne karakter). A látható végeredményhez azonban (itt 25 karakteres sorhosszak) a Kertész leszek című fájlt úgy kell módosítanunk, hogy ne legyen benne semmilyen új sor vagy sortörés:

 

Kertész leszek, fát nevelek,kelő nappal én is kelek,nem törődök semmi mással,csak a beojtott virággal.Minden beojtott virágomkedvesem lesz virágáron,ha csalán lesz, azt se bánom,igaz lesz majd a virágom.Tejet iszok és pipázok,jóhíremre jól vigyázok,nem ér engem veszedelem,magamat is elültetem.Kell ez nagyon, igen nagyon,napkeleten, napnyugaton -ha már elpusztul a világ,legyen a sírjára virág.

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

import java.io.*;

public class Main {
public static void main(String[] args) throws IOException {
    int blokkMeret = 25;
    int hossz = blokkMeret;
    char[] karakterTomb = new char[blokkMeret];
    FileReader reader = null;
    reader = new FileReader("D:\\Kertész leszek.txt");
    PrintWriter writer = new PrintWriter(new FileWriter("D:\\Hello.txt"));
    BufferedReader br = new BufferedReader(reader);
    while(hossz != -1){
    hossz = br.read(karakterTomb, 0, blokkMeret);
    if(hossz == -1){
        break;
    }
    if (hossz == blokkMeret) {
        System.out.println(karakterTomb);
        writer.println(karakterTomb);
    } else {
    for(int i = 0; i < hossz; i++) {
        System.out.print(karakterTomb[i]);
        writer.print(karakterTomb[i]);
        }
    }
}
    writer.close();
    reader.close();
    }
}

 

Végeredmény:

Kertész leszek, fát nevel
ek,kelő nappal én is kele
k,nem törődök semmi mássa
l,csak a beojtott virágga
l.Minden beojtott virágom
kedvesem lesz virágáron,h
a csalán lesz, azt se bán
om,igaz lesz majd a virág
om.Tejet iszok és pipázok
,jóhíremre jól vigyázok,n
em ér engem veszedelem,ma
gamat is elültetem.Kell e
z nagyon, igen nagyon,nap
keleten, napnyugaton -ha
már elpusztul a világ,leg
yen a sírjára virág.

 

Az utolsó sor menedzselését nem túl elegáns, bár hatékony módon oldottuk meg: mivel az utolsó sor hossza nagy valószínűséggel kisebb lesz 25-nél, ahhoz, hogy a karakterTomb-ből csak a hasznos karaktereket kaphassuk vissza (egyébként ilyen sorokkal szembesülnénk)...

 

yen a sírjára virág.g,leg

 

...ezért for ciklusba téve külön kell foglalkoznunk vele:

 

for(int i = 0; i < hossz; i++) {
    System.out.print(karakterTomb[i]);
    writer.print(karakterTomb[i]);
    }
}
 

A felmerült probléma beépített függvények segítségével másképpen is megoldható, ekkor az Arrays osztály egyik beépített metódusát használjuk fel az utolsó sor másolásához (Arrays.copyOf(karakterTomb, hossz)). A függvény használatát kötelező importálás előzi meg (import java.util.Arrays;):

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

import java.io.*;

import java.util.Arrays;

public class Main {
public static void main(String[] args) throws IOException {
    int blokkMeret = 25;
    int hossz = blokkMeret;
    char[] karakterTomb = new char[blokkMeret];
    FileReader reader = null;
    reader = new FileReader("D:\\Kertész leszek.txt");
    PrintWriter writer = new PrintWriter(new FileWriter("D:\\Hello.txt"));
    BufferedReader br = new BufferedReader(reader);
    while(hossz != -1){
    hossz = br.read(karakterTomb, 0, blokkMeret);
    if(hossz == -1){
        break;
    }
    if (hossz == blokkMeret) {
        System.out.println(karakterTomb);
        writer.println(karakterTomb);
    } else {
    for(int i = 0; i < hossz; i++) {
        System.out.println(Arrays.copyOf(karakterTomb, hossz));
        writer.print(Arrays.copyOf(karakterTomb, hossz));
        }
    }
}
    writer.close();
    reader.close();
    }
}

 

Végeredmény:

Kertész leszek, fát nevel
ek,kelő nappal én is kele
k,nem törődök semmi mássa
l,csak a beojtott virágga
l.Minden beojtott virágom
kedvesem lesz virágáron,h
a csalán lesz, azt se bán
om,igaz lesz majd a virág
om.Tejet iszok és pipázok
,jóhíremre jól vigyázok,n
em ér engem veszedelem,ma
gamat is elültetem.Kell e
z nagyon, igen nagyon,nap
keleten, napnyugaton -ha
már elpusztul a világ,leg
yen a sírjára virág.

 

A fenti példákban a bemeneti és kimeneti adatok azért egyeznek, mert a bemeneti és kimeneti karakterkódolás azonos volt. Ez az azonosság az alapértelmezés, bár ne lepődjünk meg, ha néha ilyen jellegű hibákba futunk bele: a magam részéről már többször kellett karakterkódolási hibaelhárítással foglalkoznom az Eclipse környezeten belül. Most nézzünk egy érdekes példát a különböző input-output karakterkódolási ütközésekre!

 

Kis elméleti bevezető:

A java.io csomagon belül az InputStreamReader és OutputStreamWriter osztályok olyan különleges karaktercsatornák, amelyek "legalul" bájtcsatornát vesznek igénybe karaktereik továbbítására. A bájtinformáció továbbítása után a karakterek automatikusan Unicode-szabvány szerint konvertálódnak vissza karakterekké. Az is már említésre került a Karakter című fejezetben, hogy a Java-rendszeren belül a karakterek int típusú számok formájában vannak tárolva, ebből következően lehetőségünk van a karaktereket int formátumban is megadni.

Az alábbi futtatható Java-kód ezt fogja bebizonyítani, illetve azt is megmutatja, hogy a különböző kódtábla-beállítások garantáltan zavart okoznak a karakterkódolásban. A ÷ karakter az ISO Latin-1 kódtáblában (8859_1) 247-es kódú, ám az ISO Latin/héber kódtáblában (8859_8) már 186-os:

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

import java.io.*;

public class Main {
public static void main(String[] args) throws IOException {
    char karakter = '÷';
    int karakterKod = (int) karakter;
    System.out.println(karakter);
    System.out.println(karakterKod);

    PipedInputStream inputStream = new PipedInputStream();
    PipedOutputStream outputStream = new PipedOutputStream(inputStream);
    Reader reader = new InputStreamReader(inputStream, "8859_1");
    Writer writer = new OutputStreamWriter(outputStream, "8859_8");
    writer.write(karakter);
    writer.flush();
    System.out.println(reader.read());
    }
}

 

Végeredmény:

÷

247

186

 

A különböző kódtáblák voltaképpen az Unikód-tábla egy-egy nyelvspecifikus részhalmaza, például latin/görög, latin/héber kódtábla. Bár az Unikód-tábla az összes lehetséges betűt tartalmazza, ám a legtöbb kódtábla-hivatkozás (például: (inputStream, "8859_1");) annak csak egy részhalmazát határozza meg. Így lehetséges az, hogy hibával szembesülünk, ha a kódtábla által nem definiált betűt használunk fel. Az alábbi futtatható Java-kódban a magyar ő betűt használjuk fel, amit a pontatlan kódtábla-definíció miatt a rendszer nem tud értelmezni:

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

import java.io.*;

public class Main {
public static void main(String[] args) throws IOException {
    String szo = "piperkőc";
    System.out.println(szo);
    Writer writer = new OutputStreamWriter(System.out, "8859_1");
    writer.write(szo);
    writer.close();
    }
}

 

Végeredmény:

piperkőc

piperk?c

 

A megoldás a helyes kódtábla beállítása (itt 8859_2 használata):

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

import java.io.*;

public class Main {
public static void main(String[] args) throws IOException {
    String szo = "piperkőc";
    System.out.println(szo);
    Writer writer = new OutputStreamWriter(System.out, "8859_2");
    writer.write(szo);
    writer.close();
    }
}

 

Végeredmény:

piperkőc

piperkőc

 

Összességében megállapíthatjuk, hogy a csatornák adatátviteli útvonalat biztosítanak A pontból B pontba. A fenti példákban egy dedikált fájl éppúgy lehetett adatforrás, mint adatnyelő. Ám az adatátvitelnek van egy további fontos, szabályozó aspektusa is: jogosultság adatforrás-adatnyelő elérésére, módosítására. Ezzel java.io csomagon belül a FilePermission osztály foglalkozik.