Bevezetés
A grafikus felületek programozásánál már megtapasztaltuk, hogy a NetREXX-bôl néha hiányoznak bizonyos építôelemek, így ezeket a Java nyelvben található megfelelôjükkel kell helyettesíteni. Nincs ez másként a fájlkezeléssel sem. A NetREXX (a say és az ask utasítás kivételével) nem definiál adat be- és kivitelre alkalmas utasításokat, így csak a Java nyelv által megvalósított I/O osztályokra hagyatkozhatunk.
Adatfolyamok
A Java-ban beviteli adatfolyamnak (input stream) azokat az objektumokat, amelyek adatot szolgáltatnak. Azokat az objektumokat, amelyekbe adatot írhatunk, kiviteli adatfolyamnak (output stream) nevezzük. Ezen két fô típuson túlmenôen a Java nyelv több mint 20 alváltozatot különböztet meg. Az adatfolyamokra egyszerre több forrás vagy nyelô is rácsatlakozhat. Egy adatokat szolgáltató objektumra (input stream) például egyszerre csatlakozhat egy nyomtató, egy fájl és egy hálózati port. A streamek szempontjából mindegy, hogy milyen objektumok csatlakoznak hozzájuk, mivel azok általános I/O metódusokon keresztül használják ôket.
A fájl osztály
A fájlok és könyvtárak használata során a fájl (File) osztályból képzett objektumokkal kell dolgoznunk. A fájl osztály számtalan metódust tartalmaz, amellyel információt gyûjthetünk az állományokról, vagy éppen kedvünkre manipulálhatjuk ôket. Elsô példaprogramunk a fájl osztály metódusainak használatát mutatja be:
/* lecke07a.nrx - fájl és könyvtár-információk megjelenítése */
parse arg fileName .
if fileName = "" then do
say "Adja meg a vizsgálandó fájl vagy könyvtár nevét!"
filename = ask
end
f1 = File(fileName) -- elkészítjük a fájlobjektumot
if f1.exists() = 0 then do
say 'A megadott fájl ('filename') nem létezik!'
exit 8
end
say "-- A rendszerrel kapcsolatos információk:"
say "Path elválasztó :" f1.pathSeparator
say "Path elválasztó karakter :" f1.pathSeparatorChar
say "Elválasztó :" f1.separator
say "Elválasztó karakter :" f1.SeparatorChar
say
say "-- Az állománnyal kapcsolatos információk:"
say "Olvasható:" f1.canRead()
say "Irható :" f1.canWrite()
say "Könyvtár :" f1.isDirectory()
say "Fájl :" f1.isFile()
say "Méret :" f1.length()
say "Módosítva:" f1.lastModified() "=" Date(f1.lastModified())
say "Abszolút :" f1.isAbsolute()
say "Teljes elérési út:" f1.getAbsolutePath()
say "Kanonikus elérési út:" f1.getCanonicalPath()
say "Elérési út:" f1.getPath()
parent1 = f1.getParent()
if parent1 = null then parent1 = "nincs szülô (null)"
say "Szülô :" parent1
say "Név :" f1.getName()
say "ToString :" f1.toString()
say "Hash kód :" f1.hashCode()
if f1.isDirectory() then do
say
say "-- A könyvtár tartalma:\n"
list1 = f1.list()
if list1.length = 0 then say "A könyvtár üres"
else
loop i = 0 to list1.length -1
f2 = File(f1.getAbsolutePath()' 'f1.separator' 'list1[i])
if f2.isDirectory() then say "Könyvtár:" list1[i]
else say " Fájl:" list1[i]
end
end
A példaprogram bekéri a vizsgálandó állomány nevét és létrehozza a hozzátartozó fájlobjektumot. Amennyiben az objektum sikeresen létrejön, kilistázzuk az összes, az objektummal kapcsolatos változó értékét, illetve a használható metódusok által visszaadott értékeket. Könyvtárak esetén azok tartalmát is kilistázza a program. Az elôzô példában bemutatott metódusok mellett a további metódusok állnak még rendelkezésre:
| Metódus: | Feladat: |
| delete() | Törli a fájlobjektumot |
| equals(object) | Összehasonlítja a referált és a paraméterként megadott objektumot |
| list(fájlszûrô) | Visszaadja mindazon fájlok listáját, amelyek eleget tesznek a fájlszûrônek |
| mkdir() | Elkészíti a referált objektum nevének megfelelô könyvtárat |
| mkdirs() | Elkészíti a referált objektum nevének megfelelô könyvtárat, beleértve a fôkönyvtárakat is |
| renameTo(File) | Átnevezi az objektumot a paraméterben megadott névnek megfelelôen |
Soronkénti olvasás és írás
Gyakran van szükségünk arra, hogy egy adatfolyamot soronként olvassunk vagy írjunk. Az olvasásra használhatjuk a BufferedReader osztály readLine metódusát. Az írásra többek között a PrintWriter osztály println metódusa is alkalmas, amely a platformtól függôen automatikusan a helyes sorlezárást használja. UNIX rendszereken az új sor karaktert, Windows vagy OS/2 alatt pedig az új sor és a kocsi vissza karaktereket használja az automatikus lezárásra. Az alábbi példaprogram az említett metódusokat alkalmazza arra, hogy kikeresse valamint kiírja a CONFIG.DEV fájlba és a képernyôre a C:\CONFIG.SYS fájlban található DEVICE kezdetû sorokat:
/* lecke07b.nrx - DEVICE kezdetû sorok kiválogatása */ filename = "C:/CONFIG.SYS" output = "CONFIG.DEV" say "Fájl:" filename "->" output inFile = FileReader(filename) -- a bemeneti fájl source = BufferedReader(inFile) -- az olvasási osztály outFile = FileWriter(output) -- a kimeneti fájl dest = PrintWriter(outFile) -- az írási osztály loop forever textline = source.readLine() -- beolvassuk a bemeneti fájl sorait if textline = null then leave -- elértük a fájl végét? parse textline word1 "=" . if word1 = "device" then do -- ez egy DEVICE sor? dest.println(textline) -- kiírjuk a találatot end end source.close() -- bezárjuk a fájlokat dest.close() say "A következô DEVICE sorokat tartalmazza a C:\\CONFIG.SYS:" source = BufferedReader( FileReader(output) ) -- kiolvassuk az eredményt loop until textline = null textline = source.readLine() if textline \= null then say textline end
Mazochisták használhatják a BufferedWriter osztályt is a PrintWriter helyett, ekkor viszont nekik kell gondoskodni a sorok helyes lezárásáról, amelyet a newline metódussal tehetnek meg.
Bájtorientált fájlkezelés
A bájtorientált fájlkezelés azt jelenti, hogy nem soronként, hanem bájtonként olvassuk (és esetenként írjuk is) az adatfolyamokat. A lassúsága miatt nem nagyon találkozunk ezzel a megoldással, de talán mégis érdemes néhány percet szentelni erre a témára is. A bájtonkénti beolvasásra a DataInputStream osztály readUnsignedByte metódusát használhatjuk. A DataInputStream objektumot a FileInputStream objektumból, ez utóbbit pedig a File objektumból állíthatjuk elô:
parse arg inFileName . -- bekérünk egy fájlnevet infile = File(inFileName) -- ebbôl lesz egy bemeneti fájlobjektum source = DataInputStream(FileInputStream(inFile)) -- ebbôl lesz az adatforrás objektuma ch = Rexx source.readUnsignedByte() -- beolvassuk az elsô bájtot
Adatorientált fájlkezelés
Gyakran van szükség olyan változók beolvasására vagy kiírására, amelyek valamilyen alap-adattípust (integer, float, stb.) képviselnek. A DataInputStream és DataOutputStream osztályok a következô metódusokat valósítják meg, amelyekkel ezek az adatok is eredményesen kezelhetôk:
| Metódus: | Feladat: |
| writeInt | Integer (egész szám) kiíratása |
| writeFloat | Float (lebegôpontos szám) kiíratása |
| writeUTF | Unicode karakter kiíratása |
| readInt | Integer (egész szám) beolvasása |
| readFloat | Float (lebegôpontos szám) beolvasása |
| readUTF | Unicode karakter beolvasása |
Ezen metódusok elônye, hogy platformfüggetlenek és kompakt adatfájlokat lehet velük megvalósítani. Hátrányuk, hogy a képzett adatfájl szemmel nem értelmezhetô (bináris), így a hibakeresés nehezebb, mint a szöveges fájlokat használó metódusok esetében.
Egy másik - REXX programozók számára sokkal kézenfekvôbb megoldás - a REXX karakterláncok használata az adatok ki- és bevitelére. Ennek a megközelítésnek megvan az az elônye, hogy a különbözô adattípusokat nem kell külön kezelnünk, hiszen egy REXX karakterlánc bármelyik adattípust képes befogadni. A másik kedvezô tulajdonság az, hogy a REXX karakterláncok kiíratása után keletkezô adatállomány olvasható (szöveges) formátumú lesz. Az alábbi példaprogramban felhasználók (customer) adatait adminisztráljuk. A különbözô adatmezôket a "\t" karakterrel választjuk el egymástól. Az egyszerûség kedvéért a programba kódolt négy vevô adatait íratjuk ki a lecke07c.dat fájlba. A második lépésben visszaolvassuk a kiírt adatokat, majd pedig megjelenítjük a képernyôn:
/* lecke07c.nrx -- adat ki- és bevitel REXX sztringekkel */
class lecke07c
Properties constant
yes = boolean 1
no = boolean 0
method main(args = String[]) static
custDB = Customer[4] -- helyet foglalunk 4 vevô számára
-- a vevô objektumok megadása
custDB[0] = Customer(101, "Ueli Wahli", "U.S.A.", 500.5, 25, yes)
custDB[1] = Customer(102, "Peter Heuchert", "Germany", 400.4, 30, yes)
custDB[2] = Customer(103, "Frederik Haesbrouck", "Belgium", 350.9, 24, no)
custDB[3] = Customer(104, "Norio Furukawa", "Japan", 250.5, 39, no)
-- kiíratjuk az objektumok paramétereit a lecke07c.dat fájlba
os = PrintWriter(FileWriter("lecke07c.dat"))
os.println(custDB.length) -- az objektumok száma
loop i = 0 to custDB.length - 1
custdata = custDB[i].getCustNo() || " \t" | | custDB[i].getName() || " \t" -
custDB[i].getAddress() || " \t" | | custDB[i].getHourly() || " \t" -
custDB[i].getWork() || " \t" | | custDB[i].getBool()
os.println(custdata)
end
os.close()
-- visszaolvassuk az objektumok értékeit az adatfájlból
is = BufferedReader(FileReader("lecke07c.dat"))
n = is.readLine() -- beolvassuk a feldolgozandó objektumokat
loop i = 1 to n
parse is.readLine() xcustno " \t" xname " \t" xaddress " \t" xhourly " \t" xwork " \t" xbool
say xcustno.left(4) xname.left(20) xaddress.left(10) (xhourly*xwork).right(10) xbool
end
is.close()
-- a Customer osztály definiálása
class Customer
properties private
custNo = String
name = String
address = Rexx
hourly = float
work = int
bool = boolean
method Customer(aCustNo=String, aName=String, aAddress=rexx, aHourly=float, aWork=int, aBool=boolean)
custNo = aCustNo; name = aName; address = aAddress
hourly = aHourly; work = aWork; bool = aBool
method getCustNo() returns String
return custNo
method getName() returns String
return name
method getAddress() returns Rexx
return address
method getHourly() returns float
return hourly
method getWork() returns int
return work
method getBool() returns boolean
return bool
Objektum-orientált fájlkezelés
A JDK 1.1-ben bevezetett szérializáció segítségével egyetlen adatobjektumot olvashatunk be vagy írhatunk ki a readObject és a writeObject metódusokkal. Az objektumok szérializálását az ObjectOutputStream és ObjectInputStream adatfolyammá történô transzformálással érhetjük el. Ezt azonban csak akkor tehetjük meg, ha osztályunkba elôzôleg beépítettük a java.io.Serializable interfészt. Az alap-adattípusok esetében ezzel készen is vagyunk. Speciális adattípusok esetében viszont implementálnunk kell még a writeObject és readObject metódusokat is. Mivel a NetREXX 1.0-s kiadásában a REXX karakterlánc osztály még nem volt szérializálható (az 1.1-ben már az), ezért ezt a módszert nem alkalmazhatjuk az elôzô program Customer osztályának átalakítására. A következô példaprogramban ezért Customer2 néven kissé újraírtuk ezt az osztályt. A példaprogram futása után vizsgáljuk meg egy hexa editorral a köztes fájl (lecke07d.dat) tartalmát! Azt fogjuk tapasztalni, hogy a fájl most már bináris formátumú és információt tartalmaz az osztályról (Customer2) is!
/* lecke07d.nrx */
class lecke07d
Properties constant
yes = boolean 1
no = boolean 0
method main(args=String[]) static
custDB = Customer2[4] -- lefoglaljuk a helyet 4 vevô számára
custRD = Customer2[] -- visszaolvasunk "x" vevôt
-- definiáljuk a vevôket
custDB[0] = Customer2(101, "Ueli Wahli", "U.S.A.", 500.5, 25, yes)
custDB[1] = Customer2(102, "Peter Heuchert", "Germany", 400.4, 30, yes)
custDB[2] = Customer2(103, "Frederik Haesbrouck", "Belgium", 350.9, 24, no)
custDB[3] = Customer2(104, "Norio Furukawa", "Japan", 250.5, 39, no)
-- kiíratjuk az objektumot fájlba
say "A "custDB.length "vevô adatainak kiíratása folyik..."
os = ObjectOutputStream(FileOutputStream("lecke07d.dat"))
os.writeInt(custDB.length) -- a vevôk száma
os.writeObject(custDB) -- egyetlen függvényhívással kiíratjuk az összes vevôt
os.flush()
os.close()
-- visszaolvassuk az objektumot
say "A visszaolvasás folyik..."
is = ObjectInputStream(FileInputStream("lecke07d.dat"))
n = is.readInt() -- a vevôk száma
say "A visszaolvasott "n" vevô adatai:"
custRD = Customer2[] is.readObject() -- egyetlen hívással olvassuk be az összes vevôt
loop i = 0 to custRD.length - 1
say custRD[i].getCustNo() (Rexx custRD[i].getName()).left(20) -
(Rexx custRD[i].getAddress()).left(10) -
(Rexx custRD[i].getHourly() * custRD[i].getWork()).right(10) -
custRD[i].getBool()
end
is.close()
-- A Customer osztály szérializált változata
class Customer2 implements Serializable
properties private
custNo = String
name = String
address = String -- Rexx string nem megengedett!
hourly = float
work = int
bool = boolean
method Customer2(aCustNo=String, aName=String, aAddress=rexx, aHourly=float, aWork=int, aBool=boolean)
custNo = aCustNo; name = aName; address = aAddress
hourly = aHourly; work = aWork; bool = aBool
method getCustNo() returns String
return custNo
method getName() returns String
return name
method getAddress() returns Rexx
return address
method getHourly() returns float
return hourly
method getWork() returns int
return work
method getBool() returns boolean
return bool
Különleges helyzetek lekezelése
Az adatfolyamok olvasása és írása közben érhetnek bennünket kellemetlen meglepetések. Elôfordulhat például, hogy olvasás közben az adatfolyam végére érünk. Fájlok esetében ezt nevezik fájlvég (EOF) kondíciónak, s ezt kétféleképpen is lekezelhetjük. A legkézenfekvôbb eljárás az olvasáshoz használt metódus visszatérési értékének ellenôrzése. Ha például a BufferedReader vagy DataInputStream osztály readLine metódusát használjuk, akkor a null visszatérési értéket kapjuk EOF kondíció esetén. A BufferedReader osztály read metódusa viszont már -1-et ad vissza ebben az esetben. Az alábbi kódrészlet azt mutatja, hogy hogyan lehet az EOF eseményre reagálni a visszatérési érték ellenôrzésével:
/* eof.nrx */
source = BufferedReader(FileReader("test.dat"))
loop forever
textline = source.readLine()
if textline = null then leave -- <=== itt hagyjuk el a hurkot EOF esetén
say textline
end
A másik lehetséges módszer a Java kivételkezelô mechanizmusán (exception handling) alapszik. A DataInputStream szinte mindegyik metódusa kivételes eseményt (exception) jelez ha egy REXX változóhoz a null értéket rendeljük. Ezt a következôképpen használhatjuk ki a hurokból való kiszállásra:
/* eof2.nrx */
source = DataInputStream(FileInputStream("test.dat"))
loop forever
textLine = Rexx source.readLine()
say textLine
catch NullPointerException -- <=== itt szállunk ki a hurokból
end
REXX GYÍK:
K1. Mi a célja az unicode karaktereknek?
V1. Az unicode karakter nem más, mint egy olyan karakter, amelyet nem 8 hanem 16 bájton tárolnak. Az unicode karakterekkel már el lehet tárolni az összes európai nyelv által használt karaktert, így elvileg nincs szükség a jelenleg még oly sok bosszúságot okozó kódtáblázatokra. Sajnos a 16 bájt még mindig nem ad annyi kombinációt, hogy a keleti nyelvek (pl. kínai) által használt összes karaktert is el lehessen tárolni.
Gyakorlat:
1. Készítsen egy NetREXX programot, amelyik beolvas egy fájlt és kiírja a fájl hexadecimális reprezentációját!
2. Készítsen egy NetREXX programot, amely kikeresi az összes sort egy fájlból, amelyek egy elôre megadott szót tartalmaznak!
| Kádár Zsolt 2000. 04. 25. | [ Elôzô lecke | Következô lecke | Tartalom ] |