Scrigroup - Documente si articole

     

HomeDocumenteUploadResurseAlte limbi doc
AccessAdobe photoshopAlgoritmiAutocadBaze de dateCC sharp
CalculatoareCorel drawDot netExcelFox proFrontpageHardware
HtmlInternetJavaLinuxMatlabMs dosPascal
PhpPower pointRetele calculatoareSqlTutorialsWebdesignWindows
WordXml


Clase de intrare-iesire in Java

java



+ Font mai mare | - Font mai mic



Clase de intrare-iesire in Java

Familia claselor Java pentru operatii de citire-scriere in mod text, din pachetul "java.io", este un bun exemplu de proiectare a unei familii de clase pentru a obtine cat mai multa flexibilitate cu un numar cat mai redus de clase, folosind in principal ideea claselor anvelopa ("wrapper").

Totusi, numarul claselor de I/E (clase abstracte si instantiabile) este destul de mare din urmatoarele motive:



- Clasele de intrare sunt separate de clasele de iesire, cu exceptia clasei RandomAccessFile, care suporta atat operatii de citire cat si operatii de scriere.

- S-a optat pentru patru tipuri de "fluxuri" de date ( I/O Streams"), care inseamna patru surse posibile de date pentru un program si respectiv patru destinatii posibile pentru rezultatele unui program. Aceste fluxuri sunt: fisiere disc, canal de I/E intre programe ("Pipe"), sir de caractere, bloc (vector) de octeti.

- Transferul brut de date intre un flux si o variabila din program poate fi combinat cu alte functii de "filtrare" sau de "prelucrare", cum ar fi : utilizarea unei zone tampon ("Buffer"),

numerotare linii, citire/scriere de numere in format intern, scriere de numere cu conversie din format intern in format extern, citire/scriere de obiecte s.a.

- In versiunea 1.0 se foloseau numai fluxuri de octeti, unitatea de transfer fiind de tipul byte.

Caracterele in format ASCII (ISO-Latin-1) ocupa si ele ce un octet. Din versiunea 1.1 s-au adaugat fluxuri de caractere Unicode ( pe ce 16 biti fiecare caracter), pentru a permite utilizarea unor alfabete cu pese 256 de semne diferite (pentru "internationalizare"). De aici a rezultat o dublare a numarului de clase de I/E, fiecare clasa pentru I/E pe octeti avand o clasa paralela pe caractere Unicode. Exista si doua clase "punte" intre cele doua familii de clase.

In practica se foloseste un numar relativ mic de clase, urmand anumite "stereotipuri" in combinarea claselor ce corespund unor fluxuri diferite cu clase ce asigura operatii diferite pe orice tip de flux. De asemenea se pot adauga noi prelucrari asociate cu datele citite sau scrise prin definirea de clase corespunzatoare si reutilizarea claselor de I/E existente.

1 Familia claselor InputStream, OutputStream

Un flux de date Java ('Stream') este orice sursa de date (la intrare) sau orice destinatie (la iesire). Anumite functii (metode) sunt aplicabile oricarui tip de flux si de aceea s-au definit doua clase abstracte (o sursa de date si o destinatie de date) din care sunt derivate toate cele 5 tipuri de fluxuri. Aceste clase se numesc InputStream si OutputStream.

S-au ales clase abstracte si nu interfete, pentru ca exista doua metode 'read' cu parametri, implementate si mostenite de subclase, precum si alte metode care nu sunt definite dar nici nu sunt abstracte.

Clasele abstracte InputStream si OutputStream corespund unor fluxuri generice de octeti, la care nu se precizeaza suportul datelor iar operatiile de I/E sunt operatii primitive la nivel de octet : citeste/scrie un octet, citeste/scrie un bloc de octeti. Metoda "read" (cu cateva forme diferite) este metoda de baza a clasei InputStream, iar metoda "write" ( cu aceleasi forme ca si "read") este metoda de baza a clasei OutputStream.

public abstract class InputStream

public int available() throws IOException // alte metode

}

Un flux abstract de intrare sau de iesire poate avea patru concretizari diferite, ceea ce conduce la 8 clase instantiabile, derivate din cele doua clase abstracte:

- FileInputStream, FileOutputStream pentru flux cu suport fisier disc.

- PipedInputStream, PipedOutputStream pentru canale de date intre fire de executie.

- StringBufferInputStream, StringBufferOutputStream pentru sir de caractere in memorie ca sursa sau destinatie a datelor.

- ByteArrayInputStream, ByteArrayOutputStream pentru vector de octeti (in memorie) ca sursa sau destinatie a datelor.

Clasele concrete de tip flux de citire sunt toate derivate din clasa InputStream, ceea ce se reflecta si in numele lor. Metoda 'read()' este redefinita in fiecare din aceste clase si definitia ei depinde de tipul fluxului: pentru un flux fisier 'read' este o metoda 'nativa' (care nu este

scrisa in Java si depinde de sistemul de operare gazda), pentru un flux sir sau vector de octeti 'read' preia urmatorul octet din sir(din vector).

Clasele flux concrete pot fi utilizate direct, dar numarul de operatii posibile este destul de redus : citire / scriere de octeti (fara alta zona tampon decat cea folosita de "driver"-ul de I/E din sistemul de operare gazda pentru consola si pentru fisiere disc), citire /scriere bloc de octeti folosind un vector de octeti, s.a.

Un exemplu simplu de utilizare directa a unor clase flux de I/E este copierea unui fisier disc, octet cu octet :

// copiere fisier disc octet cu octet

import java.io.*;

class FileCopy

}

Pentru fiecare din fluxurile concrete de date sunt posibile mai multe optiuni, sub forma unor operatii suplimentare, specifice clasei respective:

- Utilizarea unei zone tampon ("buffer") pentru reducerea timpului de citire/ scriere a unor fisiere mari (BufferInputStream, BufferOutputStream ).

- Posibilitatea de a "pune inapoi in flux" ultimul octet citit (PushBackInputStream).

- Posibilitatea de numerotare automata a liniilor citite (LineNumberInputStream).

- Operatii de citire/scriere numere de diferite tipuri (in format intern, fara conversie de format) si de citire linii de text de lungime variabila (DataInputStream, DataOutputStream).

- Conversie numere in format extern numai la scriere (PrintStream).

Cel mai simplu de folosit este clasa PrintStream, prin includerea unui obiect flux concret intr-un obiect PrintStream, ceea ce permite folosirea metodelor "print" si "println" cu orice argumente de un tip primitiv sau de un tip clasa. Pentru obiecte metoda "print" apeleaza automat metoda "toString" din clasa respectiva astfel incat se afiseaza un sir de caractere echivalent datelor continute in acel obiect. Exemplu :

// afisare la consola de numere

public static void main ( String arg[])

Variabila statica "out" din clasa System este de tipul PrintStream si este asociata ecranului consolei. Variabila statica "in" din clasa System, asociata tastaturii consolei, este de tipul abstract InputStream si nu poate fi folosita direct; ea trebuie "inglobata" intr-un obiect de tip DataInputStream pentru a putea citi si linii de text de lungime variabila. Exemplu:

// citire de la consola

DataInputStream cin = new DataInputStream (System.in);

String linie;

linie = cin.readLine();

Combinarea celor 8 clase sursa/destinatie cu optiunile de prelucrare asociate transferului de date se face prin intermediul claselor anvelopa ("wrapper"), care sunt numite si clase "filtru" de intrare-iesire. Daca s-ar fi utilizat derivarea pentru obtinerea claselor direct utilizabile atunci ar fi trebuit generate, prin derivare, combinatii ale celor 8 clase cu cele 4 optiuni, deci 32 de clase (practic, mai putine, deoarece unele optiuni nu au sens pentru anumite fluxuri de date). Pentru a folosi mai multe optiuni cu acelasi flux ar trebui mai multe niveluri de derivare si deci ar rezulta un numar si mai mare de clase direct utilizabile.

Principalele clase anvelopa sunt:

- FilterInputStream, FilterOutputStream pentru flux cu "filtrare" ( sau prelucrare) a datelor.

- ObjectInputStream, ObjectOutputStream pentru un flux de obiecte, cu operatii de citire si scriere de obiecte diverse.

- PrintStream, pentru scriere cu format (cu conversie) intr-un flux de iesire.

Clasele anvelopa de tip filtru sunt clase intermediare, din care sunt derivate clase care adauga operatii specifice : citire de linii de text de lungime variabila, citire / scriere de numere in format intern (binar), s.a.

O clasa anvelopa de I/E contine o variabila de tipul abstract OutputStream (InputStream), care va fi inlocuita cu o variabila de un tip flux concret (FileOutputStream, ), la construirea unui obiect de un tip flux direct utilizabil.

Clasa anvelopa FilterInputStream este derivata din InputStream si, in acelasi timp, contine o variabila de tip InputStream. Clasele derivate din FilterInputStream se numesc si clase de prelucrare, pentru ca adauga o 'prelucrare' fata de operatiile simple de citire.

public class FilterInputStream extends InputStream

// citirea unui octet

public int read () throws IOException

// alte metode

}

Metoda 'read' este o metoda polimorfica, iar selectarea metodei necesare se face in functie de tipul concret al variabilei 'in', primita ca parametru de constructor si memorata in variabila 'in' a clasei.

Nu se pot crea obiecte de tipul FilterInputStream deoarece constructorul clasei este de tip protected. In schimb, se pot crea obiecte din subclase ale clasei FilterInputStream.

Clasele DataInputStream, BufferedInputStream, LineNumberInputStream si PushbackInputStream sunt derivate din clasa FilterInputStream si sunt clasele de prelucrare a datelor citite. Cea mai folosita este clasa DataInputStream care adauga metodelor de citire mostenite si metode de citire a tuturor tipurilor primitive de date: 'readInt', 'readBoolean', readFloat', 'readLine', etc.

// fragment din clasa DataInputStream

public class DataInputStream extends FilterInputStream implements DataInput

public String readLine ( ) // citeste o linie

public byte readByte ( ) // citeste un octet

public int readInt ( ) // citeste un numar intreg

public float readFloat ( ) // citeste un numar real

// alte metode

}

La crearea unui obiect de tipul DataInputStream constructorul primeste un argument care poate fi de tipul InputStream, sau un tip derivat direct din InputStream, sau de un tip derivat din FilterInputStream.

Pentru a citi linii dintr-un fisier folosind o zona tampon, cu numerotare de linii vom folosi urmatoarea secventa de instructiuni:

import java.io.*;

class ExIO

}

De obicei nu se mai folosesc variabile intermediare la construirea unui obiect flux care include alte obiecte flux. Exemplu:

// citire linii , cu buffer, dintr-un fisier disc

class ExIO

}

Ordinea in care sunt create obiectele de tip InputStream este importanta : ultimul obiect trebuie sa fie de tipul DataInputStream, pentru a putea folosi metode ca 'readLine' si altele.

2 Utilizarea claselor de I/E din familia InputStream - OutputStream

Constructorii claselor de I/E primesc ca parametru fie un sir de caractere ce reprezinta numele fisierului, fie un obiect de tip File , care contine in el numele unui fisier simplu sau numele unui fisier director. Efectul unui constructor pentru o clasa flux de intrare este deschiderea fisierului mentionat (cu aruncarea unei exceptii de I/E daca fisierul nu este gasit), iar efectul unui constructor pentru o clasa flux de iesire este deschiderea pentru crearea unui nou fisier, cu stergerea unui eventual fisier cu acelasi nume existent anterior.

La operatiile de intrare-iesire pot apare erori (sau situatii exceptionale), dintre care unele pot fi tratate si altfel decat cu terminarea programului. In Java aceste erori produc "exceptii" iar exceptiile trebuie fie tratate acolo unde apar, fie "aruncate" mai departe. Din acest motiv orice functie Java care contine operatii cu fluxuri de I/E (cu exceptia clasei PrintStream) va trebui sa contina in antetul functiei clauza

throws IOException

Absenta clauzei "throws" este amendata de compilatorul Java cu eroare.

Programul urmator copiaza un fisier text, linie cu linie, folosind o zona tampon ("buffer") pentru reducerea timpului de acces la disc. Numele fisierelor sursa si destinatie sunt primite in linia de comanda (de apelare a interpretorului "java").

import java.io.*;

class ExBufIO

in.close(); out.close();

}

}

Clasele DataOutputStream si DataInputStream contin metode pentru scrierea si respectiv citirea unor variabile de orice tip primitiv (numeric, boolean, caracter) in format intern (fara conversie de format). Exemplul urmator creeaza un fisier de numere intregi, dupa care il citeste si afiseaza continutul.

import java.io.*;

class FileOfInt

in.close();

}

}

Exemplul anterior pune in discutie metoda de a recunoaste sfarsitul unui fisier la citire.

La citirea unei linii intr-o variabila de tip String sfarsitul de fisier este semnalat printr-un rezultat null al functiei "readLine". La citirea de octeti cu functia "read" (cu rezultat de tip int si nu de tip byte !) sfarsitul de fisier este raportat prin valoarea -1 de functia "read".

La citirea de numere dintr-un flux de tip DataInputStream metodele de citire (readByte, readInt, readFloat etc.) nu pot raporta sfarsitul de fisier deoarece nu exista o valoare care sa corespunda acestui caz special si care sa nu poata fi un numar binar citit normal din fisier.

Clasa RandomAccessFile contine o metoda "length" cu rezultat lungimea fisierului (in octeti), dar celelalte clase flux nu contin o astfel de metoda.

O solutie partiala este folosirea metodei "available" care are ca rezultat numarul de octeti care mai pot fi cititi fara "blocare", dar care nu este egal intotdeauna cu numarul total de octeti ramasi in fisier.

Pentru scrierea de numere intr-un fisier disc in format extern (sir de caractere) se poate folosi clasa PrintStream, care insa nu are corespondent un flux de intrare (pentru citire). In exemplul urmator se scriu numere intregi in format extern.

// scriere numere in format extern in fisier disc

import java.io.*;

class ExPrintFile

}

Pentru simplificarea construirii de obiecte flux se pot defini clase auxiliare. In exemplul urmator este definita o clasa pentru fisiere disc in care se poate scrie cu format.

import java.io.*;

// fisier de iesire cu conversie de format

class PrintFile extends PrintStream

public PrintFile (File file) throws IOException

}

class ExPrintFile

}

Evitarea distrugerii unui fisier anterior la crearea unui nou fisier se poate face folosind clasa File si o functie care sa determine existenta unui fisier cu nume dat intr-un director dat.

3 Utilizarea claselor de I/E din familia Reader - Writer

Incepand cu JDK 1.1 s-au introdus noi clase de I/E care permit crearea si exploatarea de fisiere cu caractere Unicode (pe 16 biti), folosind nume mai scurte pentru clase ( Reader si Writer) si o viteza sporita a operatiilor de I/E. Efectul exact al metodelor de scriere/citire din noile clase depinde de particularitatile "locale" ale sistemului pe care se executa programele Java. Pentru valorile implicite ale "localizarii" Java se scriu de fapt tot caractere ASCII pe 8 biti in fisiere si cu noile clase Writer/Reader.

Utilizarea vechilor clase de I/E este marcata de compilatorul Java ca "perimata", desi nu totdeauna se poate renunta la utilizarea (chiar partiala) a familiei InputStream-OutputStream.

In general, numele noilor clase de I/E provin din numele vechilor clase prin inlocuirea cuvantului InputStream cu Reader si a cuvantului OutputStream cu Writer. Numele metodelor se pastreaza in general la fel, ceea ce permite o adaptare rapida a programelor. Exemplu de copiere a unui fisier octet cu octet :

// utilizare clase FileReader si FileWriter

import java.io.*;

class FileCopy

}

Scrierea de numere cu conversie intr-un fisier foloseste acum clasa PrintWriter :

class ExPrintFile

}

Clasele DataOutputStream si DataInputStream nu mai au corespondent in noua familie de clase deoarece se considera ca nu se poate trece direct de la un sir de caractere la numere (asa cum se trecea de la un sir de octeti la numere binare).

Variabilele "in" si "out" nu au nici ele echivalent in variabile de tipul noilor clase si de aceea se folosesc clasele "punte" de trecere intre cele doua familii de clase :

InputStreamReader : produce un obiect Reader dintr-un obiect InputStream

OutputStreamWriter : produce un obiect Writer dintr-un obiect OutputStream

Exemplu de citire caractere individuale de la consola :

InputStreamReader stdin = new InputStreamReader (System.in);

int ch;

while ( (ch=stdin.read()) != -1)

Pentru a citi linii de text se poate folosi clasa BufferedReader, care contine o metoda "readLine". Exemplu de citire linii de la consola folosind noile clase :

// citire de la consola cu numerotare linii

import java.io.*;

class a

}

}

Clasele urmatoare din JDK 1.0 nu au corespondent in JDK 1.1 si se folosesc in continuare:

RandomAccessFile, File, SequenceInputStream, StreamTokenizer. De asemenea, clasele care asigura calculul sumei de control la citire/scriere sau compresia datelor folosesc clase de tip "Stream" si nu au fost modificate pentru noile clase de I/E.

4 Clase utilitare care folosesc clase de I/E

Calculul unei sume de control din toti octetii unui fisier este o metoda uzuala pentru verificarea integritatii fisierelor disc sau a corectitudinii transmiterii unor fisiere intre calculatoare. Suma trebuie actualizata la fiecare octet scris sau citit intr-un fisier sau intr-un alt flux de date.

Pachetul "java.util.zip" contine mai multe clase si interfete care simplifica calculul unei sume de control si compresia/decompresia datelor transmise prin fluxuri de I/E.

Interfata Checksum contine 4 metode dintre care mai importante sunt "update" care actualizeaza suma de control cu octetul primit si "getValue" care obtine valoarea sumei calculate. Exista si doua clase care implementeaza interfata Checksum pentru doua metode diferite de calcul a sumei de control: CRC32 si Adler32 (suma ocupa 32 de biti).

Clasele CheckedOutputStream si CheckedInputStream sunt derivate din clasele filtru FilterOutputStream si respectiv FilterInputStream si redefinesc metodele "read" si "write" din superclase. Contructorul unei clase "flux cu verificare" primeste doi parametri : o variabila de tip OutputStream sau InputStream care precizeaza suportul fizic al fluxului si o variabila de tip Checksum prin care se transmite procedura de calcul a sumei de control:

// fragment din clasa CheckedOutputStream

class CheckedOutputStream extends FilterOutputStream

public void write ( int b) throws IOException

. . . // alte metode

}

Utilizarea claselor flux cu verificare este foarte simpla, asa cum se vede din exemplul urmator:

// citire si scriere flux cu suma de control

import java.io.*;

import java.util.zip.*;

class a

long sum= in.getChecksum().getValue();

System.out.println('Suma de control la citire: ' +

in.getChecksum().getValue());

System.out.println('Suma de control la scriere: ' +

out.getChecksum().getValue());

}

}

Utilizarea practica a acestor clase este diferita de exemplul anterior deoarece suma de control calculata trebuie scrisa in fisierul creat (la sfarsit, de exemplu) si apoi citita si comparata cu suma calculata la citire.

O alta prelucrare asociata citirii si scrierii dintr-un flux este compresia / decompresia datelor pentru reducerea spatiului ocupat pe disc de un fisier. Exemplul urmator arata cum se poate scrie un fisier comprimat in format gzip si cum se poate citi cu decomprimare.

import java.io.*;

import java.util.zip.*;

class ExZip

}

5 Fisiere de obiecte (Obiecte persistente)

Uneori este necesara salvarea pe disc a unor obiecte astfel incat sa fie posibila reconstituirea obiectelor, pe baza fisierului creat, intr-o alta rulare a aceluiasi program.

Un obiect poate contine referinte la alte obiecte, care la randul lor pot contine alte obiecte. Salvarea unui obiect trebuie sa asigure scrierea intregii ierarhii de subobiecte intr-o secventa liniara si de aceea operatia de salvare se numeste si "serializarea" obiectelor.

Serializarea asigura persistenta obiectelor ("Lightweight persistence"), deoarece programul care foloseste fisierul de obiecte trebuie sa stie ce fel de obiecte au fost salvate. mai exact, programul care face deserializarea obiectelor trebuie sa aiba acces la fisierele de tip "class" pentru clasele cu obiecte salvate (fisierele "class" sa fie in acelasi director).

Principala utilizare a serializarii obiectelor este la transmiterea parametrilor de apel ai unei metode, aflate pe un alt calculator (server) decat calculatorul (client) pe care se executa apelul (RMI = Remote Method Invocation) si la transmiterea inapoi a rezultatului metodei de la calculatorul server la calculatorul client.

Clasele folosite in serializarea obiectelor fac parte din familia claselor de intrare-iesire InputStream-OutputStream, fiind clase de tip anvelopa care permit folosirea oricarui tip de flux ca suport al obiectelor. Nu exista fluxuri de obiecte de tip Reader/Writer.

Clasa ObjectInputStream este derivata din clasa InputStream si, in acelasi timp, contine o variabila de tip InputStream. Clasa ObjectOutputStream este derivata din clasa OutputStream si, in acelasi timp, contine o variabila de tip OutputStream.

Fata de clasele de baza, clasele pentru operatii de intrare-iesire cu obiecte contin metode "readObject" si "writeObject", care au un parametru formal de tip Object, deci pot fi apelate cu un parametru efectiv de orice tip clasa.

La construirea unui obiect de tip ObjectOutputStream se precizeaza tipul de flux folosit, prin parametrul transmis constructorului:

ObjectOutputStream objstrm = new ObjectOutputStream ( new FileOutputStream("file"));

Pot fi salvate numai obiecte din clase care implementeaza interfata Serializable, fara metode, iar majoritatea claselor cu date implementeaza aceasta interfata. La definirea unei clase proprii ale carei obiecte ar putea fi candva serializate trebuie adaugata clauza "implements Serializable" la definirea acestei clase. Exemplu:

class Arc implements Serializable

La salvarea unui obiect metoda "writeObject" scrie urmatoarele informatii:

- Numele clasei (ca sir de caractere)

- Un numar de identificare a clasei (amprenta clasei, pe 8 octeti)

- Valorile tuturor variabilelor ne-statice si ne-volatile (non-transient), inclusiv pentru membrii clasei care se refera la alte obiecte.

Aceste informatii sunt scrise de metoda "defaultWriteObject". Pentru a salva si alte informatii in acelasi fisier putem sa definim o subclasa a clasei ObjectOutputStream in care metoda "writeObject" este redefinita sau sa definim o functie care sa apeleze metoda de serializare implicita si in plus sa mai scrie si altceva in acelasi flux. Exemplu :

// metoda a clasei Arc

public void writeArc (ObjectOutputStream s) throws IOException

}

Marcarea unor campuri cu atributul transient are rolul de a interzice salvarea lor.

In exemplul urmator se salveaza continutul unei colectii de obiecte, toate de acelasi tip, dar se pot salva in acelasi fisier si obiecte de tipuri diferite.

// creare lista de arce

List arce = new ArrayList ();

arce.add ( new Arc (2,3,23)); // extremitati si cost arc

// adauga alte arce

// creare fisier de obiecte Arc

ObjectOutputStream oos = new ObjectOutputStream (

new FileOutputStream('objects'));

Iterator it = arce.iterator();

while ( it.hasNext())

oos.writeObject (it.next());

oos.close();

// citire fisier de obiecte Arc

ObjectInputStream ois = new ObjectInputStream (

new FileInputStream('objects'));

Object obj;

try catch (IOException e)



Politica de confidentialitate | Termeni si conditii de utilizare



DISTRIBUIE DOCUMENTUL

Comentarii


Vizualizari: 1472
Importanta: rank

Comenteaza documentul:

Te rugam sa te autentifici sau sa iti faci cont pentru a putea comenta

Creaza cont nou

Termeni si conditii de utilizare | Contact
© SCRIGROUP 2024 . All rights reserved