Sunteți pe pagina 1din 29

Clase pentru intrări și ieșiri.

Serializarea și deserializarea obiectelor.


profesor Dobrovolischii Olga
Fluxurile de intrare și ieșire
În Java operațiile de intrare/ieșire au la bază termenul de stream(flux)introdus pentru prima
dată de Dennis Ritchie care în 1984 implementează primul I/O pe bază de stream în cadrul
S.O. Unix. Ideea de stream are la bază crearea unui canal de comunicație între două entități.
Canalul permite trecerea unui flux de date într-o singură direcție. Deoarece există două
direcții de comunicare, există două tipuri mari de stream-uri : de intrare(input stream) și de
ieșire(output stream).

Un flux care citește date se numește de intrare, iar cel care scrie date se numește de ieșire.

Pentru a aduce informații dintr-un mediu extern , un program Java trebuie să deschidă un
canal de comunicație(flux) de la sursa informațiilor (fișier, memorie, socket, etc) și să
citească secvențial informațiile respective.
Indiferent de tipul informațiilor , citirea/scrierea de pe/către un mediu extern
respectă următorul algoritm:
Clasificarea fluxurilor
Există trei tipuri de clasificare a fluxurilor:
După direcția canalului de comunicație deschis fluxurile se impart în :
fluxuri de intrare(pentru citirea datelor)
fluxuri de ieșire(pentru scrierea datelor)
După tipul de date pe care operează
fluxuri de octeți(comunicarea serială se realizează pe 8 biți)
fluxuri de caractere(comunicarea serială se realizează pe 16 biți)
După acțiunea lor:
fluxuri primare de citire/scriere a datelor(se ocupă efectiv cu citirea/scrierea
datelor)
fluxuri pentru procesarea datelor
Fluxurile de octeți și fluxurile de caractere

Pentru a putea face o diferență între aceste două tipuri amintim care sunt fișierele text și
fișierele binare.

Un fișier care poate fi procesat folosind un editor de text, este un fișier text. Toate
celelalte fișiere sunt numite binare. Acestea nu pot fi editate cu un editor de text și sunt
create pentru a fi cititte de programe anumite.

De exemplu , fișierul sursă *.java este stocat într-un fișier text care poate fi citit cu orice
editor de text, iar fișierul *.class este stocat într-un fișier binar care este citit de JVM.
Calculatoarele nu fac diferență între cele două tipuri de fișiere. Toate fișierele sunt
stocate în format binar.
Fluxurile de octeți și fluxurile de caractere

Toate fișierele sunt stocate în format binar.


Caracterele I/O sunt construite în baza datelor fluxurilor I/O binare care au fost
codificate automat.

JVM codifică/decodifică caracterele Unicode atunci cînd le scrie/citește în/din


fișiere. De aceea fluxurile I/O binare sunt mai eficiente decît fluxurile I/O bazate
pe caractere.

Fluxurile I/O binare(la nivel de octeți ) nu necesită conversii de cod. Dacă vom
scrie o valoare numerică folosind fluxuri binare valoare exactă din memorie va fi
transferată în fișier.
Fluxurile de octeți și fluxurile de caractere
Fluxurile de octeți și fluxurile de caractere

În general , trebuie să folosim fluxuri I/O bazate pe caractere atunci cînd lucrăm cu
fișiere create de editoare text și fluxuri I/O binare pentru lucrul cu fișierele binare create
de JVM.

Fișierele binare sunt independente de schema de codificare a SO ceea ce oferă


portabilitate.
Programele JAVA pe orice calculator pot citi fișierele binare create de JAVA. De aceea
clasele java sunt fișiere binare.

Fișierele java *.class pot rula pe JVM pe orice calculator.

Clasele și interfețele standard pentru lucrul cu fluxuri se găsesc în pachetul java.io. Deci
orice program care necesită operații de intrare sau ieșire trebuie să conțină instrucțiunea
de import a pachetului java.io.
Fluxurile de octeți
Fluxurile Java Byte sunt utilizate pentru a efectua intrarea și ieșirea de
octeți pe 8 biți .

Deși există multe clase legate de fluxurile de caractere, clasele cele mai
frecvent utilizate sunt FileInputStream și FileOutputStream.

Cei mai utilizați constructori sunt cei care primesc ca argument numele
fișierului . Aceștia pot provoca excepții de tipul FileNotFoundException
în cazul în care fișierul cu numele specificat nu există.
Din acest motiv orice creare a unui flux de acest tip trebuie făcută într-un
bloc try-catch sau metoda în care sunt create fluxurile respective trebuie
să arunce excepțiile de tipul FileNotFoundException sau de tipul
superclasei IOException
declararea excepției în metodă folosind bloc try-catch

public static void main(String []args) public static void main(String []args){
throws IOException{ try{
//operațiile intrare/ieșire //operațiile intrare/ieșire
}catch(IOException ex){...}
}
Fluxurile de octeți
Exemplu: Programul ca copia conținutul fișierului in.txt într-un alt fișier out.txt. Ambele
fișiere se află în directoriul curent.
import java.io.*;
public class CopieFisier {
public static void main(String args[]) throws IOException {
FileInputStream in = null;
FileOutputStream out = null;

try { in = new FileInputStream("input.txt");


out = new FileOutputStream("output.txt");

int c;
while ((c = in.read()) != -1) {
out.write(c);
}
} finally {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
}
}
}
Fluxurile de caractere

Fluxurile de caractere sunt utilizate pentru a asigura intrarea și ieșirea fluxului pe


16 biți în Unicode.

Clasele care asigură acest lucru , cel mai des utilizate sunt FileReader and
FileWriter .

În programe FileReader utilizează clasa FileInputStream iar FileWriter utilizează


clasa FileOutputStream. Singura deosebire este ca la fluxurile de carctere clasele
FileInputStream citește doi octeți la rînd iar FileOutputStream scrie doi octeți la
rînd.
Fluxurile de caractere
Exemplu:
import java.io.*;
public class CopyFile {

public static void main(String args[]) throws IOException {


FileReader in = null;
FileWriter out = null;

try { in = new FileReader("input.txt");


out = new FileWriter("output.txt");

int c;
while ((c = in.read()) != -1) {
out.write(c);
}
} finally {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
}}}
Ierarhia claselor care operează cu fluxurile I/O
Fluxuri Standard(Standard Stream)
În Java sunt create automat 3 fluxuri pentru utilizatori:
System.out(ex. System.out.println(“mesaj”);)
System.in (ex.int i=System.in.read() ;)
System.err(ex. System.err.println(“error mesage”);)
Toate aceste fluxuri sunt atașate de consolă
Ce este serializarea obiectelor?
În mod normal orice obiect creat în Java este distrus atunci cînd finisează
programul care l-a creat, sau atunci cînd nu mai este referință la obiect acesta
este distrus automat de procesul de colectare(Garbage Collector).

Sunt multe situații în care datele cu care lucrează un program trebuie să aibă o
durată de viață mai mare decît a programului care le-a creat- trebuie să
supravețuiască în afara spațiului de adrese a unei JVM.

Pentru aceasta limbajul Java pune la dispoziția programatorilor noțiunea de


serializare a obiectelor.
Definiție: Serializarea este procesul de transformare a obiectelor într-un șir de
octeți , din care să poată fi refăcut ulterior obiectul original. Procesul invers de
citire a unui obiect serializat pentru a-i reface starea originală se numește
deserializare.

În ce constă procesul de serializare?

Pentru a putea serializa starea unui obiect este necesar ca clasa acestuia să
implementeze interfața Serializable folosind clasele de serializare și fluxurile de
ieșire la nivel de octeți ,vom converti într-o secvență de octeți starea obiectului și
o vom salva fie într-un fișier , fie într-o bază de date sau memorie.
În ce constă procesul de deserializare?

Presupunem că avem deja un obiect serializat anterior, salvat într-un fișier sau
BD sau memorie, pentru a deserializa obiectul respectiv vom folosi clasele de
deserializare și fluxurile de intrare la nivel de octeți care reconstruiesc obiectul
original.

Condițiile de serializare/deserializare a obiectelor:


Clasele trebuie să implementeze interfața java.io.Serializable, aceasta nu
conține nici o metodă , nici un atribut. Se folosește ca indicator care anunță că
clasa poate fi serializată , sau este serializată într-un fișier, bază de date, sau
trimisă pe rețea . Așa tip de interfețe poartă denumirea de interfețe de marcare.
2. Toate cîmpurile în clasă trebuie să fie serializabile. Dacă un atribut nu este
serializabil acesta trebuie să fie marcat transient(transient=ignorat de procesul
de serializare)

3. Toate cîmpurile de date primitive pot fi serializate.

4. Clasele care realizează serializarea/deserializarea sunt ObjectInputStream și


ObjectOutputStream, metodele cele mai utilizate fiind: writeObject(Object obj) și
readObject().

5. Toate clasele ce moștenesc o clasă serializată vor implementa implicit interfața


Serializable, adică nu este necesar de a declara că o clasă implementează
interfața Serializable dacă una dintre clasele moștenite au făcut acest lucru.
6. În situația în care dorim să declarăm o clasă serializabilă dar superclasa sa nu
este serializabilă atunci trebuie să avem în vedere următoarele lucruri:

Superclasa trebuie să aibă obligatoriu un constructor accesibil fără argumente ,


acesta fiind utilizat pentru inițializarea variabilelor moștenite în procesul de
restaurare a unui obiect. Variabilele proprii vor fi inițializate cu valorile de pe fluxul
de intrare. În lipsa unui astfel de constructor accesibil pentru superclasă , va fi
generată o excepție la execuție.

Variabilele accesibile ale superclasei ale superclasei nu vor fi serializate, fiind


responsabilitatea clasei curente de a asigura un mecanism propriu pentru
salvarea/restaurarea lor.

Metode de a realiza serializare/deserializarea


Utilizarea interfeței Serializable
Utilizarea interfeței Externalizable
} import java.io.*; //serializare
}
class Angajat implements Serializable { class TestAngajat
} {public static void main (String [] args){ Angajat
private String nume; persoana=new Angajat(); persoana.setNume("Tehnologii
} Moderne "); persoana.setAni(4);
private int ani;
try {
public String getNume(){ return nume;}
FileOutputStream
public void setNume(String nume) file=new
{this.nume=nume;} FileOutputStream("o
ut.ser");
public int getAni(){return ani;} ObjectOutputStream
st= new
public void setAni(int ani){this.ani=ani;} ObjectOutputStream
(file);
} st.writeObject(perso
ana);

file.close();

}catch ( IOException ex){ ex.printStackTrace();}

}}
Deserializare
class TestDeserializareAngajat{
public static void main(String [] args){
try {
FileInputStream file=new FileInputStream("out.ser"); ObjectInputStream st= new ObjectInputStream(file); Angajat
persoana=(Angajat)st.readObject(); st.close();
file.close();
System.out.println("Numele angajatului:"+persoana.getNume()); System.out.println("Virsta angajatului :"+persoana.getAni());
}catch (IOException ex){ ex.printStackTrace();
} catch (ClassNotFoundException e) {e.printStackTrace();}}}
Dezavantajul mecanismului implicit de serializare este că algoritmul pe care se
bazează , fiind mai mult creat pentru cazul general , se comportă ineficient în
anumite situații concrete: poate fi mult mai lent decît este cazul sau reprezentarea
binară generată pentru un obiect poate fi mult mai voluminoasă decît ar trebui.

În aceste situații putem înlocui algoritmul implicit cu unul propriu, particularizat


pentru o clasă anume pentru ca clasa să poată avea mai mult control asupra
serializării.

Acest lucru se realizează prin supradefinirea(într-o clasă serializabilă!)a


metodelor writeObject() și readObject() avînd exact sintaxa:

private void writeObject(java.io.ObjectOutputStream stream) throws IOException;

private void readObject(java.io.ObjectInputStream stram) throws IOException,


ClassNotFoundException
Metoda writeObject() controlează ce date sunt salvate, iar readObject()
controlează modul în care sunt restaurate obiectele, citind informațiile salvate.

Notă: În corpul metodei writeObject() pentru a controla datele ce vor fi supuse serializării
se vor folosi metode ca stream.writeInt(...), stream.writeDouble(...) ,... .Analogic și în
corpul metodei readObject() se vor folosi metode de tip stream.readInt(...), etc

Interfața Serializable realizează serializarea implicită.


Exemplu de serializare folosind interfața Externalizable

Atunci cînd dorim un control mai amănunțit al serializării vom implementa interfața Externalizable(extinde
interfața Serializable) și oferă un control complet al formatului extern al obiectului.

Definiția acestei interfețe este:


package java.io;
public interface Externalizable extends Serializable {
public void writeExternal(ObjectOutput out) throws IOException; public void readExternal(ObjectInput in) throws
IOException,
ClassNotFoundException;}
Deci clasele ce vor implementa această interfață trebuie obligatoriu să implementeze metodele
writeExternal() și readExternal() în care se va face serializarea completă a obiectelor și concordarea cu
supeclasa ei.
Interfața Externalizable este folosită în situații în care se dorește îmbunătățirea algoritmului standard , mai
exact vreșterea vitezei procesului de serializare.
Exemplu de serializare folosind interfața Externalizable
Exemplu de serializare folosind interfața Externalizable
Notă:

Clasele care nu implementează una din interfețele menționate nu vor putea fi


serializate sau deserializate.

Un cîmp declarat transient nu va fi serializat restul cîmpurilor vor fi serializate.


private String question;
transient int correctAnswer;

Cîmpurile statice nu sunt serializate(serializarea se oferă la obiect și la clasă)

Dacă obiectul serializat conține referințe la alte obiecte, acestea sunt și ele serializate
automat urmînd aceleași reguli.
De ce este utilă serializarea?
Asigură un mecanism simplu și rapid de utilizat pentru salvarea și restaurarea datelor.
Asigură persistența obiectelor , adică obiectul poate exista și între apelurile
programelor care îl folosesc, nu doar în momentul execuției programului care l-a creat.
Acest lucru se realizează prin serializarea obiectului și scrierea lui pe disc înainte de
terminarea unui program , apoi, la relansarea programului , obiectul va fi citit de pe
disc și starea lui refăcută.
Compensarea diferențelor între sisteme de operare- transmiterea unor informații între
platforme de lucru diferite se realizează unitar, independent de formatul de
reprezentare a datelor.
Transmiterea datelor în rețea - Aplicațiile ce rulează în rețea pot comunica între ele
folosind fluxuri pe care sunt trimise , respectiv recepționate obiecte serializate.
La general serializarea se folosește atunci cînd avem nevoie ca datele
încapsulate într-o instanță a unei clase să poată exista înafara timpului de
execuție a unei mașini virtuale.