Entrées-sorties

Les classes d'entrée – sorties en Java sont placées principalement dans deux packages - java.io, qui est basé sur des flux (stream) et java.nio qui est basé sur des tampons (buffers) et des canaux (channels). 
Le flux est une séquence d'octets de longueur indéterminée. Le flux d'entrée importe des données dans le programme depuis une source externe. Le flux de sortie exporte des données vers une source externe. Les deux classes principales pour les flux sont java.io.InputStream et java.io.OutputStream. 
Toutes les instructions d'entrées – sorties lèvent "checked" exceptions





2.1 Les flux d'octets

Classes InputStream et OutputStream.

Les méthodes principales des classes InputStream et OutputStream sont:

public abstract int read( ) throws IOException    //lit un octet et retourne un valeur entier entre 0 et 255
public abstract void write(int b) throws IOException    // ecrit un octer

pour un tableau entier
public int read(byte[] data) throws IOException
public void write(byte[] data) throws IOException

pour une partie du tableau:
public int read(byte[] data, int offset, int len) throws IOException
public void write(byte[] data, int offset, int len) throws IOException

Exemple: copier un fichier binaire. On va prendre deux images:

- un petit fichier (par exemple  https://ff.tu-sofia.bg/JavaAv/io/bback.gif )
-
 un plus gros fichier (par exemple https://ff.tu-sofia.bg/media/FF_Poster-red.jpg)

Créer un projet et copier les deux fichiers dans le répertoire du projet!

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FCopy {
    public static void main(String arg[]) throws IOException{
        int bt;
        if(arg.length!=2){
            System.out.println("Usage: FCopy fileSource fileDestination");
            return;
        }
        String fns = arg[0], fnd = arg[1];
        System.out.println("copy the file "+fns+" in the file "+fnd);
        FileInputStream fi = null;
        FileOutputStream fo = null;
        try {
            fi = new FileInputStream(fns);
            fo = new FileOutputStream(fnd);
            while((bt = fi.read())!=-1)fo.write(bt);
            System.out.println("file "+fns+" is copied as "+fnd);
        }
        catch(FileNotFoundException ex){
            System.out.println("Thе file "+fns+" does not exist");
        }
        finally{
            if(fi != null)fi.close();
            if(fo != null)fo.close();
        }
    }
}



import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FCopyArr {
    public static void main(String arg[]) throws IOException{
        byte bt[] = new byte[2048];
        int len;
        if(arg.length!=2){
            System.out.println("Usage: FCopy fileSource fileDestination");
            return;
        }
        String fns = arg[0], fnd = arg[1];
        System.out.println("copy the file "+fns+" in the file "+fnd);
        FileInputStream fi = null;
        FileOutputStream fo = null;
        try {
            fi = new FileInputStream(fns);
            fo = new FileOutputStream(fnd);
            while((len = fi.read(bt))!=-1){
                fo.write(bt,0,len);
            }
            System.out.println("file "+fns+" is copied as "+fnd);
        }
        catch(FileNotFoundException ex){
            System.out.println("The file "+fns+" does not exist");
        }
        finally{
            if(fi != null)fi.close();
            if(fo != null)fo.close();
        }
    }
}



2.2 Les flux de caractères


Classes Reader et Writer :  classes abstraites pour lire et écrire dans un flot des caractères.

public int read()   throws IOException
public int read(char[] cbuf)   throws IOException
public abstract int read(char[] cbuf, int off, int len) throws IOException

void     write(int c) throws IOException
public void write(char[] cbuf)    throws IOException
abstract void     write(char[] cbuf, int off, int len) throws IOException
void     write(String str) throws IOException
void     write(String str, int off, int len) throws IOException

On va utiliser la classe FileReader dérivée  de Reader et FileWriter dérivée de Writer.

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class FcharCopy {
    public static void main(String arg[]) throws IOException{
        int bt;
        if(arg.length!=2){
            System.out.println("Usage: FCopy fileSource fileDestination");
            return;
        }
        String fns = arg[0], fnd = arg[1];
        System.out.println("copy the file "+fns+" in the file "+fnd+"\n");
        FileReader fi = null;
        FileWriter fo = null;
        try {
            fi = new FileReader(fns);
            fo = new FileWriter(fnd);
            while((bt = fi.read())!=-1){
                fo.write(bt);
                System.out.print((char)bt);
            }
            System.out.println("\n\nfile "+fns+" is copied as "+fnd);
        }
        catch(FileNotFoundException ex){
            System.out.println("The file "+fns+" does not exist");
        }
        finally{
            if(fi != null)fi.close();
            if(fo != null)fo.close();
        }
    }
}


Exercice

Comptez le nombre des mots dans un fichier texte en utilisant la classe "Reader" et  sa méthode read(). Séparateurs:  ' ', '\t', '\n', fin fichier.




2.3 Les flux de lignes

Les classes BufferedReader et  PrintWriter

inputStream =  new BufferedReader(new FileReader("test.txt"));
outputStream =  new PrintWriter (new FileWriter("output.txt"));

L'exemple suivant fait la démonstration de ces classes pour copier un fichier.

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
public class FlineCopy {
    public static void main(String arg[]) throws IOException{
        String line = null;       
        String fns=null, fnd=null;
        BufferedReader sin=null;
        BufferedReader fi = null;
        PrintWriter fo = null;
        try {
            sin= new BufferedReader(new InputStreamReader(System.in));
            System.out.print("input the name of the source file:");
            fns = sin.readLine();
            System.out.print("input the name of the destination file:");
            fnd = sin.readLine();
            System.out.println("copy the file "+fns+" in the file "+fnd+"\n");
            fi = new BufferedReader(new FileReader(fns));
            fo = new PrintWriter(new FileWriter(fnd));
            while((line = fi.readLine())!= null){
                System.out.println(line);
                fo.println(line);
            }
            System.out.println("\n\nfile "+fns+" is copied as "+fnd);
        }
        catch(FileNotFoundException ex){
            System.out.println("The file "+fns+" does not exist");
            System.out.println("or the file "+fnd+" can not be created");
        }
        finally{
            if(fi != null)fi.close();
            if(fo != null)fo.close();
        }
    }
}


Exercice

Comptez le nombre des mots dans un fichier texte en utilisant les classes "BufferedReader", sa méthode readLine()   et "StringTokenizer"


2.4 Les flux d'objets

Serialization et Deserialization

La sérialisation est la conversion de l'état d'un objet en un flux d'octets.  La désérialisation fait le contraire. En d'autres termes, la sérialisation est la conversion d'un objet Java en un flux statique (séquence) d'octets, qui pourrait être enregistrée dans un fichier, une base de données ou transférée sur un réseau. La sérialisation est plateforme indépendante. On peut sérialiser les objets sur une plateforme et désérialiser sur une autre.

Les classes éligibles à la sérialisation doivent implémenter l'interface "Serializable". C'est une "marker" interface - elle ne contient pas des méthodes.

Remarques:
1. Si une classe de base a implémenté une interface Serialisable, la classe dérivée n'a pas besoin de l'implémenter, mais l'inverse n'est pas vrai.
2. Seules les données membres non statiques sont enregistrées via le processus de sérialisation.
3. Les données membres statiques et les données membres transitoires ne sont pas enregistrées via le processus de sérialisation. Donc, si vous ne souhaitez pas enregistrer la valeur d'une donnée membre non statique, rendez-la transitoire.
4. Le constructeur d'objet n'est jamais appelé lorsqu'un objet est désérialisé.

SerialVersionUID
La sérialisation associe un numéro de version à chaque classe sérialisable appelé serialVersionUID, qui est utilisé lors de la désérialisation pour vérifier que l'expéditeur et le destinataire d'un objet sérialisé ont chargé des classes pour cet objet qui sont compatibles en ce qui concerne la sérialisation. Si le destinataire a chargé une classe pour l'objet qui a un UID différent de celui de la classe de l'expéditeur correspondant, la désérialisation entraînera une InvalidClassException.
Une classe Serialisable peut déclarer explicitement son propre UID en déclarant un nom de champ. Il doit être statique, final et de type long. Si une classe sérialisable ne déclare pas explicitement un serialVersionUID, alors le runtime de sérialisation en calculera un par défaut pour cette classe en fonction de divers aspects de la classe.  Cependant, il est fortement recommandé que toutes les classes sérialisables déclarent explicitement la valeur SerialVersionUID, car son calcul est très sensible aux détails de la classe qui peuvent varier en fonction des implémentations du compilateur.

Les flux enveloppes ObjectInputStream et ObjectOutputStream sont utilisés pour la sérialisation et la désérialisation des objets
ObjectInputStream et ObjectOutputStream sont des classes de haut niveau qui sont dérivées respectivement java.io.InputStream et java.io.OutputStream. ObjectOutputStream peut écrire des types primitifs et des graphiques d'objets dans un OutputStream sous la forme d'un flux d'octets. Nous pouvons ensuite lire ces flux en utilisant ObjectInputStream.

Exemple

Si on veut travailler avec un fichier dans la répertoire courante "save.ser" : constructeurs

ObjectInputStream ois = new ObjectInputStream (new FileInputStream ("save.ser")) ;
ObjectOutputStream oos =  new ObjectOutputStream (new FileOutputStream ("save.ser")) ;

Les deux méthodes principales sont writeObject() et readObject(). Une conversion explicite vers le type d'objet est nécessaire pendant son reconstruction.


2.4.1 Les classes:

import java.io.Serializable;
public class PersonSr implements Serializable{
     private static final long serialVersionUID = 1L;
    static int num=0;
    String name;
    String id;
    PersonSr(){
        name = "student "+ ++num;
        id = (int)(Math.random()*3999)+1111+"";
    }
}   
public class StudentSr extends PersonSr{
     private static final long serialVersionUID = 1L;
    int notes[];
    StudentSr(){
        notes = new int [5];
        for(int i =0;i<notes.length;i++){
            notes[i]=(int)(Math.random()*5)+2;
        }
    }
    public String toString(){
       String rez;
            rez = "name:"+name+"\tid:"+id+"\tnotes:";
        for(int i=0;i<notes.length;i++){
            rez += "\t"+notes[i];
        }
        return rez;       
    }
}



2.4.2 Serialisation:

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class StudWr {
    public static void main(String arg[]) throws IOException{
        ObjectOutputStream oos = null;
        StudentSr s;
        try{
            oos = new ObjectOutputStream (
                new FileOutputStream ("save.ser"));
            for(int i=0;i<9;i++){
                s =new StudentSr();
                System.out.print(""+s);
                oos.writeObject(s);
                System.out.println("       # is serialized");
            }
        }
        finally{
            oos.close();
        }
    }
}


2.4.3 Deserialisation

import java.io.EOFException;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class StudRd {
    public static void main(String[] arg)throws
             IOException,ClassNotFoundException{
        ObjectInputStream ois = null;
        StudentSr s;
        int n=0, nf=0;
        try{
            ois = new ObjectInputStream (
                new FileInputStream ("save.ser"));
            for(;;){
                s= (StudentSr)ois.readObject();
                n++;
                if(average(s)>=4){
                    System.out.print(""+s);
                    System.out.println("\thas average "+ average(s));
                    nf++;
                }
            }
        }
        catch(EOFException ex){
            System.out.println("printed "+nf+" students, found total "+n+" students");
        }
        finally{
            ois.close();
        }
    }
    public static double average(StudentSr s){
        double sum;
        int i;
        for(sum=i=0;i<s.notes.length;i++){
            sum+=s.notes[i];
        }
        return sum/s.notes.length;
    }
}


2.4.4 Mot clé   transient:

 
class MyClass implements Serializable {
    transient Thread thread;  //Skip serialization of the transient field
    transient String fieldIdontwantSerialization;
         // Serialize the rest of the fields
    int data;
    String x;
         // More code
}

2.4.5  Exercice

Modifiez le programme pour qu'au lieu de générer des étudiants, ils soient inscrits dans deux fichiers texte : names.txt (contenant les noms des étudiants) et numbers.txt (contenant leurs numéros correspondants). Arrêtez le programme en cas d'une faute ou bien  la fin d'un des deux fichiers.




2.5. La classe File

La classe File contient des méthodes de manipulation des fichiers et des répertoires.

2.5.1 Le premier exemple affiche sur la console le contenu d'un répertoire

import java.io.*;
public class FileLst{
    public static void main(String args[]) {
        try{
            File path = new File(args[0]);
            String[] list = path.list();
            for(int i = 0; i < list.length; i++)
                System.out.println(list[i]);
        }
        catch (ArrayIndexOutOfBoundsException e) {
            System.err.println("there is no command line parameter");
        }
        catch (NullPointerException ex) {
            System.err.println("The directory does not exist");
        }
    }
}



2.5.2 Le deuxième exemple affiche sur la console les paramètres d'un répertoire (donné comme paramètre sur la ligne de commande).

import java.io.*;
import java.util.*;
public class FileInf{
    public static void main(String args[]) {
        File f;
        try{
            f = new File(args[0]);
        }   
        catch (ArrayIndexOutOfBoundsException e) {
            System.err.println("there is no command line parameter");
            return;
        }
        if(!f.exists()){
            System.err.println("The "+f+" does not exist");
            return;
        }
        System.out.println(
                " Abs path: " + f.getAbsolutePath() +
                "\n can write: " + f.canWrite() +
                "\n can read: " + f.canRead() +
                "\n Name: " + f.getName() +
                "\n Parent " + f.getParent() +
                "\n Path: " + f.getPath() +
                "\n Modif: " + new Date(f.lastModified()));
        if(f.isFile())
            System.out.println("This is a file with length "+ f.length() + " bytes");
        else if(f.isDirectory())
            System.out.println("This is a directory");
    }
}


2.5.3 Le dernier exemple montre comment créer, supprimer et changer le nom d'un fichier:

import java.io.*;
public class FileManip{
    public static void main(String args[]){
        File newF = new File("D:\\tst\\Test.txt");
        File chng = new File("D:\\tst\\Test_change.txt");
        File dir1 = new File("D:\\tst\\Text.txt");    
        if(dir1.mkdirs()){
            System.out.println("Directory "+dir1+" created");
        }
        else {
            System.out.println("Directory "+dir1+" exist yet");
        }
        try{
            if(newF.createNewFile()){
                System.out.println("File  "+newF+" created");
            }
            else{
                System.out.println("File  "+newF+" exist yet");
            }
        }catch (IOException e) {
            System.out.println("the path to file "+ newF + "does not exist" );
        }
        if(newF.renameTo(chng)){
            System.out.println("File  "+newF+" renamed to" + chng);
        }
        else {
            System.out.println("File  "+newF+" does not exist");           
        }
        if (chng.delete()){
            System.out.println("File  "+ chng + " deleted");
        }
        else {
            System.out.println("File  "+chng + "can not be deleted");
        }
        if(dir1.delete()){
            System.out.println("Directory " + dir1 + " deleted"); 
        }
        else {
            System.out.println("Directory " + dir1 + " can not be deleted");
        }
    }
}

2.5.4 Exercice:

Modifiez le programme de 2.1.2 (copie binaire par blocs) afin de visualiser le déroulement de la copie en pourcentage après chaque bloc transféré (utilisez la fonction length() de 2.5.2). Testez pour un fichier volumineux, à votre préférence.