Sun Academic Initiative

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", "Laboratorio di Informatica nell'Artigianato" e "Laboratorio di Programmazione ad Oggetti". 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.




Utilizzo dei files. Javadoc e Vettori

Martedì, 29 Ottobre 2007

OBIETTIVI DELLA LEZIONE

In questa lezione:

  1. Impareremo ad utilizzare i files
  2. Vedremo come scrivere commenti standard

Nota

È 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).

Input/Output

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:

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).

Files

Operare con un file fondamentalmente significa:

  1. Istanziare un oggetto della classe File
  2. Istanziare uno stream opportuno (adatto a quanto vogliamo fare);
  3. Leggere o scrivere almeno un carattere;
  4. Gestire le eccezioni che possono presentarsi;
  5. Chiudere lo stream

I/O da file in formato testo: BASE

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();
  }
}

I/O da file in formato binario

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().

I/O da file in formato testo: AVANZATO

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

La classe File

Su ogni oggetto della classe file possiamo operare in modo da svolgere tutte quelle operazioni necessarie per manipolare un file:

Utilizzare file con codifiche diverse da quella di default

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();
  }
}

Javadoc

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

Esempio

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

Vettori

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);
  }
}

Java è lento?

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 -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
 
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:

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 7.1 (.NET 2003) cl -Za -W3 -nologo -Ox /G7 /arch:SSE2
Microsoft VC 8 (2005) cl -Za -W3 -nologo -Ox /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...

Lab

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'.

©2007 Roberto Sassi