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ò permetterà 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 all'indirizzo: [Elenco di tutti i Corsi disponibili].
Per il momento, il programma è aperto in via sperimentale agli studenti dei corsi "Laboratorio di Informatica Applicata" e "Laboratorio di Informatica nell'Artigianato". Di certo, i corsi costituiscono una ulteriore preziosa opportunità di studio ed approfondimento personale. Per accedere ai corsi è necessario un codice di accesso. Invito tutti gli studenti interessati a rivolgersi al docente, Roberto Sassi [sassi@dti.unimi.it] che vi fornirà le informazioni necessarie.
Una volta ricevuto i codici, trovate le istruzioni per l'accesso 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:
È essenziale complementare quanto visto a lezione con la lettura di un manuale Java! Per quanto riguarda il libro adottato [Cay S. Horstmann, “Concetti di informatica e fondamenti di Java”, terza edizione, Apogeo 2005], i capitoli trattati fino a questa lezione compresa sono: 1, 2, 3, 4, 6, 7, 8, 9, 10 (solo 10.1, 10.2 e 10.3), 12, 14, 15, 16 (solo 16.1 e 16.2).
Se invece avete preferito utilizzare il libro: [Y. Daniel Liang, Introduction to Java programming (comprehensive version), Fifth edition, 2005] i capitoli da studiare sono: 1, 2, 3, 4, 5 (eccetto 5.6 e 5.7), 6, 7, 8, 9 (solo 9.4, 9.5 e 9.6), 10 (opzionale ma suggerito), 15 (eccetto 15.7 e 15.8), 16 (eccetto 16.9, 16.10 e 16.11), 18 (solo 18.5.2; le sezioni 18.1, 18.2, 18.3, 18.4 e 18.5.1 sono opzionali ma suggerite).
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:
In java esiste un metodo standard per scrivere i commenti. E' 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 cosiderazione). 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 tutto 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 VC7.1 e GCC 3.4.2). I test sono stati eseguiti su di un portatile Pentium M 735 1.7MHz (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 beta-server |
334.53 | 197.94 | 630.96 | 108.11 | 215.30 | 520.33 |
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 beta |
258.50 | 172.09 | 469.30 | 56.06 | 257.61 | 337.45 |
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 |
GNU GCC 3.4.2 (MinGW) | 465.81 | 289.36 | 521.36 | 113.12 | 477.67 | 927.54 |
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:
GNU GCC 3.4.2 (MinGW) | gcc -O2 -march=pentium-m -funroll-loops |
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'.
©2006 Roberto Sassi