Utilizzo dei files. Javadoc

Venerdì, 29 Ottobre 2004

OBIETTIVI DELLA LEZIONE

In questa lezione:

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

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 byte 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
[L'esempio dello stream binario è stato eliminato. 
 Creava più dubbi di quanti ne risolveva. 
 Ne vedremo di più chiari nelle prossime lezioni]

La classe File

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

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

Costanti

In Java, per definire una costante possiamo utilizzare congiuntamente le parole chiave static final. Esempio:

public static final double PI = 3.14159;

Per convenzione le costanti hanno nomi tutti maiuscoli. Più parole si congiungono con un carattere "_". Esempio:

public static final int ALBERI_MELE = 20;

Soluzione esercizio bonus

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) {
    String sequenza;
    int i, j, lunghezzaSottosequenza;
    int ripetizioniA = 0, ripetizioniB = 0;
    
    sequenza = JOptionPane.showInputDialog("Inserisci la sequenza da analizzare");
    
    // Facciamo in modo che i caratteri della stringa di input siano maiuscoli
    sequenza = sequenza.toUpperCase();
    
    // Controlliamo che i caratteri immessi siano corretti
    for(i=0; i<sequenza.length(); i++) {
      if( (sequenza.charAt(i)!='A') && (sequenza.charAt(i)!='B') ) {
        JOptionPane.showMessageDialog(null, "La sequenza che hai immesso è illecita");
        System.exit(0);
      }
    }
    
    // Contiamo la lunghezza massima delle sottosequenze
    // Primo ciclo: scandisce le possibili sottosequenze
    i=0;
    while(i<sequenza.length()) {
      j=i+1; 
      // Secondo ciclo: scandisce i caratteri all'interno della sottosequenza
      // All'uscita dal ciclo, j è la posizione del primo carattere che non appartiene
      // alla sottosequenza
      while( (j<sequenza.length()) && (sequenza.charAt(j)==sequenza.charAt(i)) ) {
        j++;
      }
      lunghezzaSottosequenza = j-i;
      
      if( (sequenza.charAt(i)=='A') && (lunghezzaSottosequenza>ripetizioniA) ) {
        ripetizioniA = lunghezzaSottosequenza;
      } 
      if( (sequenza.charAt(i)=='B') && (lunghezzaSottosequenza>ripetizioniB) ) {
        ripetizioniB = lunghezzaSottosequenza;
      }
      
      i=j;
    }
      
    JOptionPane.showMessageDialog(null, "Il numero di A consecutive è " +
      ripetizioniA + "\nil numero di B consecutive è " + ripetizioniB);
    System.exit(0);
  }
}

La documentazione generata da Javadoc è la seguente: index.html

Lab

Esercizio 1

Dato il file sanmartino.txt, scrivere un programma che conti il numero di ripetizioni della lettera 'o'. [Soluzione: 15 volte]

Esercizio 2

Dato il file COSTITUZ.TXT, scrivere un programma che conti il numero di ripetizioni della parola "Repubblica".

Esercizio 3

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 4

Prendete un esercizio che avete completato e commentatelo opportunamente (con un commento Javadoc). Eseguite Javadoc e analizzatene l'output.

Esercizio 5

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

©2004 Roberto Sassi