This shows you the differences between two versions of the page.
|
poo:laboratoare:13 [2026/01/12 08:42] george.tudor1906 |
poo:laboratoare:13 [2026/01/12 08:46] (current) george.tudor1906 |
||
|---|---|---|---|
| Line 1: | Line 1: | ||
| - | ===== Breviar 13 ===== | + | ===== Laboratorul 13 – Fluxuri I/O. Funcționale ===== |
| - | ==== 1. Fluxuri (Streams) ==== | + | {{:poo:laboratoare:arhiva_13.zip|Arhiva laborator}} |
| - | === 1.1 Introducere === | + | === Problema 1 === |
| - | Dennis Ritchie implementează în 1984 primul sistem I/O pe bază de stream în cadrul | + | Să se scrie un program pentru afișarea pe ecran a liniilor aflate pe poziții impare |
| - | sistemului de operare Unix. Acest concept are la bază crearea unui canal de comunicație | + | dintr-un fișier text. Fiecare linie va fi precedată de numărul ei și un spațiu. |
| - | între două entități: **sursă** și **destinație**. Sursa scrie informații în canalul de | + | |
| - | comunicație, iar destinația poate să citească aceste date, canalul permițând trecerea | + | |
| - | fluxului de date într-o singură direcție. | + | |
| - | **Clasificarea fluxurilor:** | + | În implementare, se va folosi un obiect de tip **LineNumberReader**. |
| - | După direcția canalului de comunicație: | + | Cerințe: |
| - | * fluxuri de intrare (pentru citirea datelor) | + | * Tratați toate excepțiile care ar putea să apară exact acolo unde apar! |
| - | * fluxuri de ieșire (pentru scrierea datelor) | + | * Atenție la închiderea fișierelor – să se facă chiar dacă apare excepție la citire! |
| - | După tipul de date pe care le operează: | + | Pentru validarea acestei cerințe, puteți folosi fișierul text //test01.in// pus la |
| - | * fluxuri de octeți (transfer serial pe 8 biți) | + | dispoziție în arhiva laboratorului. |
| - | * fluxuri de caractere (transfer serial pe 16 biți) | + | |
| - | După acțiunea lor: | + | === Problema 2 === |
| - | * fluxuri primare de citire/scriere a datelor | + | |
| - | * fluxuri pentru procesarea datelor | + | |
| - | Datorită faptului că există două direcții de comunicare, există două tipuri mari de | + | Să se scrie un program care citește un text de la tastatură și îl salvează într-un |
| - | stream-uri pentru orice nod de comunicație: **input stream** și **output stream**. | + | fișier pe disc. Citirea se va face până la introducerea cuvântului **exit**. |
| - | Tastatura ar fi un exemplu de input stream, iar monitorul un output stream. | + | |
| - | Sursa și destinația nu trebuie să fie neapărat periferice, ele pot fi și module soft. | + | |
| - | === 1.2 Fluxuri de octeți === | + | În implementare se va utiliza metoda **readLine()** pentru un obiect de tip |
| + | **DataInputStream** sau **BufferedReader**. | ||
| - | Programele folosesc fluxuri de octeți pentru a citi sau scrie date pe 8 biți (un octet). | + | Tratați toate excepțiile care ar putea să apară! |
| - | Fluxurile la nivel de octet utilizează două ierarhii având drept clase rădăcină: | + | |
| - | **InputStream** și **OutputStream**. | + | |
| - | Cele mai utilizate clase sunt **FileInputStream** și **FileOutputStream**. | + | === Problema 3 === |
| - | <code java> | + | Să se implementeze un program care citește din fișierul //test02.in// un text și |
| - | public class Copy { | + | determină numărul de cuvinte din text. |
| - | private String input, output; | + | |
| - | public Copy(String input, String output) { | + | Pentru citire se vor utiliza un obiect de tip **FileReader** și unul de tip **StreamTokenizer**. |
| - | this.input = input; | + | |
| - | this.output = output; | + | |
| - | } | + | |
| - | + | ||
| - | public void copyFile() { | + | |
| - | InputStream in = null; | + | |
| - | OutputStream out = null; | + | |
| - | try { | + | |
| - | in = new FileInputStream(input); | + | |
| - | out = new FileOutputStream(output); | + | |
| - | while (in.available() > 0) | + | |
| - | out.write(in.read()); | + | |
| - | } catch (FileNotFoundException e) { | + | |
| - | e.printStackTrace(); | + | |
| - | } catch (IOException e) { | + | |
| - | e.printStackTrace(); | + | |
| - | } finally { | + | |
| - | try { | + | |
| - | if (out != null) out.close(); | + | |
| - | if (in != null) in.close(); | + | |
| - | } catch (IOException e) { | + | |
| - | e.printStackTrace(); | + | |
| - | } | + | |
| - | } | + | |
| - | } | + | |
| - | } | + | |
| - | </code> | + | |
| - | + | ||
| - | <note warning> | + | |
| - | IMPORTANT! Fișierele text trebuie să se afle în folderul rădăcină al proiectului | + | |
| - | care conține clasele care prelucrează informația din aceste fișiere. | + | |
| - | </note> | + | |
| - | + | ||
| - | === 1.3 Fluxuri de caractere === | + | |
| - | + | ||
| - | Limbajul Java stochează valorile de tip caracter folosind convenții Unicode. | + | |
| - | Fluxurile de caractere I/O își translatează automat formatul intern de la setul | + | |
| - | de caractere locale. | + | |
| - | + | ||
| - | Toate clasele de fluxuri de caractere moștenesc clasele **Reader** și **Writer**. | + | |
| - | Pentru operațiile I/O cu fișiere: **FileReader** și **FileWriter**. | + | |
| <code java> | <code java> | ||
| - | public class Read { | + | FileReader in = new FileReader(new File("test02.in")); |
| - | private String input; | + | StreamTokenizer str = new StreamTokenizer(in); |
| - | + | ||
| - | public Read(String input) { | + | |
| - | this.input = input; | + | |
| - | } | + | |
| - | + | ||
| - | public void read() { | + | |
| - | FileReader stream = null; | + | |
| - | BufferedReader br = null; | + | |
| - | try { | + | |
| - | stream = new FileReader(input); | + | |
| - | br = new BufferedReader(stream); | + | |
| - | String line = br.readLine(); | + | |
| - | while (line != null) { | + | |
| - | System.out.println(line); | + | |
| - | line = br.readLine(); | + | |
| - | } | + | |
| - | } catch (FileNotFoundException e) { | + | |
| - | e.printStackTrace(); | + | |
| - | } catch (IOException e) { | + | |
| - | e.printStackTrace(); | + | |
| - | } finally { | + | |
| - | try { | + | |
| - | if (stream != null) stream.close(); | + | |
| - | if (br != null) br.close(); | + | |
| - | } catch (IOException e) { | + | |
| - | e.printStackTrace(); | + | |
| - | } | + | |
| - | } | + | |
| - | } | + | |
| - | } | + | |
| </code> | </code> | ||
| - | <note> | + | === Problema 4 – Funcționale === |
| - | Observație: Obiectul BufferedReader creează un flux de intrare a caracterelor cu | + | |
| - | stocare temporară (și posibilitate de citire a caracterelor sub formă de linii) | + | |
| - | din fluxul de intrare a caracterelor primit ca parametru. | + | |
| - | </note> | + | |
| - | === 1.4 Fluxuri cu zone tampon (Buffer) === | + | Realizați o arhitectură unificată, similară cu Collections, pentru manipularea listelor |
| + | care să conțină: | ||
| - | Pentru un flux I/O fără zone tampon, fiecare cerere de citire sau scriere este | + | * Interfața **Function**, parametrizată corespunzător, care conține o metodă **execute**, având ca parametru un obiect de tip **A** și un rezultat de tip **B**, unde **A** și **B** sunt două tipuri formale; |
| - | administrată direct de sistemul de operare. Aceasta face ca programul să fie mai | + | |
| - | puțin eficient, deoarece fiecare cerere declanșează accesul la disc, activitate | + | |
| - | în rețea sau alte operații care consumă timp. | + | |
| - | Pentru a reduce timpul de procesare, platforma Java implementează fluxuri I/O cu | + | * Interfața **Addition** folosită pentru a calcula suma a două numere de tip **T**, unde **T** este un subtip al lui **Number** (aceasta va conține o metodă **zero** care va întoarce elementul neutru al operației de adunare și o metodă **add**, care primește două obiecte de tip **T** și returnează suma lor); |
| - | zone tampon: | + | |
| - | * Fluxurile de intrare cu zone tampon citesc datele dintr-o zonă de memorie cunoscută ca **buffer** | + | |
| - | * API-ul de intrare este apelat numai când tamponul este gol | + | |
| - | * Similar, fluxurile de ieșire scriu datele în zona de tampon și API-ul de ieșire este apelat când tamponul este plin | + | |
| - | Un program poate converti un flux fără zonă tampon într-un flux cu zonă tampon | + | * Doi algoritmi polimorfici: **reverse** care inversează elementele unei liste și **sum** care calculează suma elementelor din listă; acești algoritmi trebuie să poată fi folosiți pe implementări diferite de liste, deoarece le abordează la nivel de interfață; |
| - | astfel: un obiect de tip flux fără zonă tampon este trecut ca argument unui | + | |
| - | constructor pentru o clasă de tip flux cu zonă tampon. | + | |
| - | + | ||
| - | === 1.5 Fluxuri standard === | + | |
| - | + | ||
| - | Limbajul Java pune la dispoziția utilizatorului trei fluxuri standard pentru | + | |
| - | comunicare cu consola: | + | |
| - | + | ||
| - | | **Flux** | **Descriere** | **Referință** | | + | |
| - | | Standard Input | pentru citirea datelor | System.in (InputStream) | | + | |
| - | | Standard Output | pentru afișarea datelor | System.out (PrintWriter) | | + | |
| - | | Standard Error | pentru afișarea erorilor | System.err (PrintWriter) | | + | |
| - | + | ||
| - | <note> | + | |
| - | Observație: Nu este necesară instanțierea acestor trei stream-uri, deoarece ele | + | |
| - | se deschid automat înaintea execuției aplicației. De asemenea, acestea nici nu | + | |
| - | se închid prin apelul metodei close(). | + | |
| - | </note> | + | |
| - | + | ||
| - | **Exemplu cu InputStreamReader și BufferedReader:** | + | |
| - | + | ||
| - | <code java> | + | |
| - | public class Suma { | + | |
| - | int a, b; | + | |
| - | + | ||
| - | public void read() { | + | |
| - | InputStreamReader stream = null; | + | |
| - | BufferedReader br = null; | + | |
| - | try { | + | |
| - | stream = new InputStreamReader(System.in); | + | |
| - | br = new BufferedReader(stream); | + | |
| - | String line = br.readLine(); | + | |
| - | a = Integer.parseInt(line); | + | |
| - | line = br.readLine(); | + | |
| - | b = Integer.parseInt(line); | + | |
| - | } catch (IOException e) { | + | |
| - | e.printStackTrace(); | + | |
| - | } finally { | + | |
| - | try { | + | |
| - | if (stream != null) stream.close(); | + | |
| - | if (br != null) br.close(); | + | |
| - | } catch (IOException e) { | + | |
| - | e.printStackTrace(); | + | |
| - | } | + | |
| - | } | + | |
| - | } | + | |
| - | } | + | |
| - | </code> | + | |
| - | + | ||
| - | **Exemplu cu Scanner:** | + | |
| - | + | ||
| - | <code java> | + | |
| - | public class Test { | + | |
| - | public static void main(String args[]) throws Exception { | + | |
| - | Scanner in = new Scanner(System.in); | + | |
| - | int a, b; | + | |
| - | a = in.nextInt(); | + | |
| - | b = in.nextInt(); | + | |
| - | int result = a + b; | + | |
| - | System.out.println(a + " + " + b + " = " + result); | + | |
| - | in.close(); | + | |
| - | } | + | |
| - | } | + | |
| - | </code> | + | |
| - | + | ||
| - | ==== 2. Funcționale ==== | + | |
| - | + | ||
| - | === 2.1 Introducere === | + | |
| - | + | ||
| - | În paradigma funcțională, funcțiile sunt valori de ordinul 1 ce pot fi manipulate | + | |
| - | ca orice altă valoare. **Funcționalele** sunt funcții care manipulează alte funcții, | + | |
| - | primindu-le ca argumente sau returnându-le ca rezultat. | + | |
| - | + | ||
| - | === 2.2 Lista funcționalelor === | + | |
| - | + | ||
| - | == foldl(function, init, list) == | + | |
| - | + | ||
| - | Returnează rezultatul aplicării funcției **function** pe rând asupra unui element | + | |
| - | din listă și a unui acumulator **init**. Ordinea folosirii elementelor din listă | + | |
| - | este de la **stânga la dreapta**. | + | |
| - | + | ||
| - | <code> | + | |
| - | foldl(f(x, y) = x + y, 5, [0, 1, 2, 3]) | + | |
| - | = f(f(f(f(5, 0), 1), 2), 3) | + | |
| - | = f(f(f(5, 1), 2), 3) | + | |
| - | = f(f(6, 2), 3) | + | |
| - | = f(8, 3) | + | |
| - | = 11 | + | |
| - | </code> | + | |
| - | + | ||
| - | == foldr(function, init, list) == | + | |
| - | + | ||
| - | Are un comportament similar cu foldl, însă ordinea folosirii elementelor din listă | + | |
| - | este de la **dreapta la stânga**. | + | |
| - | + | ||
| - | <code> | + | |
| - | foldr(f(x, y) = y, 4, [0, 1, 2, 3]) | + | |
| - | = f(0, f(1, f(2, f(3, 4)))) | + | |
| - | = f(0, f(1, f(2, 4))) | + | |
| - | = f(0, f(1, 4)) | + | |
| - | = f(0, 4) | + | |
| - | = 0 | + | |
| - | </code> | + | |
| - | + | ||
| - | == map(function, list) == | + | |
| - | + | ||
| - | Returnează lista rezultatelor aplicării unei funcții **f** asupra fiecărui element | + | |
| - | dintr-o listă. | + | |
| - | + | ||
| - | <code> | + | |
| - | map(f(x) = 2*x, [0, 1, 2, 3]) => [0, 2, 4, 6] | + | |
| - | </code> | + | |
| - | + | ||
| - | == filter(predicat, list) == | + | |
| - | + | ||
| - | Returnează lista elementelor dintr-o listă care satisfac un predicat **p**. | + | |
| - | Un predicat este o funcție care are un rezultat de tip Boolean. | + | |
| - | + | ||
| - | <code> | + | |
| - | filter(f(x) = x % 2 == 0, [0, 1, 2, 3]) => [0, 2] | + | |
| - | </code> | + | |
| - | + | ||
| - | == reduce(function, list) == | + | |
| - | + | ||
| - | Aplică funcția pentru primele două elemente din listă, apoi pentru rezultatul | + | |
| - | obținut anterior și următorul element și tot așa. | + | |
| - | + | ||
| - | <code> | + | |
| - | reduce(f(x, y) = x + y, [47, 11, 42, 13]) | + | |
| - | = f(f(f(47, 11), 42), 13) | + | |
| - | = f(f(58, 42), 13) | + | |
| - | = f(100, 13) | + | |
| - | = 113 | + | |
| - | </code> | + | |
| - | + | ||
| - | == all(predicat, list) == | + | |
| - | + | ||
| - | Primește un predicat și verifică dacă **toate** elementele din listă satisfac predicatul. | + | |
| - | + | ||
| - | <code> | + | |
| - | all(f(x) = x > 0, [0, 1, 2, 3]) => False (0 nu e > 0) | + | |
| - | all(f(x) = x >= 0, [0, 1, 2, 3]) => True | + | |
| - | </code> | + | |
| - | + | ||
| - | == any(predicat, list) == | + | |
| - | + | ||
| - | Primește un predicat și verifică dacă există **cel puțin un element** în listă | + | |
| - | care satisface predicatul. | + | |
| - | + | ||
| - | <code> | + | |
| - | any(f(x) = x < 0, [1, 2, 3, 4]) => False | + | |
| - | any(f(x) = x % 2 == 0, [1, 2, 3]) => True (2 e par) | + | |
| - | </code> | + | |
| - | === 2.3 Rezumat funcționale === | + | * O serie de metode care au un comportament similar cu funcționalele din paradigma funcțională. |
| - | | **Funcțională** | **Descriere** | **Exemplu** | | + | Veți porni implementarea de la clasa **ListUtil**, pusă la dispoziție în arhiva laboratorului. |
| - | | **foldl** | Agregare stânga→dreapta cu acumulator | foldl(+, 0, [1,2,3]) = 6 | | + | |
| - | | **foldr** | Agregare dreapta→stânga cu acumulator | foldr(-, 0, [1,2,3]) = 2 | | + | |
| - | | **map** | Transformă fiecare element | map(x→x*2, [1,2]) = [2,4] | | + | |
| - | | **filter** | Păstrează elementele care satisfac predicatul | filter(par, [1,2,3]) = [2] | | + | |
| - | | **reduce** | Agregare fără acumulator inițial | reduce(+, [1,2,3]) = 6 | | + | |
| - | | **all** | Toate satisfac predicatul? | all(>0, [1,2]) = True | | + | |
| - | | **any** | Există cel puțin unul care satisface? | any(<0, [1,2]) = False | | + | |