Programmation Java/Entrées Sorties
Les opérations d'entrées-sorties concernent la lecture (entrée) et l'écriture (sortie) de données à travers différents types de flux...
En Java, les opérations d'entrées-sorties de base sont gérées par les classes du package java.io
. Ces classes obéissent au patron de conception décorateur. Ces objets sont souvent créés au sein d'objets correspondant au patron de conception fabrique.
Flux d'entrée-sortie
modifierLe package java.io
possède deux classes principales :
InputStream
: cette classe abstraite définit les fonctions de lecture (entrée ou input en anglais),OutputStream
: cette classe abstraite définit les fonctions d'écriture (sortie ou output en anglais).
Ces deux classes abstraites définissent des fonctions bas-niveau et sont implémentées dans différentes sous-classes concrètes. Il y a plusieurs types de flux dont : les flux sur fichiers, les flux sur tubes, et les flux sur zones de mémoire. Par exemple on a :
FileInputStream
: lecture d'un fichier,FileOutputStream
: écriture d'un fichier.
La classe java.net.Socket
possèdent des méthodes retournant des instances concrètes des classes InputStream
et OutputStream
pour lire et écrire depuis/vers la socket TCP.
java.io.InputStream
modifier
La classe java.io.InputStream
possèdent des méthodes lisant une série d'octets (byte).
int InputStream.read()
: lit un octet et retourne sa valeur (0 à 255) ou -1 pour signaler la fin de flux.int InputStream.read(byte[] b)
: litb.length
octets pour remplir le tableau, et retourne le nombre d'octets lus.int InputStream.read(byte[] b, int off, int len)
: litlen
octets, et les dépose en commençant àb[off]
, et retourne le nombre d'octets lus
Les deux dernières méthodes lisent une série d'octets dans un tableau, mais peuvent retourner un nombre inférieur à celui demandé pour différentes raisons :
-1
indique qu'aucun octet n'a pu être lu car la fin du flux a été atteinte.0
indique qu'aucun octet n'a pu être lu temporairement, pas de données disponible (par exemple depuis une connexion réseau). Cela se produit pour les flux non bloquants.- Un nombre positif mais inférieur à celui demandé s'il n'y en a pas plus pour le moment (connexion réseau) ou définitivement (fin du flux).
java.io.OutputStream
modifier
La classe java.io.OutputStream
possède des méthodes écrivant une série d'octets (byte).
OutputStream.write()
: écrit un byteOutputStream.write(byte[] b)
: écritb.length
bytes.OutputStream.write(byte[] b, int off, int len)
: écritlen
bytes, commencer àb[off]
.
java.io.PrintStream
modifier
La classe java.io.PrintStream
hérite de la classe java.io.OutputStream
, et permet d'afficher tous les types de données sous forme textuelle.
La sortie standard et l'erreur standard impriment sur la console et sont des instances de la classe java.io.PrintStream
.
Exemple :
system.out.println("Bonjour !"); // Affiche une chaîne avec retour à la ligne
int count = 100;
system.out.print(count); // Affiche un entier sans retour à la ligne
system.out.print(' '); // Affiche un caractère
Tout objet peut être affiché car les méthodes print
et println
appellent la méthode ToString()
définie dans la classe java.lang.Object
racine de l'arbre hiérarchique de tous les types d'objets.
Exemple :
class NombreComplexe
{
double n_real, n_img;
public NombreComplexe(double r, double i)
{
this.n_real = r;
this.n_img = i;
}
public String toString()
{
return n_real + " + i*"+n_img;
}
}
NombreComplexe a = new NombreComplexe(1.0, 0.5);
system.out.println(a); // Appelle println(Object) pour afficher :
// 1.0 + i*0.5
La méthode toString()
est également appelée implicitement lors de la concaténation de chaînes de caractères :
String resultat = "Solution complexe : " + a;
// -> Solution complexe : 1.0 + i*0.5
Il est donc important que cette méthode n'ait aucun effet de bord (modification d'un objet, synchronisation, ...).
Lecture-écriture haut niveau
modifierLe package java.io
possède des classes permettant la lecture et l'écriture de différents types de données.
Reader, Writer, PrintStream, Scanner
modifierPour intéragir avec l'utilisateur dans la console[1].
Exemple de saisie d'un tableau de noms utilisant la classe Scanner
:
import java.util.*;
public class ArrayExample
{
static Scanner input = new Scanner(System.in);
public static void main(String[] args)
{
int nombre_de_noms = getInt("Nombre d'entrées du tableau ?");
String[] noms = new String[nombre_de_noms];
// Saisie
for (int i = 0; i < noms.length; i++)
noms[i] = getString("Entrée n°" + (i+1));
// Affichage
for (int i = 0; i < noms.length; ++i)
System.out.println(noms[i]);
}
public static int getInt(String prompt)
{
System.out.print(prompt + " ");
int entier = input.nextInt();
input.nextLine(); // Ignorer la fin de la ligne jusqu'au retour à la ligne.
return entier;
}
public static String getString(String prompt)
{
System.out.print(prompt + " ");
return input.nextLine();
}
}
Gestion des répertoires et des fichiers
modifierType de chemin
modifierUn chemin est une instance de la classe java.io.File
et peut désigner :
- un fichier normal (La méthode
isFile()
retournetrue
), - un répertoire (La méthode
isDirectory()
retournetrue
), - un fichier spécial spécifique au système d'exploitation (pipe, device, chemin réseau UNC, ...).
Résolution de chemin
modifierPour construire une instance de java.io.File
à partir d'un chemin, utilisez le constructeur File(String path)
:
File f = new File("/home/fr-wikibooks-org/mes_fichiers/fichier.txt"); // sous Linux
Il peut s'agir d'un chemin relatif :
File f = new File("source\\fichiers\\exemple.txt"); // sous Windows
Pour créer un chemin relatif à celui d'un répertoire, utilisez le constructeur File(File dir, String path)
:
File f_dir = new File("/app/test/fichiers"); // Répertoire de base File f_config = new File(f_dir, "premier/info.cfg"); // Relatif au répertoire de base // --> /app/test/fichiers/premier/info.cfg File f_autre = new File(f_dir, "../autre/alternative.cfg"); // Relatif au répertoire de base // --> /app/test/fichiers/../autre/alternative.cfg // C'est-à-dire /app/test/autre/alternative.cfg
Pour obtenir le chemin absolu, appelez la méthode getAbsoluteFile()
pour l'obtenir sous la forme d'un objet File
, ou la méthode getAbsolutePath()
pour l'obtenir sous la forme d'une chaîne de caractères String
.
File f_autre = new File(f_dir, "../autre/alternative.cfg"); // Relatif au répertoire de base // --> /app/test/fichiers/../autre/alternative.cfg f_autre = f_autre.getAbsoluteFile() // --> /app/test/autre/alternative.cfg // Répertoire courant : File f_curdir = new File(".").getAbsoluteFile();
Pour obtenir le chemin canonique, utilisez la méthode getCanonicalFile()
pour l'obtenir sous la forme d'un objet File
, ou la méthode getCanonicalPath()
pour l'obtenir sous la forme d'une chaîne de caractères String
.
Le chemin canonique résout les liens symboliques et les deux méthodes peuvent donc lancer une exception en cas d'erreur (lien cassé).
Lister les fichiers d'un répertoire
modifierPour les chemins désignant un répertoire, la méthode listFiles()
retourne un tableau des fichiers contenu dans le répertoire.
Ce tableau n'inclut pas d'entrée pour .
(répertoire courant) ni pour ..
(répertoire parent).
Lister les chemins racines
modifierLa méthode statique listRoots()
retourne un tableau des chemins racines du système de fichiers.
Pour Linux, cette méthode retourne le répertoire racine /
.
Pour Windows, cette méthode retourne le répertoire racine \
de chaque lecteur disponible (ex: C:\
D:\
...).
Cette méthode est particulièrement utile pour créer un sélecteur de fichiers utilisant un arbre de répertoires.
Création d'un répertoire
modifierPour créer un dossier :
import java.io.File;
File mon_dossier = new File("mon_chemin");
mon_dossier.mkdir();
Pour créer un dossier récursivement (avec ses parents), remplacer mkdir
par mkdirs
.
Création d'un fichier temporaire
modifierPour créer un fichier temporaire, utilisez la méthode statique createTempFile(prefix, suffix, directory)
.
- prefix
- Préfixe du nom de fichier.
- suffix
- Suffixe du nom de fichier.
- directory
- Répertoire où créer le fichier, ou
null
pour le créer dans le répertoire temporaire du système.
La méthode retourne le chemin du fichier créé ; le nom contient un identifiant unique.
La variante createTempFile(prefix, suffix)
équivaut à appeler la même méthode avec null
pour directory
: elle crée un fichier dans le répertoire temporaire du système.
La suppression n'est pas gérée par cette méthode.
Utilisez la méthode deleteOnExit()
sur le fichier créé pour qu'il soit supprimé automatiquement à la fin de l'application.
Il n'est pas possible d'annuler la requête de suppression de fichier en fin d'exécution.
Exemple :
File tempfile = File.createFile("debuglog", ".tmp"); // Dans le répertoire temp du système tempfile.deleteOnExit(); // Suppression du fichier à la fin de l'exécution // ... utiliser le fichier temporaire
Copie
modifierPour copier un dossier (avec son contenu) :
import org.apache.commons.io.FileUtils;
FileUtils.copyDirectory('chemin/source', 'chemin/destination');
Sans la bibliothèque Apache :
public static boolean recursiveCopy(File f_from, File f_to)
{
// Récupérer les attributs du fichier/répertoire source
long time = f_from.lastModified();
boolean cr = f_from.canRead();
boolean cw = f_from.canWrite();
boolean cx = f_from.canExecute();
if (f_from.isDirectory())
{
f_to.mkdirs();
File[] files = f_from.listFiles();
for(File ff : files)
{
File ft = new File(f_to, ff.getName());
if (!recursiveCopy(ff, ft)) return false;
}
}
else if (f_from.isFile()) // Test permettant d'éviter la copie de certains fichiers spéciaux
{
File p = f_to.getParentFile();
if (!p.exists()) p.mkdirs();
try
{
InputStream in = new FileInputStream(f_from);
try
{
OutputStream out = new FileOutputStream(f_to);
byte[] b = new byte[8192];
int len;
try
{
while ((len = in.read(b))>0)
out.write(b, 0, len);
}
finally { out.close(); }
}
finally { in.close(); }
}
catch (IOException e)
{ return false; }
}
// Mettre les attributs sur le fichier/répertoire destination
f_to.setReadable(cr);
f_to.setExecutable(cx);
f_to.setWritable(cw);
f_to.setLastModified(time);
return true;
}
Suppression
modifierPour un répertoire vide, ou un fichier :
import java.io.File;
File mon_chemin = new File("mon_chemin");
mon_chemin.delete();
Pour un répertoire vide ou non, ou un fichier :
import java.io.File;
import org.apache.commons.io.FileUtils;
File mon_dossier = new File("mon_chemin");
FileUtils.deleteQuietly(mon_dossier);
Sans la bibliothèque Apache :
public static boolean recursiveDelete(File f)
{
if (f.isDirectory())
{
File[] files = f.listFiles();
for(File ff : files)
if (!recursiveDelete(ff)) return false;
}
return f.delete();
}
Exemples concrets de flux
modifierFichiers textes
modifierPour lire un fichier texte plusieurs fois en reprenant du début, il faut introduire une variable de type FileChannel
, uniquement pour repositionner le lecteur[2] :
import java.io.*;
import java.nio.channels.FileChannel;
public class LireDeuxFois {
public static void main(String[] args) throws Exception {
if (args.length == 1) {
FileInputStream fis = new FileInputStream(args[0]);
FileChannel fc = fis.getChannel();
int cc;
// Première lecture
while ((cc = fis.read()) != -1) {
System.out.println((char)cc);
}
fc.position(0); // Reset
// Deuxième lecture
while ((cc = fis.read()) != -1) {
System.out.println((char)cc);
}
fis.close();
}
}
}
Images
modifierLe paquetage javax.imageio
contient des classes permettant la lecture et l'écriture d'images en utilisant des flux d'entrées-sorties génériques (java.io.InputStream
et java.io.OutputStream
), des flux d'images (javax.imageio.ImageInputStream
et javax.imageio.ImageOutputStream
) ou simplement un chemin de fichier (java.io.File
).
Exemple convertissant une image GIF en PNG :
import javax.imageio.ImageIO;
import java.io.File;
// Lecture d'une image à partir d'un fichier :
Image image = ImageIO.read(new File("Icone1.gif"));
// Écriture d'une image dans un fichier :
ImageIO.write(image, "png", new File("Icone2.png"));
La classe ImageIO
possède également une méthode read
acceptant un flux de classe java.io.InputStream
.
Ce flux peut être créé à partir d'une connexion réseau ou de ressources locales au paquetage d'une classe de l'application.
La lecture crée un fichier temporaire dans le répertoire par défaut (méthode createTempFile
de la classe java.nio.file.Files
).
Il est possible d'obtenir une exception avec le message can’t create cache file
si la création échoue.
Si l'application a été lancée par une autre application Java, le problème peut venir de la non transmission des variables d'environnement du système au processus créé, car celles-ci contiennent le chemin du répertoire des fichiers temporaires. Par défaut, sous Windows, le répertoire C:\Windows
est utilisé mais l'écriture n'y est pas autorisé.
Consoles
modifierLes applications sur la plupart des systèmes d'exploitation (Windows, Linux, Android, ...) sont associées à trois flux standards connectés par défaut à la console, mais pouvant être redirigés depuis le shell ou par une application vers un autre flux (un fichier, un flux d'une autre application, ...).
En java ces flux sont disponibles dans la classe java.lang.System
:
in
- Flux d'entrée standard de classe
java.io.InputStream
, utilisé pour lire une valeur entrée par l'utilisateur si le flux est relié à la console. out
- Flux de sortie standard de classe
java.io.PrintStream
, que l'application utilise pour afficher un résultat. err
- Flux d'erreur standard de classe
java.io.PrintStream
, que l'application utilise pour afficher des messages d'erreur.