Vood, failid Vood Voogude kasutamise abil saab sarnaselt andmeid lugeda nii failist, Internetist, sõnest, baidimassiivist, teiselt lõimelt kui ka klaviatuurilt. Samuti saab voo abil andmeid ühtmoodi väljastada. Vaid voo loomisel piisab märkida, kuhu ta suunatakse ning meetodid lugemiseks ning kirjutamiseks on edaspidi sarnased. Kui on vaja andmete päritolu- või sihtkohta muuta, piisab vaid muutusest voo loomisel. Nii saab näiteks Internetiühenduseta arvutis katsetada võrgust lugevat programmi, andes talle tegelikult andmed ette failist. Põhilised sisendi-väljundi klassid asuvad paketis java.io. Tähtede lugemiseks mõeldud klassid lõppevad sõnaga Reader, kirjutamiseks - Writer. Näiteks failist tähtede lugemiseks sobib FileReader, faili kirjutamiseks FileWriter. Baitide kirjutamiseks on OutputStream, lugemiseks InputStream. Faili puhul siis vastavalt FileOutputStream ning FileInputStream. Sisimas käib andmevahetus baitide kaupa, kasutamismugavuse huvides on aga loodud selle ümber mitmesuguseid kesti. Nii aitab näiteks DataOutputStream muuta baitideks nii täisarvud, reaalarvud kui tõeväärtused. Andmevoog faili Järgnevas näites loon kõigepealt failivoo, kuhu saan andmeid kirjutada vaid ühe baidi kaupa. Mähisklassi DataOutputStream abil loon andmevoo ka muude andmetüüpide kirjutamiseks. Meetod writeInt kirjutab täisarvu (4 baiti), writeDouble, writeFloat, writeLong, writeChar, writeBoolean igaüks vastavat tüüpi väärtuse. Kui failist lugema hakata, siis peab arvestama andmete järjekorda ja tüüpi. import java.io.*; public class Voog1{ public static void main(String argumendid[]) throws IOException{ FileOutputStream failivoog=new FileOutputStream("arvud.dat"); DataOutputStream andmevoog=new DataOutputStream(failivoog); andmevoog.writeInt(25); andmevoog.close(); } } Andmevoog massiivi Kui soovida faili asemel mälus paiknevasse massiivi kirjutada, siis tuleb vaid sihtkoht teisiti määrata. Nii voogu kirjutamine kui voo sulgemine jääb samaks. import java.io.*; public class Voog2{ public static void main(String argumendid[]) throws IOException{ ByteArrayOutputStream massiivivoog=new ByteArrayOutputStream(); DataOutputStream andmevoog=new DataOutputStream(massiivivoog); andmevoog.writeInt(25); andmevoog.close(); System.out.println("Masiivis on "+ massiivivoog.toByteArray().length+" baiti."); } } Siin on lühiduse huvides vaid üks arv voogu kirjutatud, tegelikult aga võib sinna palju rohkem kirjutada. Faili kirjutamisel on piiriks arvuti kettamaht, mällu kirjutamisel mälumaht. Kui aga voo teisest otsast keegi loeb, siis polegi voogu läbivate andmete hulk piiratud. Vajadusel võib voogu kirjutamiseks luua omaette meetodi, mis saab parameetriks voo ning lisab sinna oma andmed. Lühem andmevoo loomine Kuna kirjutamisel pole meil eraldi osutit failivoole vaja, siis võime failivoo luua alles andmevoo konstruktoris, andes esimese osuti otse teisele. import java.io.*; public class Voog1a{ public static void main(String argumendid[]) throws IOException{ DataOutputStream andmevoog=new DataOutputStream( new FileOutputStream("arvud.dat") ); andmevoog.writeInt(25); andmevoog.close(); } } OutputStream Juhul, kui siiski soovime madalama taseme voo osutit eraldi kasutada (näiteks pärast samasse väljundvoogu mõne muu andmevoo abil kirjutada), siis võime ta kirjeldada lihtsalt kui väljundvoo osuti. Kuna OutputStream on kõigi väljundvoogude ülemklass, siis tohib ta osutada nii faili, mällu, võrku kui mujale minevale voole. DataOutputStream aga kasutab nagunii vaid kõigile väljundvoogudele ühiseid oskusi (s.t, et neisse saab baidi kaupa kirjutada). Siis saab sihtkoha märkida sobivasse sihtkohta voo avamisega. import java.io.*; public class Voog1b{ public static void main(String argumendid[]) throws IOException{ OutputStream voog=new FileOutputStream("arvud.dat"); DataOutputStream andmevoog=new DataOutputStream(voog); andmevoog.writeInt(25); andmevoog.close(); } } Kui faili kirjutamisel panna failivoo konstruktori parameetriks vaid failinimi, siis luuakse uus fail ning hakatakse sinna alates algusest kirjutama sõltumata sellest, kas enne selle nimega fail leidus või mitte. Enne olnud andmed lähevad lihtsalt kaduma. Kui aga soovitakse faili lõppu kirjutada, siis tuleb failivoo konstruktorisse lisada tõeväärtustüüpi muutuja näitamaks, kas lisada faili lõppu või luua uus tühi fail. S.t. new FileOutputStream("arvud.dat", true) jätab eelmise faili sisu alles ning lisab uued andmed faili lõppu. Andmete lugemine Failist aitab baite lugeda FileInputStream ning baite lihttüüpideks muundada DataInputStream. Viimase abil saab lihttüüpideks muundada ka mujalt (massiivist, Internetist, ...) tulevad baidid sarnaselt nagu DataOutputStream oskas muid andmetüüpe baitideks muundada. Lugemisel on iga tüübi lugemiseks vastav meetod: readDouble, readBoolean jne. import java.io.*; public class Voog3{ public static void main(String argumendid[]) throws IOException{ DataInputStream sisse=new DataInputStream( new FileInputStream("arvud.dat") ); System.out.println(sisse.readInt()); sisse.close(); } } Baitide lugemine Algsest sisendvoost on võimalik ka otse baite lugeda. Seda võib tarvis minna siis, kui meil ongi baite vaja. Kui me ei tea saabuvate andmete hulka ning struktuuri, ka siis jääb üle neid vaid baidikaupa lugeda. Mõnikord on baidikaupa hea kontrollida, et kas andmed tulevad ikka korrektselt. Baite vaid omale teada oleva algoritmi abil muundades saame saadetavad andmed juhusliku lugeja jaoks salakirjaks muuta. Kirjutades voogu otse baite, peaks olema võimalik ka oma spetsiifiliste andmete puhul andmemuundamist kiiremaks muuta. Baidivoost lugemiseks on InputStream'i käsk read, mis väljastab baidi väärtuse täisarvuna tüübist int vahemikus 0 kuni 255. Voo lõppedes väljastatakse -1, selle järgi saab teada, et voost tulevad andmed on otsas. Siin näites trükitakse järgemööda ekraanile failis olevate baitide väärtused. Sellist programmi võib mõnikord faili sisu kontrollimiseks täiesti vaja minna. Asendades FileInputStream'i mõne muu sisendvooga, saame kontrollida sealt saabuvate baitide väärtusi. import java.io.*; public class Voog3a{ public static void main(String argumendid[]) throws IOException{ InputStream sisse=new FileInputStream("arvud.dat"); System.out.println("Failis olevate baitide väärtused:"); int nr=sisse.read(); while(nr!=-1){ System.out.print(nr+" "); nr=sisse.read(); } } } Mällu lugemine Kui soovime kõiki voost saabuvaid andmeid üheskoos töödelda, tuleks nad kuidagi üheskoos enese käsutusse saada. Kohaliku faili puhul on võimalik vähemalt faili pikkus enne andmete lugemist teada saada, võrgust saabuvate andmete puhul pole aga mingeid kindlaid vahendeid pikkuse küsimiseks. Üheks võimaluseks on saabuvad andmed kirjutada mällu baidivoogu, seejärel nad sealt baidimassiiviks muuta ja sealtkaudu töötlema hakata. import java.io.*; public class Voog3b{ public static void main(String argumendid[]) throws IOException{ InputStream sisse=new FileInputStream("arvud.dat"); ByteArrayOutputStream malu=new ByteArrayOutputStream(); int nr=sisse.read(); while(nr!=-1){ malu.write(nr); nr=sisse.read(); } byte[] massiiv=malu.toByteArray(); System.out.println("Failist loeti "+massiiv.length+" baiti."); } } Internetis paikneva faili lugemine Baidivoo abil saab enese käsutusse soovikohase Internetti välja pandud faili. Luues aadressile vastava URLi objekti ning avades selle ühenduse, võib ühenduselt küsida sisendvoo, millelt saab baidi kaupa vastava faili andmeid lugeda. Siin näites kirjutan saabuvad baidid töökataloogis asuvasse faili, programmi töö tulemusena saan kaugel asuvast failist omale koopia. import java.io.*; import java.net.URL; public class Voog3c{ public static void main(String argumendid[]) throws IOException{ String aadress="http://www.tpu.ee/logo.jpg"; InputStream sisse= new URL(aadress).openConnection().getInputStream(); OutputStream valja=new FileOutputStream("pilt.jpg"); int nr=sisse.read(); while(nr>=0){ valja.write(nr); nr=sisse.read(); } sisse.close(); valja.close(); } } Teksti lugemine Teksti saab rea kaupa lugeda klassi BufferedReader abil. Selle konstruktor vajab parameetriks klassi Reader järglast, näiteks FileReaderit, kui soovitakse failist lugeda. Voo lõpu puhul antakse lugemisel rea väärtuseks null (tühiväärtus). import java.io.*; public class Voog4{ public static void main(String argumendid[]) throws IOException{ BufferedReader sisse=new BufferedReader( new FileReader("andmed.txt") ); String rida=sisse.readLine(); System.out.println("Faili esimene rida on: "+rida); System.out.println("Nüüd järjestikku käik faili read:"); while(rida!=null){ System.out.println(rida); rida=sisse.readLine(); } } } InputStreami (baidivoo) saab Readeriks (tähevoog) muundada klassi InputStreamReader abil. Näiteks võrgust lugemisel annab pistik vaid baidivoo, sellest teksti lugemisel on aga soovitav muuta see enne tekstivooks. Socket sc=new Socket("www.postimees.ee", 13); BufferedReader sisse=new BufferedReader( new InputStreamReader(sc.getInputStream()) ); Teksti kirjutamine Teksti soovitatakse kirjutada klassi PrintWriter abil. Tema loomisel võib talle parameetriks anda nii OutputStreami kui Writeri. PrintWriter tunneb ise ära, millise vooga on tegemist ning vastavalt sellele saadab talle andmeid. import java.io.*; public class Voog5{ public static void main(String argumendid[]) throws IOException{ PrintWriter valja=new PrintWriter(new FileWriter("ruudud.txt")); for(int nr=1; nr<=100; nr++){ valja.println(nr*nr); } valja.close(); } } Tavaliselt kogub PrintWriter välja saadetavad andmed kokku ning alles puhvri täitumisel või voo sulgemisel saadab andmed kohale. Nii kulub vähem ressursse, sest sageli (näiteks Internetis) kulub ühe baidi või terve bloki andmete saatmiseks ühepalju energiat, sest ülekantavad blokid on kindla pikkusega ning juhul kui saadetakse vähem andmeid kui blokki mahub, siis täidetakse ülejäänud bloki sisu "aherainega". Andmete teele saatmiseks saab voole öelda flush(). Et teade alati kohe peale kirjutamist teele läheks, selleks tuleb PrintWriteri konstruktorisse lisada true. Socket sc=new Socket("lin2.tpu.ee", 79); PrintWriter valja=new PrintWriter(sc.getOutputStream(), true); valja.println("jaagup"); BufferedReader sisse=new BufferedReader( new InputStreamReader(sc.getInputStream()) ); String rida=""; while((rida=sisse.readLine())!=null)System.out.println(rida); Sõnevoog Kuna sõne pikkusel java keeles pole piirangut, siis saab ka suuremad andmed (näiteks failitäie teksti) sõnesse paigutada. Sõnesse kirjutamiseks sobib StringWriter, kuhu saab kirjutusvoo suunata samuti nagu mõne muu Writeri (näiteks FileWriteri) sisse. Sõne saab sellest kätte meetodiga toString(). StringReaderi abil saab pikast sõnest lugeda nagu voost, konstruktorina tuleb talle anda sõne, mille andmeid soovitakse voona lugema hakata. Toru Lõimede vahel andmevahetuseks saab luua toru. Selle tulemusena saab ühest otsast andmeid voona kirjutada, teisest lugeda. Kasutatakse näiteks siis, kui üks pool toodab andmeid (näiteks küsib kasutaja käest), teine pool aga töötleb neid (näiteks võrdleb ülejäänutega). Sellisel juhul ei pea kasutaja enne uue tulemuse ootama, et arvuti oleks eelmised suutnud ära töödelda. Samas saab töötlemise lõim töötada kasutajast sõltumatult, ootamata tema klahvivajutusi. Torusse saatmiseks on klass PipedOutputStream, torust lugemiseks PipedInputStream. Toru algus ja ots tuleb omavahel ühendada meetodi connect abil. zip-faili loomine Ka zip-faile saab java abil luua ilma, et peaks ise faili struktuuri uurima. ZipOutputStreamile tuleb anda väljundvoog pakitud andmete väljasaatmiseks, teda ennast võib aga kasutada nagu iga muud väljundvoogu, näiteks PrintWriteri abil temasse andmeid kirjutades. Meetod putNextEntry teatab, et nüüd hakkavad tulema järgmise arhiivi lisatava faili andmed. Parameetriks tuleb anda ZipEntry, mis sisaldab andmeid lisatava faili kohta, lihtsamal juhul vaid selle nime. import java.io.*; import java.util.zip.*; public class Voog6{ public static void main(String argumendid[]) throws IOException{ ZipOutputStream zo=new ZipOutputStream( new FileOutputStream("nimed.zip") ); PrintWriter valja=new PrintWriter(zo, true); zo.putNextEntry(new ZipEntry("nimed.txt")); valja.println("Juku"); valja.println("Kaarel"); zo.putNextEntry(new ZipEntry("kirjeldus.txt")); valja.println("Korvpallimeeskonna varumängijad"); valja.close(); } } Sarnasel põhimõttel on võimalik kirjutada ka jar-formaadis arhiivifaile. Samuti saab ise luua filtri, mis andmed meile sobivalt muudab. Kui kirjutada filtrit, mis saadaks tekstist edasi vaid numbrid, tuleb lihtsalt iga tähe puhul vaadata, kas tegemist on numbriga ning siis vastavalt ta kas edasi saata või mitte. Voogude kokkuliitmine SequenceInputStream aitab kokku liita mitmest voost tulevad andmed. Senikaua võetakse esimesest voost, kuni see on tühi, siis minnakse järgmise voo juurde. Viimase voo ammendumisega saab ka SequenceInputStream tühjaks. Isendite lugemine ja kirjutamine Voogu on võimalik kirjutada ja sealt lugeda ka terveid objekte (ehk isendeid) klasside ObjectInputStream ning ObjectOutputStream abil. Nii saab säilitada või mujale mööda voogu edasi anda näiteks punkte, pilte või ka keerulisemate komponentide olekuid ilma, et peaks teadmagi, kuidas komponendid tehtud on. import java.io.*; import java.awt.Point; import java.util.Date; public class Voog7{ public static void main(String argumendid[]) throws IOException{ ObjectOutputStream valja=new ObjectOutputStream( new FileOutputStream("objektid.dat") ); valja.writeObject(new Point(3, 2)); valja.writeObject(new String("Kirjutamise aeg")); Date praegu=new Date(); valja.writeObject(praegu); valja.close(); } } import java.io.*; import java.awt.Point; import java.util.Date; public class Voog7a{ public static void main(String argumendid[]) throws Exception{ ObjectInputStream sisse=new ObjectInputStream( new FileInputStream("objektid.dat") ); Point p=(Point)sisse.readObject(); String s=(String)sisse.readObject(); Date aeg=(Date)sisse.readObject(); sisse.close(); System.out.println(p+" "+s+" "+aeg); } } Ka klasse (ning koos nendega alamprogramme) on võimalik voogu mööda transportida. Nii on võimalik omale võrku mööda kohale laadida meetodid, mida kohalikus masinas olemas pole. Juhupöördusfail Voo abil on andmeid võimalik vaid järjest kirjutada. Nii võib ühest otsast andmeid alles luua, teisest aga juba lugeda. Kui aga sooviksime voogude abil failis mõnda väärtust asendada, tuleks meil kogu fail ümber kirjutada, asendades vajaliku väärtuse. Juhupöördusfaili (RandomAccessFile) abil saab lugeda ning kirjutada faili etteantud positsioonilt. Kui näiteks soovin failis baidi nr. 1000 väärtust suurendada ühe võrra, siis tuleb mul panna osuti sellele kohale, lugeda väärtus, arvutada uus väärtus, panna osuti kirjutuskohale (ehk samale kohale kust ennist lugesin) ning kirjutada uus väärtus faili. Juhupöördusfaili saab vastavate meetoditega (writeInt, writeBoolean, readInt, readBoolean jne.) kirjutada ning lugeda kõiki lihttüüpe, samuti sõnet. Konstruktoris tuleb määrata failinimi ning lisaks sellele teatada, kas soovitakse failist vaid lugeda või soovitakse sinna ka kirjutada. Faili lõpust edasi kirjutades muudetakse faili pikemaks. Faili keskele kirjutades kirjutatakse sinna jäänud andmed üle. Faili pikkuse saab lühemaks muuta käsuga setLength, sel juhul kaotatakse faili lõpu taha jäänud andmed. Jooksva kirjutus/lugemiskoha asukohta saab määrata meetodiga seek. Failid ja kataloogid Nii failide kui kataloogidega tegelemiseks on Java keeles klass File. Selle abil saab kontrollida faili pikkust, loomise ning muutmise aega, neid võrrelda, luua, kustutada ning ümber nimetada. Saab kontrollida, kas fail on olemas, kas sinna saab lugeda või kirjutada. Kataloogi puhul saab küsida samas kataloogis asuvate failide nimesid, luua alamkatalooge. Andmed faili kohta Klassi Fail1 main-meetodis uuritakse, kas fail nimega "nimed.txt" leidub. Juhul kui jah, siis kirjutatakse välja ta nimi, pikkus ning viimane muutmisaeg. import java.io.*; import java.util.Date; public class Fail1{ public static void main(String argumendid[]) throws IOException{ File fail=new File("nimed.txt"); if(fail.exists()){ System.out.println( "Faili "+fail.getName()+" pikkus on "+fail.length()+ " baiti. Viimati muudeti seda "+new Date(fail.lastModified()) ); } } } Kataloogi sisu päring Kataloogiosuti luuakse nagu failiosuti, s.t. antakse konstruktorisse vastava faili või kataloogi nimi. Ühe punktiga tähistatakse jooksvat kataloogi ning kahe punktiga ülemkataloogi. Alles spetsiifiliste meetodite rakendamisel kontrollitakse, kas tegemist on faili või kataloogiga, s.t. list() saab öelda vaid kataloogidele, meetod väljastab selles kataloogis asuvate failide nimed sõnemassiivina. import java.io.*; public class Kataloog1{ public static void main(String argumendid[]){ File kataloog=new File("."); // . on jooksev kataloog String failid[]=kataloog.list(); System.out.println("Kodukataloogis asuvad failid on:"); for(int i=0;i