2. Notion de fichier

2.1. Introduction

Un fichier en Java est un objet instance de la classe File. Cet objet peut représenter indifféremment un fichier ou un répertoire, avec ou sans chemin d'accès, relatif ou absolu. Il est important de noter que cette instance est une notion abstraite, qui ne représente pas nécessairement un fichier réel existant. Comme nous allons le voir, les méthodes exposées par cette classe permettent d'interroger le système de fichier, afin de savoir entre autres si ce fichier existe, s'il s'agit d'un répertoire, si l'on peut le lire, etc...

2.2. La classe File

2.2.1. Notion de chemin

Une instance de File ne permet pas d'écrire ou de lire le contenu d'un fichier, mais bien de manipuler ce fichier en tant que tel. La classe File définit la notion de chemin dans un système de fichiers. Ce chemin se compose de deux éléments.
  • Un préfixe qui dépend du système d'exploitation sur lequel on est. Ce peut être un simple slash sous Unix ( /), une lettre de lecteur sous Windows ( D:) ou un double anti-slash pour les éléments de réseau Windows ( \\, qui s'écrit en fait \\\\ du fait qu'il faut échapper l'anti-slash dans le code Java). Ce préfixe est optionnel.
  • Une séquence de chaînes de caractères. Cette séquence peut être vide, elle peut ne comporter qu'un seul élément, elle peut également en comporter plusieurs. Ces éléments constituent les noms associés à ce fichier. Cette séquence représente le chemin vers ce fichier.
Le premier nom de cette séquence peut être un nom de répertoire, ou, dans le cas d'un réseau Windows, un nom d'hôte. Le dernier nom de cette séquence peut être un nom de fichier ou un nom de répertoire. Tous les noms intermédiaires de cette séquence sont des noms de répertoire.

2.2.2. Caractère de séparation

Un chemin vers un fichier utilise toujours un caractère de séparation, qui permet d'identifier les noms des répertoires. Ce caractère de séparation dépend du système d'exploitation. Pour cela, il est défini dans un champ public statique de la classe File, sous deux formes :
  • separator : sous forme de chaîne de caractère ;
  • separatorChar : sous forme de caractère.
Une chemin vers un fichier doit toujours utiliser l'un de ces champs, ce qui permet de le rendre portable d'un système à l'autre.

Exemple 111. Utilisation de File.separator

// utilisation de StringBuffer pour éviter les concaténation 
 // de chaînes de caractères
StringBuffer accessFileName =  new StringBuffer() ;
accessFileName.append("tmp").append(File.separator).append("access.log") ;
System.out.println(accessFileName) ; 

Ce code affiche le résultat suivant sous Unix :
 tmp/access.log
Et le résultat suivant sous Windows :
 tmp\access.log

2.2.3. Chemin absolu ou relatif, répertoire courant

Une instance de File peut représenter un chemin vers un répertoire ou un fichier. Dans les deux cas, ce chemin peut être absolu ou relatif. Un chemin relatif est toujours mesuré par rapport au répertoire courant dans lequel s'exécute l'application. On y a accès par une propriété système de la machine Java : user.dir.

Exemple 112. Lecture du répertoire courant

System.out.println(System.getProperty("user.dir")) ;

L'exécution de ce code donne le résultat suivant, qui dépend bien sûr de l'installation locale.
 D:\projets\TD\workspace\exo-03
Notons, rappelons que les instances de File représentent des fichiers ou des répertoires abstraits, qui n'existent pas nécessairement dans le système de fichiers courant. Enfin, on peut noter aussi que la classe File est immutable . Une fois fixé le chemin d'une instance, on ne peut plus le modifier.

2.3. Construction d'une instance de File

La classe File admet quatre constructeurs. Ces constructeurs peuvent prendre des noms de fichiers écrits dans des chaînes de caractères. Dans ce cas, le caractère de séparation éventuellement présent dans cette chaîne est automatiquemet reconnu. Sous Unix, ce caractère doit être le slash ( /). Sous Windows, à la fois le slash ( /) et l'anti-slash échappé ( \\) sont reconnus.
  • File(String pathname) : prend en paramètre une chaîne de caractères qui indique un chemin, relatif ou absolu, vers un fichier ou un répertoire.
  • File(String parent, String child) : prend deux chaînes de caractères en paramètre. La première indique le chemin vers le fichier ou le répertoire. La seconde le nom de ce ficher ou répertoire.
  • File(File parent, String child) : ce constructeur est analogue au précédent, sauf que le chemin est exprimé sous la forme d'une instance de File.
  • File(URI uri) : ce dernier constructeur prend en paramètre une URI.

2.4. Méthodes exposées

2.4.1. Surcharge des méthodes de Object

La classe File surcharge equals() et hashCode(), de même que toString(). Cette dernière méthode affiche juste le fichier ou le répertoire représenté par cette instance de File. De plus, la classe File implémente Comparable. L'ordre choisi est simplement l'ordre lexicographique du nom du fichier ou du répertoire représenté. On prendra garde que sous Unix les différences entre majuscules et minuscules sont supportées, ce qui n'est pas le cas sous Windows.

2.4.2. Interrogation du type de ressources et de ses droits

  • isFile(), isDirectory() : permettent de tester si cette instance de File représente un fichier ou un répertoire.
  • exists(), canRead(), canWrite(), canExecute() : permettent de tester différents éléments sur le fichier ou le répertoire représenté par cette instance de File.
  • setReadable(boolean b), setWritable(boolean b), setExecutable(boolean b) : permettent de modifier la propriété correspondante du fichier ou du répertoire. Ces méthodes peuvent prendre un booléen supplémentaire, qui indique si la propriété correspondante doit être modifiée pour tous les utilisateurs, ou uniquement pour le propriétaire du fichier ou du répertoire.
  • lastModified() et setLastModified() : permettent de manipuler la date de dernière modification de ce fichier ou de ce répertoire.
  • length() : retourne la taille du fichier sous forme d'un long quand cette instance de File représente un fichier. Le résultat n'est pas prévisible si cette instance représente un répertoire.
  • isHidden() : permet de savoir si ce fichier ou répertoire est caché ou non. Notons que la notion de fichier caché est différente sous Unix et Windows. Sous Unix, un fichier est caché s'il commence par un point ( .). Sous Windows, chaque fichier possède un attribut particulier qui indique s'il est caché ou non.
Certaines de ces opérations peuvent nécessiter des droits particuliers sur les fichiers ou répertoires. Ces droits sont gérés en fonction du système de fichier utilisé. Toutes les méthodes visant à modifier les propriétés d'un fichier ou d'un répertoire peuvent donc échouer. Elles signalent un succès ou un échec de l'opération en retournant un booléen. Ces méthodes ne jettent donc pas d'exception.

2.4.3. Interrogation du nom et du chemin

Les méthodes suivantes permettent d'obtenir différentes parties du nom d'un fichier.
  • getName() : retourne le nom de ce fichier ou répertoire, sans son chemin d'accès s'il est précisé. Ce nom correspond au dernier élément de la séquence de nom de cette instance de File.
  • getParent() : retourne le nom du parent de ce fichier ou répertoire. Le parent est défini par la séquence complète des noms, stockée dans cette instance de File, de laquelle on a retiré le dernier élément, qui correspond par convention au nom de ce fichier ou répertoire.
  • getPath() : retourne le chemin complet de ce fichier. Le retour de cette méthode est le même que celui de la méthode toString().
  • getAbsolutePath() : le retour de cette méthode diffère si l'instance de File représente un chemin absolu ou pas. Si ce chemin est absolu, le retour de cette méthode est le même que getPath(). S'il ne l'est pas, un chemin absolu est calculé, relativement au répertoire courant de l'application.
  • getCanonicalPath() : le calcul de ce chemin est plus complexe que dans les cas précédents. Le chemin retourné est le chemin direct vers le fichier ou le répertoire représenté. La machine Java supprime de ce chemin les mouvements via le répertoire .. notamment, et identifie les éventuels raccourcis ou liens symboliques, qu'elle remplace par les vrais chemins dans le système de fichier. La détermination du chemin final peut donc générer des requêtes sur le système de fichier, qui peuvent échouer. Aussi cette méthode peut-elle jeter l'exception IOException.

2.4.4. Création de fichier

Les méthodes suivantes permettent de créer ou effacer le fichier ou répertoire représenté par cette instance de File.
  • createNewFile() : demande la création de ce fichier au système de fichier. Cette création ne peut se faire que si le fichier à créer n'existe pas déjà. Si la création n'a pu avoir lieu alors cette méthode retourne false. Si une erreur a été rencontrée, alors la méthode jette une IOException.
  • delete() : demande l'effacement de ce fichier ou répertoire. Retourne false si cet effacement n'a pu avoir lieu.
  • mkdir() et mkdirs() : ces deux méthodes créent le répertoire représenté par cette instance de File. mkdirs() peut créer une série de répertoires imbriqués, ce qui n'est pas le cas de mkdir(). Retourne false si la création n'a pas pu se faire.
  • deleteOnExit() : demande à la machine Java d'effacer automatiquement ce fichier ou ce répertoire quand l'application se termine. Si plusieurs fichiers ou répertoires sont enregistrés de la sorte, alors ils sont effacés en commençant par le dernier qui a été enregistré. L'effacement ne peut se faire que si la JVM s'éteint normalement, sans plantage.

2.4.5. Création de fichiers temporaires

Notons les deux méthodes statiques de la classe File, qui permettent de créer des fichiers temporaires : createTempFile(String prefix, String suffix, File directory) et createTempFile(String prefix, String suffix). La machine Java garantit deux choses sur ces fichiers :
  • le fichier retourné est toujours un nouveau fichier, qui n'existait pas auparavant ;
  • deux appels successifs à cette méthode, avec les mêmes arguments, ne retournent pas le même fichier.
Le nom du fichier créé commence par prefix, puis est complété par un code unique, qui garantit l'unicité du fichier créé. L'extension de ce fichier est prefix, ou .tmp si prefix est nul. Ces méthodes créent et retournent un fichier temporaire vide dans le répertoire passé en paramètre. Si ce paramètre est nul, alors un répertoire temporaire est utilisé par défaut, fixé par la propriété système java.io.tmpdir. Si le paramètre suffix est nul, alors le fichier aura pour extension .tmp. Si l'on souhaite que les fichiers temporaires soient effacés à la fermeture de l'application, alors il faut les enregistrer par appel à la méthode deleteOnExit().

2.4.6. Capacité du système de fichier

La version 6 de l'API a vu apparaître trois nouvelles méthodes, qui manquaient cruellement auparavant. Ces méthodes permettent d'interroger le système de fichier pour connaître la capacité de stockage du système de fichier sur lequel on est.
  • getFreeSpace() : retourne un long qui porte le nombre d'octets utilisables sur la partition sur laquelle se trouve le fichier interrogé. On prendra garde qu'aucun test n'est fait pour savoir si cette l'application a le droit d'écrire sur cette partition. Il est par conséquent possible qu'aucun de ces octets ne soit disponible.
  • getUsableSpace() : retourne un long qui porte le nombre d'octets utilisables sur la partition sur laquelle se trouve le fichier interrogé. Cette estimation est plus précise que getFreeSpace(). Effectivement, la machine Java vérifie un certain nombre de choses pour déterminer cet espace, comme les droits en écriture.
  • getTotalSpace() : retourne un long qui porte le nombre d'octets de cette partition.
Il est à noter qu'un support de stockage est un élément parfois très sollicité dans un système. Dans beaucoup de cas, l'estimation de la place qu'il reste sur un tel support n'est donc pas une information qui reste valide longtemps.

2.4.7. Contenu d'un répertoire

La classe File expose quelques méthodes utiles pour lire le contenu d'un répertoire. Bien sûr, l'utilisation de ces méthodes n'a de sens que si l'instance de File représente un répertoire et non pas un fichier. Ces méthodes existent toutes en deux versions : la première retourne des tableaux de String, représentant les noms des fichiers se trouvant dans le répertoire, la seconde retournant des tableaux de File. Dans tous les cas, on ne peut pas prévoir l'ordre dans lequel ces fichiers apparaissent dans le tableau. Certaines de ces méthodes prennent en paramètre des objets de type FileFilter ou FilenameFilter. Ces deux interfaces modélisent des filtres sur les fichiers. La première interface définit une méthode accept(File file), la seconde accept(File dir, String name). Les paramètres de ces deux méthodes définissent en fait un unique fichier, de deux façons différentes. Si cette méthode retourne true, alors le fichier est accepté, et sera présent dans le tableau retourné, sinon ça ne sera pas le cas.
  • list() : retournent tous les fichiers et répertoires de ce répertoire.
  • listeFiles() : retournent tous les fichiers (sans les répertoires) de ce répertoire.
  • listFiles(FileFilter) et listFiles(FilenameFilter) : retournent tous les fichiers (sans les répertoires) de ce répertoire, qui satisfont le filtre passé en paramètre.
Voyons un exemple d'utilisation de filtre sur une liste de fichiers. On veut trouver tous les fichiers .java contenus dans un répertoire.

Exemple 113. Utilisation de listFiles(FileFilter)

// répertoire de recherche
String rep =  "D:/workspace/td-04/src/org/paumard/file" ;
 // construction d'un fichier sur ce répertoire
File repFile =  new File(rep) ;

 // filtrage du contenu de ce répertoire
 // on passe en paramètre une instance de classe anonyme
File [] fichiersJava = repFile.listFiles(new FileFilter() {

    // cette interface n'a qu'une unique méthode
    public  boolean accept(File pathname) {
       // on récupère le nom de ce fichier...
      String fileName = pathname.getName() ;
      
       // ... et on teste s'il se termine par .java
       return fileName.endsWith(".java") ;
   }

}) ;

 // il ne reste plus qu'à afficher les noms des fichiers 
 // récupérés
 for (File fichierJava : fichiersJava) {
   System.out.println(fichierJava.getName()) ;
}

L'exécution du code précédent donne un résultat du type :
 Main.java
 Marin.java
Notons également la méthode statique listRoots(), qui retourne la liste des éléments racine du système de fichier sur lequel on se trouve. Sous Unix, il n'y en a qu'un seul : /, mais sous Windows, on peut en avoir plusieurs.

Exemple 114. Utilisation de File.listRoots()

// liste des éléments racine du système de fichier courant
File [] roots = File.listRoots() ;

 // affichage du tableau résultat
System.out.println(Arrays.toString(roots)) ;

Sur une machine Windows, l'exécution de ce code donne un résultat du type :
 [C:\, D:\, E:\, F:\, G:\]

2.4.8. Méthodes toURI() et toURL()

La méthode toURI(), comme son nom le laisse supposer, permet de construire une URI à partir de ce fichier. L'URI d'un fichier se compose du préfixe file:/ suivi du nom complet de ce fichier. La méthode toURL() est dépréciée, et ne doit être utilisée en aucun cas. La classe URL, malgré son nom sympathique, est une classe qui comporte de nombreux problèmes, et qui ne doit être utilisée sous aucun prétexte. On utilise à la place la classe URI, depuis Java 4.
Java langage & API
Retour au blog Java le soir
Cours & Tutoriaux
Table des matières
Introduction : un peu d'histoire
1. Java : genèse d'un nouveau langage
Programmer en Java
1. Un premier exemple
1.1. Aperçu général, cycle de vie
1.2. Un premier programme
1.3. Programmes, applets, servlets, etc...
2. Une première classe
2.1. Écriture d'une classe
2.2. Instanciation de cette classe
3. Types de base, classes et objets
3.1. Types de base
3.2. Classes et objets
Classes Object et String
1. Introduction
2. La classe Object
2.1. La méthode toString()
2.2. La méthode clone()
2.3. La méthode equals()
2.4. La méthode hashCode()
2.5. La méthode finalize()
2.6. La méthode getClass()
3. La classe String
3.1. Introduction
3.2. Construction d'un objet de type String
3.3. Concaténation, StringBuffer et StringBuilder
3.4. Concaténations de chaînes de caractères depuis Java 8
3.5. Concaténations de chaînes de caractères depuis Java 11
3.6. Extraction d'une sous-chaîne de caractères
3.7. Comparaison de deux chaînes de caractères
3.8. Méthodes de comparaisons lexicographiques
3.9. Méthode de recherche de caractères
3.10. Méthode de modification de chaîne
3.11. Méthode de duplication
3.12. Support de l'unicode, internationalisation
Structure d'une classe
1. Introduction
2. Classes
2.1. Classes publiques
2.2. Classes internes
2.3. Classe membre
2.4. Classes locales
2.5. Classes anonymes
2.6. Le mot-clé this
3. Éléments statiques
3.1. Champ statique
3.2. Cas des constantes
3.3. Bloc statique
3.4. Classe membre statique
4. Membres d'une classe, visibilité
4.1. Bloc non statique
4.2. Accès à un membre, visibilité
4.3. Les champs
4.4. Signature d'une méthode
4.5. Les méthodes
4.6. Getters et Setters
5. Constructeur, instanciation
5.1. Chargement d'une classe
5.2. Constructeurs d'une classe
5.3. Instanciation d'un objet
5.4. Destruction d'objets
5.5. Le mot-clé final
6. Énumérations
6.1. Déclaration d'une énumération
6.2. Classe énumération
6.3. Méthode toString()
6.4. Méthode valueOf()
6.5. Méthode values()
6.6. Méthode ordinal()
6.7. Méthode compareTo()
6.8. Constructeurs privés
6.9. Classe utilitaire : EnumSet
Noms, opérateurs, tableaux
1. Introduction
2. Identificateurs, noms et expressions
2.1. Identificateurs et noms
2.2. Expressions
3. Opérateurs, ordre d'exécution
3.1. Ordre d'exécution
3.2. Les opérateurs
3.3. Les opérateurs ++ et --
3.4. Les opérateurs % et /
3.5. Les opérateurs <<, >> et >>>
3.6. L'opérateur instanceof
3.7. Les opérateurs &, | et ^
3.8. Les opérateurs && et ||
3.9. L'opérateur ? ... :
3.10. Les opérateurs d'affectation
4. Tableaux
4.1. Création d'un tableau
4.2. Initialisation d'un tableau
4.3. Utilisation d'un tableau comme un Object
4.4. Tableaux de tableaux
4.5. Copie de tableaux
5. Blocs, boucles et contrôles
5.1. Blocs
5.2. Mots-clés réservés
5.3. Tests : if et switch
5.4. Boucles : for, while, do ... while
5.5. Commandes continue et break
5.6. Commandes return et goto
Nombres, précision, calculs
1. Introduction
2. Calculs
2.1. Précision
2.2. Codage des nombres flottants
2.3. Le mot-clé strictfp
2.4. Conversion de types
3. Dépassements de capacité
3.1. Cas des entiers
3.2. Cas des flottants
3.3. Bibliothèques BigInteger et BigDecimal
4. Fonctions mathématiques
4.1. Fonctions usuelles
4.2. Générateurs aléatoires
5. Classes enveloppe
5.1. Associer les types de base à des objets
5.2. Auto-boxing
Héritage, abstraction, interfaces
1. Introduction
2. Abstraction et encapsulation
2.1. Abstraction
2.2. Encapsulation
3. Héritage
3.1. Définition de l'héritage
3.2. Conséquences pour les membres
3.3. Polymorphisme
3.4. Empêcher l'héritage
4. Classes abstraites
5. Interfaces
5.1. Introduction
5.2. Définition
5.3. Java 8 et les interfaces
5.4. Utilisation des interfaces
5.5. Définition de constantes dans les interfaces
5.6. Utilité des interfaces
Packages
1. Introduction
2. Notion de paquet
2.1. Déclaration d’appartenance à un paquet
2.2. Chargement d’une classe
2.3. Choix de nom
3. Archives, chemin de recherche, classpath
3.1. Archives
3.2. Variable CLASSPATH
3.3. Notion de classloader
3.4. Bilan sur les classes chargées
3.5. Visibilité
3.6. Conseils d'écriture, bibliothèque standard
Exceptions
1. Introduction
2. Erreurs et Exceptions
2.1. Classe Throwable, notion de stack trace
2.2. Classe Error
2.3. Classe RuntimException
2.4. Classe Exception
3. Déclenchement d'une exception
3.1. Exceptions déclenchées par la JVM
3.2. Exceptions déclenchées par l'application
4. Capter une exception
4.1. Traiter une exception localement
4.2. Code de captage
5. Créer ses propres exceptions
Entrées / sorties
1. Introduction
2. Notion de fichier
2.1. Introduction
2.2. La classe File
2.3. Construction d'une instance de File
2.4. Méthodes exposées
3. Flux de sortie
3.1. Introduction, notion de flux
3.2. Écriture de caractères, classe Writer
3.3. Bufferisation, construction d'un flux sur un autre
3.4. Utilisation de PrintWriter
3.5. Écriture d'octets, OuputStream
3.6. Écriture de types primitifs : DataOutputStream
3.7. Écriture d'objets : ObjectOutputStream
4. Flux d'entrée
4.1. Introduction
4.2. Lecture de caractères, classe Reader
4.3. Bufferisation, lecture ligne par ligne
4.4. Lecture d'octets : InputStream
4.5. Lecture de types primitifs : DataInputStream
4.6. Lecture d'objets : ObjectInputStream
5. Lecture et écriture de flux croisés
5.1. Introduction
5.2. Lire des caractères sur un flux binaire : classe InputStreamReader
5.3. Écrire des caractères sur un flux binaire : classe OutputStreamWriter
5.4. Remarque sur les jeux de caractères
6. Serialization d'objets
6.1. Enjeu de la sérialization d'objets
6.2. Serialization d'un objet
6.3. Sérialization d'une grappe d'objets
6.4. Première surcharge : méthode writeObject() readObject()
6.5. Deuxième surcharge : utilisation d'un externalizer
6.6. Troisième surcharge : utilisation d'un objet proxy
7. Flux compressés
7.1. Introduction
7.2. Flux de type gzip
7.3. Flux de type zip