Sunteți pe pagina 1din 10

Fluxuri de intrare/ieşire

Clasele ce oferă facilităţi de intrare/ieşire se găsesc în pachetul java.io.

Schema generală

Una dintre metodele prin care putem îmbogăţi funcţionalitatea unei clase C
este cea de a declara în ea un câmp d de tipul altei clase D şi de a iniţializa acest câmp
printr-un constructor:

class C {
D d;
C(D d) {this.d=d; }
. . .
}

În acest mod, în metodele clasei C putem invoca metode ale clasei D, prin
intermediul obiectului d.
În cazul în care clasa D extinde clasa C, această tehnică poartă numele de
suprapunerea de obiecte, care aplicată la fluxuri de intrare/ieşire se numeşte
suprapunere de fluxuri. În această situaţie sintagma "suprapunerea obiectelor adaugă
noi facilităţi (funcţionalităţi)", înseamnă adaptarea metodelor clasei suprapuse la
contextul clasei care o suprapune.
Facilităţile de intrare/ieşire din Java au la bază noţiunea de flux. Un flux este o
succesiune de elemente (octeţi sau caractere), citite şi scrise secvenţial.
Pentru un flux de intrare sursa datelor poate fi un fişier, dar şi un şir sau tablou
de octeţi, respectiv caractere. Pentru un flux de ieşire, datele transmise sunt stocate
într-un fişier sau într-un tablou de octeţi, respectiv caractere. Este posibilă (şi chiar
recomandată) utilizarea zonelor tampon. De asemenea este posibil ca un flux de ieşire
să "comunice" cu un flux de intrare, în sensul că datele scrise în fluxul de ieşire vor
constitui sursa pentru fluxul de intrare.

Dacă la citire nu sunt încă date disponibile în flux şi nu s-a detectat sfârşitul
fluxului, atunci firul de executare care realizează citirea va fi blocat până când vor
exista date disponibile. Analog, în cazul a două fluxuri comunicante ce folosesc o
zonă tampon, firul de executare care are sarcina să scrie va fi blocat în situaţia în care
zona tampon este plină.
O primă clasificare a fluxurilor are în vedere elementele de bază care sunt
transmise: caractere sau octeţi.
Object
InputStream (clasă abstractă pt. citire la nivel de octet)
OutputStream (clasă abstractă pt. scriere la nivel de octet)
Reader (clasă abstractă pt. citire la nivel de caracter)
Writer (clasă abstractă pt. scriere la nivel de caracter)

Toate clasele, interfeţele şi metodele din pachetul java.io au modificatorul


public, iar în plus aproape toate metodele conţin clauza throws IOException.
Fluxuri ce lucrează la nivel de octet

O structură simplificată de clase


Object
DataInput (interfaţă)
InputStream (abstractă)
FileInputStream
FilterInputStream
DataInputStream implements DataInput
DataOutput (interfaţă)
OuputStream (abstractă)
FileOutputStream
FilterOutputStream
DataOutputStream implements DataOutput

Clasele neabstracte, afară de FileInputStream şi de FileOutputStream,


au un constructor cu un parametru de tipul InputStream, respectiv de tipul
OutputStream.

Exemplul 1. Într-o primă etapă vom citi de la intrarea standard un număr


natural n şi apoi n numere reale; vom crea în directorul curent un fişier cu numele
out.dat în care vom scrie datele citite. Într-o a doua etapă vom citi din fişierul
out.dat valoarea n şi cele n numere şi le vom tipări la ieşirea standard.

Prima etapă este realizată de următorul program:


import java.io.*;
class Unu {
public static void main(String[] sir) throws Exception {
int n;
DataOutputStream g = new DataOutputStream(
new FileOutputStream("out.dat") );
IO.write("n= "); n = (int) IO.read(); g.writeInt(n);
IO.writeln("Introduceti " + n + " numere reale");
for (int i=0 ; i<n ; i++) g.writeDouble( IO.read() );
g.close();
}
}
unde:
- prin new FileOutputStream("out.dat") este creat fişierul out.dat;
- obiectul g de tipul DataOutputStream foloseşte metodele writeInt şi
writeDouble (anunţate în interfaţa DataOutput şi implementate în clasa
DataOutputStream) pentru a scrie în fişierul out.dat;
- pentru închiderea fişierului este invocată metoda close a clasei
FilterInputStream (moştenită din DataOutputStream).
A doua etapă este realizată de următorul program:
import java.io.*;
class Doi {

public static void main(String[] ss) throws Exception {


double d; int n;
DataInputStream g = new DataInputStream(
new FileInputStream("out.dat") );
n = g.readInt();
for (int i=0 ; i<n; i++)
IO.write( g.readDouble() + "\t" );
IO.writeln(); g.close();
}
}
unde:
- prin new FileInputStream("out.dat") este deschis fişierul out.dat;
- obiectul g de tipul DataInputStream foloseşte metodele readInt şi
readDouble (anunţate în interfaţa DataInput şi implementate în clasa
DataInputStream) pentru a citi din fişierul out.dat;
- pentru închiderea fişierului este invocată metoda close a clasei
FilterInputStream (moştenită din DataInputStream).

• Interfaţa DataOutput
Conţine metode menite a scrie, într-un flux de ieşire neprecizat, date de tipuri
primitive, precum şi şiruri de caractere.
void write(int b)
void writeBytes(String s) throws NullPointerException
void writeChars(String s) throws NullPointerException
void writeUTF(String s) void writeBoolean(boolean v)
void writeByte(int v) void writeShort(int v)
void writeChar(int v) void writeInt(int v)
void writeLong(long v) void writeFloat(float v)
void writeDouble(double v)

• Interfaţa DataInput
int skipBytes(int n) boolean readBoolean()
byte readByte() int readUnsignedByte()
short readShort() int readUnsignedShort()
char readChar() int readInt()
long readLong() float readFloat()
double readDouble() String readLine()
String readUTF()

Observaţie. Dacă s-a ajuns la sfârşitul fluxului de intrare înainte de a se fi citit


numărul dorit de octeţi, va fi lansată excepţia EOFException.
Se presupune că datele citite au fost scrise în fişier cu metodele
complementare anunţate în interfaţa DataOutput.
• Clasa abstractă OutputStream
Adaugă:
void flush() void close()

• Clasa abstractă InputStream


Adaugă:
long skip(long n) int available() void close()

• Clasele FilterInputStream şi FilterOutputStream


class FilterOutputStream extends OutputStream {
protected OutputStream out;
FilterOutputStream(OutputStream out)
redefiniri ale metodelor clasei OutputStream
}

class FilterInputStream extends InputStream {


protected InputStream in;
FilterInputStream(InputStream in)
redefiniri ale metodelor clasei InputStream
}

• Clasele FileOutputStream şi FileInputStream


Metodele clasei FileOutputStream implementează, respectiv redefinesc
metodele cu aceleaşi nume şi signatură din OutputStream, aplicându-le pentru
fluxul de ieşire primit ca argument de constructor.
class FileOutputStream extends OutputStream {
FileOutputStream(String nume)
throws SecurityException,FileNotFoundException
FileOutputStream(String nume, boolean adaug)
throws SecurityException,FileNotFoundException
redefiniri ale metodelor read, flush şi close din OutputStream
}
Constructorul cu un parametru deschide fluxul de ieşire nume.
class FileInputStream extends InputStream {
FileInputStream(String nume)
throws SecurityException,FileNotFoundException
redefiniri ale metodelor write, skip, available şi close
din OutputStream
}
Constructorul clasei deschide fluxul de intrare constituit de fişierul precizat
prin şirul de caractere nume.
• Clasele DataOutputStream şi DataInputStream
Clasele DataOutputStream şi DataInputStream oferă în plus

posibilitatea ca fluxurile să nu mai fie privite la nivel de octet, ci ca succesiuni de


date primitive sau şiruri de caractere. Datele vor fi scrise în fluxul de ieşire într-un
format independent de modul de reprezentare al datelor în sistemul pe care se
lucrează.
class DataOutputStream extends FilterOutputStream
implements DataOutput {
DataOutputStream(OutputStream out)
protected int written;
implementarea metodelor write din interfaţa DataOutput
void flush()
final int size()
}

class DataInputStream extends FilterInputStream


implements DataInput {
DataInputStream(InputStream in)
implementarea metodelor read şi skipBytes din interfaţa DataOutput
}

O structură extinsă de clase

Object
DataInput (interfaţă)
InputStream (clasă abstractă)
FileInputStream
FilterInputStream
DataInputStream
BufferedInputStream
PipedInputStream
DataOutput (interfaţă)
OutputStream (clasă abstractă)
FileOutputStream
FilterOutputStream
DataOutputStream
Buffered OutputStream
PrintStream
PipedOutputStream

Exemplul 2. Adaptăm Exemplul 1 la lucrul cu zone tampon, utilizând un


buffer pentru fluxul de intrare din clasa Doi. Pentru aceasta este suficient să înlocuim
declararea fluxului g prin:
DataInputStream g = new DataInputStream(
new BufferedInputStream(
new FileInputStream("out.dat") ) );
• Clasele PipedOutputStream şi PipedInputStream
Funcţionalitatea nouă oferită de aceste clase constă în crearea a câte un obiect
pos, respectiv pis de fiecare din aceste tipuri, obiecte ce sunt "conectate" în
următorul sens: ceea ce este scris prin intermediul obiectului pos este citit prin
intermediul obiectului pis. Fiecare dintre obiecte (fluxuri) cunoaşte identitatea
celuilalt. Este recomandat să folosim fire de executare separate pentru utilizarea celor
două obiecte, deoarece încercarea de a realiza acest tip de transmisie din cadrul
aceluiaşi fir de executare poate conduce la blocare totală. Este folosită o zonă tampon
cu disciplină de coadă. Orice obiect de unul dintre tipurile PipedInputStream şi
PipedOutputStream trebuie conectat la exact un obiect de celălalt tip, în caz
contrar fiind lansată o excepţie.
PipedInputStream extends InputStream {
protected static final int PIPE_SIZE; // PYPE_SIZE
protected byte[] buffer;
protected int in, out;
PipedInputStream(PipedOutputStream pos)
PipedInputStream()
void connect(PipedOutputStream pos)
protected void receive(int b)
implementarea metodelor read, available şi close din InputStream.
}

Constructorul cu un parametru creează un obiect de tipul


PipedInputStream şi îl conectează la obiectul pos primit ca parametru.
Constructorul fără parametri crează un obiect, dar nu realizează conectarea; aceasta
trebuie realizată ulterior prin invocarea metodei connect.
Câmpul PYPE_SIZE reprezintă mărimea cozii de intrare, ale cărei elemente
apar în buffer. Câmpurile in şi out indică poziţiile în care va fi primit următorul
octet, respectiv poziţia din care va fi citit primul octet din acest flux de intrare. Coada
vidă este identificată prin in<0, iar coada plină este identificată prin in==out.
Prin invocarea metodei receive este primit un octet de intrare.
class PipedOutputStream extends InputStream {
PipedOutputStream(PipedOutputStream pos)
PipedOutputStream()
void connect(PipedInputStream pis)
implementarea metodelor write, flush şi close din clasa OutputStream
}

Constructorul cu un parametru creează un obiect de tipul


PipedOutputStream şi îl conectează la obiectul pis primit ca parametru.
Constructorul fără parametri creează un obiect, dar nu realizează conectarea; aceasta
trebuie realizată ulterior prin invocarea metodei connect.
Observaţie. Dacă fluxul de intrare pis şi fluxul de ieşire pos sunt ambele
neconectate, conectarea lor se poate realiza şi prin oricare dintre invocările
pis.connect(pos) şi pos.connect(pis).
Exemplul 4. Reuăm problema Producător - Consumator; sunt transmise
valorile din intervalul 10..99.

class PIS extends PipedInputStream {


int IN() { return in; }
int OUT() { return out; }
}

class Banda {
PIS pis; PipedOutputStream pos; int lung;
Banda(int l) {
try {
lung = l; IO.writeln("lung = "+lung);
pis = new PIS(); pos = new PipedOutputStream();
pis.connect(pos);
}
catch (IOException e) { }
}

synchronized void pune(int b) {


try {
if ( pis.IN()-pis.OUT() > lung-1 ) {
IO.write(" !P "); wait();
}
IO.write(" P" + b); pos.write(b); notify();
}
catch (IOException e) { IO.write("Prod"); }
catch(InterruptedException e) { }
}

synchronized int ia() {


int b = 99;
try {
if ( pis.IN() < 0 ) { IO.write(" !C "); wait(); }
b = pis.read(); IO.write(" C" + b); notify();
}
catch (IOException e) { IO.write("Cons"); }
catch(InterruptedException e) { }
return b;
}
}

class Prod extends Thread {


Banda p;
Prod(Banda f) { p = f; }

public void run() {


int b;
for (b=10; b<100; b++)
try {
Thread.sleep( (int) (100*Math.random()) ); p.pune(b);
}
catch(InterruptedException e) { };
}
}

class Cons extends Thread {


Banda p;
Cons(Banda f) { p = f; }

public void run() {


int b = 99;
do {
try {
Thread.sleep( (int) (100*Math.random()) ); b = p.ia();
}
catch(InterruptedException e) { }
}
while (b<99);
}
}

class Piped {
public static void main(String[] sir) {
Banda f = new Banda(2);
Prod P = new Prod(f); Cons C = new Cons(f);
C.start(); P.start();
}
}

Clasa Scanner

Clasa Scanner apare în pachetul java.util şi permite regăsirea într-un text


a tipurilor primitive şi a şirurilor, folosind expresii regulate.

public final class Scanner extends Object


implements Iterator<String>

Un scanner (obiect de tipul Scanner) împarte intrarea în entităţi, folosind un


şablon de delimitatori (delimitatorii impliciţi sunt spaţiile albe) şi regăseşte entităţile
prin invocarea de metode next. Pentru a verifica dacă urmează o entitate de un
anumit tip se folosesc metodele hasNext.

Exemplu. Citirea unui întreg de la intrarea standard se poate realiza astfel:


Scanner sc = new Scanner(System.in);
int i = sc.nextInt();

Intrarea poate fi şi un fişier text:


Scanner sc = new Scanner(new File("fisier"));
sau un şir de caractere.

Exemplu. Programul următor realizează citirea repetată de la intrarea standard


şi introducerea într-un fişier "text" a unei succesiuni de întregi terminată cu o entitate
diferită de un întreg, până este detectat şirul "STOP".

import java.util.*; import java.io.*;


class Scan {
public static void main(String[] sss) throws Exception {
PrintWriter out = new PrintWriter(new File("aaa"));

Scanner sc = new Scanner(System.in); String s;


do {
while(sc.hasNextInt()) out.print(sc.nextInt()+" ");
s = sc.next(); out.println();
}
while(!s.equals("STOP"));
out.close();
}
}
unde despre clasa PrintWriter menţionăm doar că acţionează la nivel de caracter şi
pune la dispoziţie metodele print şi println fără argumente sau cu un argument
ce poate fi un tip primitiv sau un şir de caractere.

Metodele de scanare relative la citire pot conduce la blocare prin aşteptarea


unei valori de intrare.

Metodele next() şi hasNext(), precum şi metodele asociate tipurilor


primitive (ca de exemplu nextInt() şi hasNextInt()), încep prin a "sări" peste
intrările ce corespund şabloanelor de intrare şi apoi încearcă să regăseascăurmătoarea
entitate.

Un obiect de tipul Scanner nu este adecvat lucrului cu fire de executatre


decât în prezenţa sincronizării.

Constructorul are un parametru de unul dintre tipurile:


File, InputStream, Readable, String, ReadableByteChannel.

Cele mai folosite metode sunt următoarele:

public boolean hasNext()


întoarce true dacă mai urmează o entitate;
public String next()
detectează şi întoarce următoarea entitate;
public boolean hasNextLine()
întoarce true dacă mai urmează o nouă linie de intrare;
public String nextLine()
întoarce următoarea linie de la intrare;

În metodele descrise în continuare, XXX poate fi:


Boolean, Byte, Short, Int, Long, Float, Double, BigInteger, BigDecimal.
Metodele hasNextXXX(), cu semnificaţie evidentă, pot lansa excepţia:
IllegalStateException dacă scanner-ul este închis.
Metodele nextXXX(), cu semnificaţie evidentă, pot lansa excepţiile:
InputMismatchException - dacă entitatea nu este cea aşteptată;
NoSuchElementException - dacă s-a ajuns la sfârşitul intrării;
IllegalStateException - dacă scanner-ul este închis.
Metoda:
public void close()
închide scanner-ul.

S-ar putea să vă placă și