Sun Academic Initiative - SAI - è un programma ideato per rafforzare la collaborazione fra Sun e le Istituzioni Accademiche [ulteriori informazioni al sito SUN Microsystems].
Il nostro dipartimento ha aderito nel Giugno 2005 al programma. Ciò permette agli studenti, afferenti al Polo di Crema, di poter frequentare gratuitamente corsi di formazione on-line messi a disposizione direttamente da SUN Microsystems. I corsi vertono principalmente sulle tecnologie Java e Solaris. Una lista completa dei corsi la potete trovare in questo file.
Per accedere ai corsi è necessario seguire le istruzioni che trovate a questo indirizzo. Tenete presente che ogni corso deve essere completato entro 90 giorni, calcolati dal momento in cui è stato fatto partire. Conviene pertanto selezionare e fare partire solo i corsi che si intende seguire.
OBIETTIVI DELLA LEZIONE
In questa lezione:
Senza la possibilità di interagire con il resto del mondo, molti programmi sarebbero inutili. L'interazione di un programma con il resto del mondo è l'input/output. Storicamente, una delle parti più difficili nel design di un linguaggio di programmazione era la gestione dell'innumerevole quantità di sistemi di input o output. Java (come altri linguaggi di programmazione) utilizza una astrazione per rappresentare l'I/O: gli streams. Qualunque sia la periferica attraverso la quale vogliamo che il nostro programma comunichi, (un file, una connessione di rete, etc) ci basterà utilizzare lo stream opportuno.
Esistono due modi fondamentalmente diversi per memorizzare i dati:
31 36 31 30 37 31
che codificano i caratteri '1' '6' '1' '0' '7' '1'.
Notate che alcune codifiche prevedono che un carattere venga codificato con più codici. Ad Esempio, con UTF-8:
00000000 00000010 01110101 00101111
Tutte le volte che vorremo interagire con una periferica comunicando in formato testo dovremo utilizzare le classi Reader e Writer e le loro sottoclassi.
Quando invece vorremo utilizzare il formato binario dovremo utilizzare le classi InputStream e OutputStream (e loro sottoclassi).
Operare con un file fondamentalmente significa:
File fileAppunti = new File("appunti.txt");
FileReader appuntiLezione = new FileReader(fileAppunti);mentre per scrivere dobbiamo creare un oggetto FileWriter:
FileWriter appuntiLezione = new FileWriter(fileAppunti);Questi oggetti associano al file uno stream, con il quale operare.
appuntiLezione.close();
Ad esempio ecco un programma che legge il file sanmartino.txt e lo stampa a video:
import java.io.*; class SanMartino { public static void main (String[] args) throws IOException { File filePoesia = new File("sanmartino.txt"); FileReader streamPoesia = new FileReader(filePoesia); int carattereLetto; while( (carattereLetto=streamPoesia.read()) != -1 ) { System.out.print((char)carattereLetto); } System.out.print('\n'); streamPoesia.close(); } }
In questo altro esempio, il contenuto del file sanmartino.txt viene copiato, una volta reso maiuscolo, nel file sanmartinomaiuscolo.txt
import java.io.*; class SanMartinoMaiuscolo { public static void main (String[] args) throws IOException { File filePoesiaIn = new File("sanmartino.txt"); File filePoesiaOut = new File("sanmartinomaiuscolo.txt"); FileReader streamPoesiaIn = new FileReader(filePoesiaIn); FileWriter streamPoesiaOut = new FileWriter(filePoesiaOut); int carattereLetto; char carattereDaScrivere; while( (carattereLetto=streamPoesiaIn.read()) != -1 ) { carattereDaScrivere = Character.toUpperCase((char)carattereLetto); streamPoesiaOut.write(carattereDaScrivere); } streamPoesiaIn.close(); streamPoesiaOut.close(); } }
L'I/O in formato binario è assolutamente analogo a quanto visto per i file in formato testo. A differenza di prima dobbiamo: per leggere istanziare un oggetto FileInputStream:
FileInputStream appuntiLezioneBinario = new FileInputStream(fileAppunti);
mentre per scrivere serve un oggetto FileOutputStream:
FileOutputStream appuntiLezioneBinario = new FileOutputStream(fileAppunti);Anche in questo caso abbiamo a disposizione per leggere e scrivere i metodi read() e write().
Leggere semplicemente un carattere non è sempre ottimale. Molte volte
quello che ci interessa è leggere da un file un numero (che è composto
da più caratteri ad esempio). Per fare questo dobbiamo "incartare"
("wrap") lo steam di input in un BufferedReader. Lo stream
viene ora riversato in un buffer e possiamo leggerne una linea alla
volta, con il metodo readLine.
Per scomporre in parti la linea letta, possiamo utilizzare il metodo
split(token) della classe String. token
è un carattere separatore.
Vediamo ad esempio un programma che legge la seconda colonna del file
altezze.txt e
ne calcola la media.
import java.io.*; class MediaAltezze { public static void main(String[] args) throws IOException { File fileAltezze = new File("altezze.txt"); BufferedReader streamAltezze = new BufferedReader(new FileReader(fileAltezze)); String lineaLetta; double media = 0; int numeroUtenti = 0; while( (lineaLetta = streamAltezze.readLine()) != null ) { String[] porzioniLineaLetta = lineaLetta.split("\t"); media += Double.parseDouble(porzioniLineaLetta[1]); numeroUtenti++; } media/=numeroUtenti; System.out.println("La media delle altezze è " + media); streamAltezze.close(); } }
Anche scrivere un carattere alla volta non è sempre ottimale. Per esempio quando vogliamo scrivere un numero a virgola mobile in un file. Per semplificarci la vita, possiamo "incartare" lo stream testo di scrittura in un oggetto della classe PrintWriter. In questo modo possiamo utilizzare i metodi print() e println() che fanno per noi il lavoro di scrivere carattere per carattere la codifica della nostra variabile. Vediamo ad esempio un programma che genera 10 numeri casuali e li stampa nel file provatesto.txt:
import java.io.*; class ProvaScrittura { public static void main(String[] args) throws IOException { double[] numeri = new double[10]; for(int i=0; i<10; i++) numeri[i] = Math.random(); File fileProvaTesto = new File("provatesto.txt"); PrintWriter streamProvaTesto = new PrintWriter(new FileWriter(fileProvaTesto)); for(int i=0; i<10; i++) streamProvaTesto.println(numeri[i]); streamProvaTesto.close(); } }
Il file provatesto.txt contiene:
0.17607143616534005 0.5401234521919662 0.3810251267294814 0.6677822896261097 0.9010739531281279 0.5243986237412629 0.19966383854395364 0.9257102581342692 0.9393950203062066 0.9813927079940031
Su ogni oggetto della classe file possiamo operare in modo da svolgere tutte quelle operazioni necessarie per manipolare un file:
Abbiamo visto come scrivere informazioni su files. Ma come farlo in codifiche diverse da quelle standard? Vediamo in un esempio come scrivere un file con codifica UTF-8 invece della codifica di default di sistema ("Cp1252" in Windows XP).
import java.io.*; class SanMartinoMaiuscolo_UTF8 { public static void main (String[] args) throws IOException { File filePoesiaIn = new File("sanmartino.txt"); File filePoesiaOut = new File("sanmartinomaiuscolo.txt"); FileReader streamPoesiaIn = new FileReader(filePoesiaIn); // Dobbiamo utilizzare uno stream differente da FileWriter! // Per prima cosa dobbiamo creare uno stream binario FileOutputStream // Quindi, dobbiamo incartare lo stream binario in uno testuale che utilizzi la // codifica opportuna: OutputStreamWriter streamPoesiaOut = new OutputStreamWriter(new FileOutputStream(filePoesiaOut), "UTF-8"); // Per verificare il tipo di codifica che stiamo utilizzando System.out.println(streamPoesiaOut.getEncoding()); int carattereLetto; char carattereDaScrivere; while( (carattereLetto=streamPoesiaIn.read()) != -1 ) { carattereDaScrivere = Character.toUpperCase((char)carattereLetto); streamPoesiaOut.write(carattereDaScrivere); } streamPoesiaIn.close(); streamPoesiaOut.close(); } }
In java esiste un metodo standard per scrivere i commenti. È il terzo modo di inserire commenti in un file Java che vediamo.
/** * Return the real number represented by the string s, * or return Double.NaN if s does not represent a legal * real number. * * @param s String to interpret as real number. * @return the real number represented by s. */ public static double stringToReal(String s) { try { return Double.parseDouble(s); } catch (NumberFormatException e) { return Double.NaN; } }
Tipici tag che possiamo utilizzare sono:
Per produrre automaticamente la documentazione basta digitare dalla riga di comando:
javadoc nomefile.java
Aggiungiamo un commento Javadoc alla classe CercaNumeroRipetizioni, soluzione all'esercizio 5 della lezione 10
import javax.swing.*; /** * Cerca il numero di numero di ripetizioni in una sequenza composta solo da * caratteri 'A' o 'B' * * @author Roberto Sassi * @version 1.0 * */ public class CercaNumeroRipetizioni { public static void main(String[] args) { ...
La documentazione generata da Javadoc è la seguente: index.html
La struttura dati fornita da un array è efficace e performante, ma non permette il ridimensionamento dinamico. Dato un array, per averne uno più capiente dobbiamo crearne uno nuovo e copiare tutto il contenuto del vecchio nel nuovo ... Decisamente tedioso da programmare ogni volta. La libreria di classi Java ci fornisce i Vettori, vediamone le caratteristiche in queste slides.
Un esempio, un programma che permette all'utente di inserire una serie di numeri a piacere e ne calcola la media. L'immissione termina quando l'utente preme invio senza inserire alcun numero.import java.util.*; import javax.swing.*; class EsempioVector { public static void main(String[] args) { String stringaInput; double numero, media = 0.0; Vector elencoNumeri = new Vector(); do { stringaInput = JOptionPane.showInputDialog("Inserisci un numero!"); if(!stringaInput.equals("")) { numero = Double.parseDouble(stringaInput); elencoNumeri.addElement(new Double(numero)); } } while(!stringaInput.equals("")); for(int i=0; i<elencoNumeri.size(); i++) media += ((Double) elencoNumeri.elementAt(i)).doubleValue(); media /= elencoNumeri.size(); JOptionPane.showMessageDialog(null, "Hai inserito " + elencoNumeri.size() + " numeri; la loro media è " + media + "."); System.exit(0); } }
Abbiamo visto durante la prima parte del corso che un programma Java viene compilato con il compilatore Java (javac) in modo da produrre il bytecode che viene poi eseguito dalla Virtual Machine.
Nelle prime versioni di Java, il bytecode veniva interpretato dalla JVM. Di conseguenza l'esecuzione era più lenta. Se per certe applicazioni questo non era comunque un problema (la portabilità ripagava la lentezza), lo era per altre (pensate ad applicazioni numeriche, al limite i giochi elettronici).
Per questo motivo, la JVM venne migliorata con una tecnologia detta "just in time" (JIT, pressapoco a partire dalla versione Java 1.1). Quando una certa classe veniva invocata, il bytecode veniva compilato dalla JVM JIT nel codice oggetto della piattaforma in uso. La compilazione selettiva (solo quando una porzione di codice era invocata) riduceva i tempi morti iniziali. La tecnologia JIT ebbe un discreto successo. La JVM JIT Microsoft (jview, oggi ferma alla versione 1.1.4) che la implementava bene era (ed è tuttora) piuttosto veloce.
Anche la semplice compilazione JIT aveva i suoi limiti. Infatti, l'ottimizzazione che la JVM JIT poteva eseguire era molto limitata (il codice che non era ancora stato invocato non veniva preso in considerazione). La generazione successiva di JVM incorporò quindi una nuova tecnologia detta "HotSpot" (in forma matura, dalla versione Java 1.3): il codice viene prima interpretato e compilato nel codice oggetto della piattaforma nativa, solo quando considerato "Hot", cioè quando la compilazione ne ridurrebbe decisamente i tempi di esecuzione (tenuto conto dei tempi non indifferenti di compilazione). La JVM HotSpot, interpretando prima il codice, può eseguire delle ottimizzazioni ben più significative. Letteralmente, una JVM JIT compila il codice prima di eseguirlo, ma generalmente anche una JVM HotSpot è chiamata JIT.
Le JVM attuali utilizzano la tecnologia HotSpot. Inoltre, per applicazioni che devono essere molto veloci è possibile richiedere un livello di ottimizzazione HotSpot superiore (al prezzo di un tempo più significativo di attesa quando si lancia l'applicazione) tramite l'opzione -server.
Tanto per avere un'idea della reale velocità di Java, ecco un test effettuato utilizzando un benchmark per il calcolo numerico e scientifico (SciMark 2.0a). Nella tabella è riportata, per ogni JVM, una stima del numero di milioni di operazioni in virgola mobile effettuate al secondo (MegaFlops). Più il numero è alto e più la JVM è veloce. Per un confronto, sono riportati anche i risultati ottenuti compilando un codice equivalente in C con due diversi compilatori (Microsoft e GNU GCC). I test sono stati eseguiti su di un portatile Pentium M 735 1.7 MHz (Windows XP).
(Mflops) | ||||||
---|---|---|---|---|---|---|
Score | FFT | SOR | Monte Carlo | Sparse matmult | LU | |
Sun Microsystems Inc. JVM 1.5.0 -server |
335.03 | 190.17 | 637.01 | 70.90 | 260.68 | 516.39 |
Sun Microsystems Inc. JVM 1.6.0 -server |
329.38 | 191.84 | 626.86 | 110.79 | 212.50 | 504.93 |
IBM Corporation JVM 1.4.2 |
309.24 | 213.44 | 493.33 | 77.69 | 283.34 | 478.39 |
IBM Corporation JVM 1.4.1 |
307.63 | 213.44 | 484.77 | 80.01 | 283.22 | 476.72 |
IBM Corporation JVM 1.5.0 |
271.77 | 202.54 | 473.92 | 92.44 | 175.46 | 414.49 |
Sun Microsystems Inc. JVM 1.6.0 |
256.44 | 172.83 | 467.03 | 52.97 | 253.62 | 335.79 |
Microsoft Corp. JVM 1.1.4 |
254.34 | 108.85 | 462.54 | 36.52 | 203.28 | 460.48 |
Sun Microsystems Inc. JVM 1.4.2 |
235.03 | 97.20 | 496.00 | 33.17 | 227.24 | 321.56 |
Sun Microsystems Inc. JVM 1.5.0 |
233.13 | 104.67 | 495.87 | 40.49 | 210.46 | 314.16 |
Sun Microsystems Inc. JVM 1.3.1_02 |
219.37 | 107.25 | 478.63 | 35.65 | 164.04 | 311.29 |
Sun Microsystems Inc. JVM 1.4.1_02 |
196.26 | 87.81 | 469.30 | 40.73 | 173.10 | 210.37 |
INTEL C Compiler 11.0 | 480.87 | 361.43 | 509.10 | 183.61 | 566.68 | 783.55 |
GNU GCC 3.4.2 (MinGW) | 465.81 | 289.36 | 521.36 | 113.12 | 477.67 | 927.54 |
GNU GCC 4.2.1 (MinGW) | 441.38 | 341.47 | 534.52 | 100.39 | 330.49 | 900.02 |
Microsoft VC 8 (2005) | 396.94 | 271.27 | 634.91 | 69.80 | 445.22 | 563.49 |
Microsoft VC 7.1 (.NET 2003) | 364.28 | 267.93 | 610.76 | 63.22 | 446.73 | 432.75 |
Le opzioni utilizzate con i due compilatori C sono state:
INTEL C Compiler 11.0 | icl /O2 /QxHOST /Qipo |
GNU GCC 3.4.2 (MinGW) | gcc -O2 -march=pentium-m -funroll-loops |
GNU GCC 4.2.1 (MinGW) | gcc -O2 -march=pentium-m -funroll-loops |
Microsoft VC 8 (2005) | cl -Za -W3 -nologo -Ox /arch:SSE2 |
Microsoft VC 7.1 (.NET 2003) | cl -Za -W3 -nologo -Ox /G7 /arch:SSE2 |
La JVM più veloce (1.5.0 HotSpot, versione server) è risultata essere solo
l'8% più lenta della versione C compilata con Microsoft VC 7.1 (che è
un buon compilatore).
Il test è molto limitato, ma mostra come le nuove JVM abbiano fatto molti progressi
in termini di velocità di elaborazione. Java non è più così lento...
Esercizio 1 [Soluzione]
Dato il file sanmartino.txt, scrivere un programma che conti il numero di ripetizioni della lettera 'o'. [Soluzione: 15 volte]
Esercizio 2 [Soluzione A, Soluzione B]
Scrivere la classe ScriviNumeroIntero che chieda all'utente un numero intero positivo e lo aggiunga, incrementato di 1, al file numeri.txt. Dovete utilizzare SOLO la classe FileWriter e NON la classe PrintWriter. [Suggerimenti: 1) utilizzate il costruttore della classe FileWriter in modalità append (consultate l'help in linea!); 2) utilizzate il metodo Math.log10() per sapere quanti caratteri scrivere su file; 3) dividete il numero intero ripetutamente per 10 e utilizzate il resto della divisione intera per procedere].
Successivamente ripetete l'esercizio utilizzando la classe PrintWriter. Molto più facile, o no?
Esercizio 3 [Soluzione]
Dato il file COSTITUZ.TXT, scrivere un programma che conti il numero di ripetizioni della parola "Repubblica".
Esercizio 4 [Soluzione]
Scrivere un programma che chieda all'utente di inserire 10 numeri double e li scriva sul file "miatabella.txt" ordinati su due colonne (due dati per riga).
Esercizio 5
Prendete un esercizio che avete completato e commentatelo opportunamente (con un commento Javadoc). Eseguite Javadoc e analizzatene l'output.
Esercizio 6 [Soluzione]
Il file sequenzadna.txt, contiene la sequenza delle basi che codifica il gene umano dei ricettori di blu. Scrivere un programma che conti il numero di ripetizioni di ciascuna base. Le basi sono 4: 'a', 'c', 'g' e 't'.
In questo ulteriore esempio aggiungiamo finalmente la possibilità di collegare delle "azioni" ai nostri bottoni grazie agli ActionListener
import javax.swing.*; class Finestrella extends JFrame { public Finestrella() { java.awt.BorderLayout layout = new java.awt.BorderLayout(); this.setLayout(layout); JLabel etichetta = new JLabel("Inserisci la tua email:"); JTextField campoTesto = new JTextField(16); JButton bottone = new JButton("Conferma"); this.add(etichetta, java.awt.BorderLayout.EAST); this.add(campoTesto, java.awt.BorderLayout.CENTER); this.add(bottone, java.awt.BorderLayout.SOUTH); // Aggiunge il listener BottoneListenerClass mioListener = new BottoneListenerClass(); bottone.addActionListener(mioListener); this.setTitle("Configura Email"); this.setSize(400,90); this.setLocationRelativeTo(null); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } } public class QuintaProvaFrame { public static void main(String[] args) { Finestrella mioDialog = new Finestrella(); mioDialog.setVisible(true); } } class BottoneListenerClass implements java.awt.event.ActionListener { public void actionPerformed(java.awt.event.ActionEvent e) { System.out.println("Hai premuto il bottone!"); } }
©2008 Roberto Sassi