În lecțiile precedente am învățat ce reprezintă fluxurile de intrare și ieșire și cum se utilizează acestea la nivel de caractere.
În lecția de astăzi ne vom concentra pe modul de lucru cu fluxurile binare în limbajul de programare Java. Adică vom învăța cum se realizează citirea și scrierea datelor în fișiere binare.
După cum cunoaștem, în limbajul Java, pentru a implementa orice funcționalitate, trebuie să cunoaștem clasele care ne permit realizarea ei, precum și constructorii și metodele specifice ale acestor clase.
La baza ierarhiei fluxurilor bazate pe octeți se află clasele abstracte OutputStream (pentru scriere) și InputStream (pentru citire). Cunoașterea metodelor acestor clase ne permite, practic, să stăpânim aproximativ 90% din funcționalitatea tuturor claselor care le moștenesc, deoarece anume cu aceste clase lucrăm de fapt.
Clasa InputStream conține următoarele metode:
1) public abstract int read() throws IOException; – citește octetul curent din flux şi îl returnează sub forma unui întreg cu valori între 0 şi 255. Dacă s-a ajuns la capătul fluxului se returnează valoarea –1
2) public int read(byte b[]) throws IOException; - citesc de la poziția curentă din flux un număr de octeți egal cu lungimea tabloului b şi îl înscriu în tabloul b, returnează numărul de octeţi citiţi în bufferul b sau –1 dacă s-a ajuns la capătul fluxului.
3) public int read(byte b[], int off, int len) throws IOException; - citesc de la poziția curentă din flux un număr de octeţi egal cu len şi îl înscriu în tabloul b, la poziția off. Returnează numărul de octeţi citiţi în bufferul b sau –1 dacă s-a ajuns la capătul fluxului.
4) public long skip(long n) throws IOException; - mută poziția cursorului de citire peste n octeţi, întorcând numărul efectiv de octeţi peste care s-a sărit, sau -1 în caz de EOF.
5) public int available() throws IOException; - returnează numărul de octeţi disponibili pentru citire din flux.
6) public void close() throws IOException;- închide un flux. Odată terminat lucrul cu un flux, acesta trebuie închis, eliberând astfel resursele sistem ocupate de acel flux. În mod normal JVM închide aceste fluxuri în momentul în care programul se termină, dar este recomandabil ca programatorul să facă „curat”.
7) public synchronized void mark(int readlimit); - prin această funcție se memorează poziția curentă în flux, specificând prin argumentul readlimit, maximul de octeţi (se crează un buffer de dimensiune readlimit) care se pot citi până la apelul funcției reset.
8) public synchronized void reset() throws IOException;- repoziționează cursorul de citire din flux la poziția la care s-a făcut marcarea prin funcția mark.
9) public boolean markSupported(); - returnează adevărat daca metodele mark() și reset() sunt implementate, și fals în caz contrar.
10) protected void finalize() throws IOException;- asigură apelarea metodei close() pentru fluxul curent în caz că nu mai sunt referinţe la acesta.
Pentru a utiliza clasa InputStream în programare a fost nevoie de construirea unor clase derivate din aceasta, clase care să fie conectate cu surse de date reale existente în sistem.
Din categoria claselor pentru fluxuri de intrare la nivel de octet conectate la diferite tipuri de resurse fac parte:
● ByteArrayInputStream permite conectarea unui flux la un tablou de octeţi.
● SequenceInputStream permite combinarea mai multor fluxuri având ca rezultat obținerea unui singur flux.
● AudioInputStream permite conectării cu un stream audio.
● FileInputStream oferă posibilitatea conectării cu un fişier pentru a citi datele înregistrate în acesta.
Clasa OutputStream conține următoarele metode:
1) public abstract void write(int i) throws IOException; - scrie în flux octetul cel mai puţin semnificativ al argumentului, care este de tip întreg.
2) public void write (byte b[]) throws IOException;- permit scrierea în flux a unui tablou de octeţi .
3) public void write (byte b[], int off, int len) throws IOException;- permit scrierea în flux a unei secțiuni din tabloul de octeţi b începând de la poziția off şi având lungimea len.
4) public long flush() throws IOException; - forţează transmiterea datelor la destinație (capătul flux-ului) şi este utilă în cazul în care se folosește un buffer de scriere (golește buffer-ul).
5) public void close() throws IOException; - închide fluxul deschis .
În cadrul acestui curs utiliza mai multe clase pentru a opera cu fluxurile la nivel de octeți. Toate metodele sunt moștenite de la clasele InputStream şi OutputStream, nu au metode noi.
Clasa FileInputStream are constructorii :
1) FileInputStream(File file) throws FileNotFoundException – va realiza conectarea cu un fişier concret. Numele și calea către fișier fiind setate cu ajutorul unei instanțe a clasei File.
2) FileInputStream(String name) - va realiza conectarea cu un fișier calea căruia este dată de name.
Clasa FileOutputStream are un singur constructor:
FileOutputStream (cale catre fisier);
Exemplu 1. Programul va crea un fișier binar numit binar.dat, va scrie în fișier primele 10 numere naturale la nivel de octet, folosind FileOutputStream. Va deschide fișierul pentru citire folosind FileInputStream și va afișa pe ecran toate valorile citite, separate prin spațiu.
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException{
FileOutputStream output = new FileOutputStream("binar.dat");
for (int i=1;i<=10;i++) output.write(i);
output.close();
FileInputStream input = new FileInputStream("binar.dat");
int value;
while((value=input.read()) != -1)
System.out.print(value + " ");
input.close();
}}
Fișierul binar.dat creat în exemplul de mai sus este un fișier binar. Va putea fi citit de un program Java însă nu de un editor de text.
Extensia .dat este pur și simplu un nume generic pentru extensia fișierului creat. Nu are o semnificație aparte în Java sau în sistemul de operare.
Astfel puteți crea o extensie personalizată. De exemplu, .xyz sau .info pentru fișierele tale, atâta timp cât programul tău știe cum să le citească și să le scrie.
De exemplu:
FileOutputStream output = new FileOutputStream("date_elevi.myext");
Fișierul va avea extensia .myext, iar Java îl va percepe la fel ca pe orice fișier binar. 👌
După ce am învățat cum se utilizează clasele FileInputStream și FileOutputStream pentru citirea și scrierea simplă a octeților, următorul pas este să folosim clase care ne permit să lucrăm cu date structurate: DataInputStream și DataOutputStream, care extind funcționalitatea fluxurilor binare pentru a citi și scrie direct tipuri de date primitive.
Clasa DataInputStream permite citirea octeţilor din flux şi convertirea lor la un tip dorit.Constructor: DataInputStream(InputStream fluxIntrare);
Metode:
1) boolead readBoolean() throws IOException;
2) byte readByte() throws IOException;
3) short readShort() throws IOException;
4) char readChar() throws IOException;
5) int readInt() throws IOException;
6) long readLong() throws IOException;
7) float readFloat() throws IOException;
8) double readDouble() throws IOException;
9) String readUTF() throws IOException;
10)void close() throws IOException;
Clasa DataOutputStream converteşte valorile primitive sau șiruri de caractere în octeţi şi le transmite la ieşire prin flux. Constructor:
DataOutputStream(OutputStream fluxIesire);
Metode:
1) void writeBoolean(boolean b) throws IOException;
2) void writeByte(int b) throws IOException;
3) void writeBytes(String s) throws IOException;
4) void writeShort(int s) throws IOException;
5) void writeChar(int c) throws IOException;
6) void writeInt(int i) throws IOException;
7) void writeLong(long l) throws IOException;
8) void writeFloat(float f) throws IOException;
9) void writeDouble(double d) throws IOException;
10) void writeUTF(String s) throws IOException;
Exemplul 2. Programul va crea fișierul binar numit out.dat folosind DataOutputStream. Va scrie în fișier numele și nota a trei elevi folosind metodele writeUTF() și writeDouble(). Apoi va citi din fișier datele cu ajutorul metodelor clasei DataInputStream și va afișa pe ecran numele și nota fiecărui elev.
import java.io.*;
public class Main {
public static void main(String [] args) throws IOException{
DataOutputStream output=new DataOutputStream(new FileOutputStream("out.dat"));
output.writeUTF("Mihai"); output.writeDouble(9.56);
output.writeUTF("Ion"); output.writeDouble(9.81);
output.writeUTF("Maria"); output.writeDouble(9.60);
output.close();
DataInputStream input=new DataInputStream(new FileInputStream("out.dat"));
System.out.println(input.readUTF()+" "+input.readDouble());
System.out.println(input.readUTF()+" "+input.readDouble());
System.out.println(input.readUTF()+" "+input.readDouble());
input.close();
}
}
Exemplul 3. Programul va crea fișierul binar numit out.dat folosind DataOutputStream. Va scrie în fișier numele și nota citite de la tastatură a n elevi. Apoi va citi din fișier datele cu ajutorul metodelor clasei DataInputStream și va afișa pe ecran numele și nota fiecărui elev.
import java.io.*;
import java.util.Scanner;
public class Main {
public static void main(String[] args) throws IOException {
Scanner scanner = new Scanner(System.in);
System.out.print("Introduceți numărul de elevi: ");
int n = scanner.nextInt();
scanner.nextLine(); // consumă Enter-ul rămas după nextInt()
DataOutputStream output = new DataOutputStream(new FileOutputStream("out.dat"));
for (int i = 0; i < n; i++) {
System.out.print("Introduceți numele elevului " + (i+1) + ": ");
String nume = scanner.nextLine();
System.out.print("Introduceți nota elevului " + (i+1) + ": ");
double nota = scanner.nextDouble();
scanner.nextLine(); // consumă Enter-ul rămas
output.writeUTF(nume);
output.writeDouble(nota);
}
output.close();
DataInputStream input = new DataInputStream(new FileInputStream("out.dat"));
System.out.println("\nLista elevilor cu notele lor:");
try {
for (int i = 0; i < n; i++) {
String nume = input.readUTF();
double nota = input.readDouble();
System.out.println(nume + " " + nota);
}} catch (EOFException e) {
// Sfârșitul fișierului
}
input.close();
}}
Fișierul binar out.dat a fost creat și în el au fost salvate datele introduse de la tastatură. Deoarece este un fișier binar, acesta nu poate fi citit direct cu un editor de text, de aceea trebuie să oferim o modalitate de a verifica ce informații au fost stocate.
În acest scop, în metoda main() sunt incluse și instrucțiuni care citesc datele din fișier și le afișează pe ecran, astfel încât să putem vedea conținutul salvat.
Limbajul Java oferă oportunități de a salva și starea obiectelor în fișiere binare. De ce apare această necesitate? Atunci când lucrăm cu obiecte, în mod normal, acestea există doar pe durata execuției programului. După terminarea programului sau atunci când nu mai există referințe către ele, obiectele sunt eliminate automat din memorie de Garbage Collector.
Există însă situații în care dorim ca datele unui obiect să fie păstrate și după închiderea programului, pentru a putea fi utilizate ulterior.
În astfel de cazuri, limbajul Java permite salvarea obiectelor într-un fișier în format de obiect, printr-un proces numit serializare. Astfel, starea obiectului este salvată într-un fișier și poate fi refăcută mai târziu în program, atunci când este nevoie.
Dar despre serializare vom discuta într-o altă postare! 💬