Comment manipuler les fichiers en Java?

Nous commençons ce chapitre en revoyant ce que sont les fichiers et les répertoires au sein d’un système de fichiers. Nous présentons également la classe File et l’interface Path ainsi que la façon de les créer.

Conceptualisation du Système de Fichiers

Commençons par les bases. Les données sont stockées sur des dispositifs de stockage persistants, comme les disques durs et les cartes mémoire. Un fichier au sein du dispositif de stockage contient des données. Les fichiers sont organisés en hiérarchies à l’aide de répertoires. Un répertoire est un emplacement qui peut contenir des fichiers ainsi que d’autres répertoires. Lorsque nous travaillons avec des répertoires en Java, nous les traitons souvent comme des fichiers. En fait, nous utilisons beaucoup des mêmes classes et interfaces pour opérer sur les fichiers et les répertoires. Par exemple, un fichier et un répertoire peuvent tous deux être renommés avec la même méthode Java. Notez que nous disons souvent fichier pour désigner un fichier ou un répertoire dans ce chapitre.

Pour interagir avec les fichiers, nous devons nous connecter au système de fichiers. Le système de fichiers est en charge de la lecture et de l’écriture de données au sein d’un ordinateur. Différents systèmes d’exploitation utilisent différents systèmes de fichiers pour gérer leurs données. Par exemple, les systèmes basés sur Windows utilisent un système de fichiers différent de ceux basés sur Unix. La JVM se connectera automatiquement au système de fichiers local, vous permettant d’effectuer les mêmes opérations sur plusieurs plateformes.

Ensuite, le répertoire racine est le répertoire le plus élevé dans le système de fichiers, duquel tous les fichiers et répertoires héritent. Sous Windows, il est désigné par une lettre de lecteur comme C:\, tandis que sous Linux, il est désigné par une seule barre oblique avant, /.

Un chemin est une représentation d’un fichier ou d’un répertoire au sein d’un système de fichiers. Chaque système de fichiers définit son propre caractère séparateur de chemin qui est utilisé entre les entrées de répertoire. La valeur à gauche d’un séparateur est le parent de la valeur à droite du séparateur. Par exemple, la valeur de chemin /utilisateur/accueil/zoo.txt signifie que le fichier zoo.txt est à l’intérieur du répertoire accueil, le répertoire accueil étant à l’intérieur du répertoire utilisateur.

Séparateurs de Fichiers du Système d’Exploitation

Différents systèmes d’exploitation varient dans leur format de noms de chemins. Par exemple, les systèmes basés sur Unix utilisent la barre oblique avant, /, pour les chemins, tandis que les systèmes basés sur Windows utilisent le caractère barre oblique arrière, \. Cela dit, de nombreux langages de programmation et systèmes de fichiers prennent en charge les deux types de barres obliques lors de l’écriture d’instructions de chemin. Java offre une propriété système pour récupérer le caractère séparateur local pour l’environnement actuel :

System.out.print(System.getProperty("file.separator"));

Nous utilisons à la fois des chemins absolus et relatifs vers le fichier ou le répertoire au sein du système de fichiers. Le chemin absolu d’un fichier ou d’un répertoire est le chemin complet depuis le répertoire racine jusqu’au fichier ou répertoire, incluant tous les sous-répertoires qui contiennent le fichier ou le répertoire. Alternativement, le chemin relatif d’un fichier ou d’un répertoire est le chemin depuis le répertoire de travail actuel jusqu’au fichier ou répertoire. Par exemple, voici un chemin absolu vers le fichier Ours.java :

C:\app\animaux\Ours.java

Voici un chemin relatif vers le même fichier, en supposant que le répertoire actuel de l’utilisateur est défini sur C:\app :

animaux\Ours.java

Déterminer si un chemin est relatif ou absolu dépend du système de fichiers. Pour correspondre aux conventions, nous adoptons les conventions suivantes :

  • Si un chemin commence par une barre oblique avant (/), il est absolu, avec / comme répertoire racine, tel que /oiseau/perroquet.png.
  • Si un chemin commence par une lettre de lecteur (c:), il est absolu, avec la lettre de lecteur comme répertoire racine, tel que C:/oiseau/info.
  • Sinon, c’est un chemin relatif, tel que oiseau/perroquet.png.

Les chemins absolus et relatifs peuvent contenir des symboles de chemin. Un symbole de chemin est l’un d’une série de caractères réservés avec une signification spéciale dans certains systèmes de fichiers. Il y a deux symboles de chemin que vous devez connaître, comme indiqué dans le Tableau 14.1.

SymboleDescription
.Une référence au répertoire actuel
..Une référence au parent du répertoire actuel

Supposons que le répertoire actuel est /poisson/requin/marteau. Dans ce cas, ../nager.txt est un chemin relatif équivalent à /poisson/requin/nager.txt. De même, ./jouer.png fait référence à jouer.png dans le répertoire actuel. Ces symboles peuvent également être combinés pour un effet plus grand. Par exemple, ../../poissonClown est un chemin relatif équivalent à /poisson/poissonClown au sein du système de fichiers.

Parfois, vous verrez des symboles de chemin qui sont redondants ou inutiles. Par exemple, le chemin absolu /poisson/poissonClown/../requin/./nager.txt peut être simplifié en /poisson/requin/nager.txt. Nous verrons comment gérer ces redondances plus tard dans le chapitre lorsque nous couvrirons normalize().

Un lien symbolique est un fichier spécial au sein d’un système de fichiers qui sert de référence ou de pointeur vers un autre fichier ou répertoire. Supposons que nous avons un lien symbolique de /zoo/utilisateur/favori vers /poisson/requin. Le dossier requin et ses éléments peuvent être accessibles directement ou via le lien symbolique. Par exemple, les chemins suivants référencent le même fichier :

/poisson/requin/nager.txt
/zoo/utilisateur/favori/nager.txt

En général, les liens symboliques sont transparents pour l’utilisateur, car le système d’exploitation se charge de résoudre la référence au fichier réel. Alors que les API I/O ne prennent pas en charge les liens symboliques, NIO.2 inclut un support complet pour créer, détecter et naviguer dans les liens symboliques au sein du système de fichiers.

Création d’un Fichier ou Chemin

Afin de faire quelque chose d’utile, vous avez d’abord besoin d’un objet qui représente le chemin vers un fichier ou répertoire particulier sur le système de fichiers. En utilisant l’I/O hérité, c’est la classe java.io.File, tandis qu’avec NIO.2, c’est l’interface java.nio.file.Path. La classe File et l’interface Path ne peuvent pas lire ou écrire des données dans un fichier, bien qu’elles soient passées comme référence à d’autres classes, comme vous le verrez dans ce chapitre.

Rappelez-vous, un File ou Path peut représenter un fichier ou un répertoire.

Création d’un Fichier

La classe File est créée en appelant son constructeur. Ce code montre trois constructeurs différents :

File zooFichier1 = new File("/home/tigre/data/rayures.txt");
File zooFichier2 = new File("/home/tigre", "data/rayures.txt");
File parent = new File("/home/tigre");
File zooFichier3 = new File(parent, "data/rayures.txt");
System.out.println(zooFichier1.exists());

Les trois créent un objet File qui pointe vers le même emplacement sur le disque. Si nous passions null comme parent au constructeur final, il serait ignoré, et la méthode se comporterait de la même façon que le constructeur à une seule chaîne. Pour le plaisir, nous montrons également comment vérifier si le fichier existe sur le système de fichiers.

Création d’un Chemin

Puisque Path est une interface, nous ne pouvons pas créer une instance directement. Après tout, les interfaces n’ont pas de constructeurs ! Java fournit un certain nombre de classes et de méthodes que vous pouvez utiliser pour obtenir des objets Path.

La façon la plus simple et la plus directe d’obtenir un objet Path est d’utiliser une méthode de fabrique statique définie sur Path ou Paths. Tous ces quatre exemples pointent vers la même référence sur le disque :

Path zooPath1 = Path.of("/home/tigre/data/rayures.txt");
Path zooPath2 = Path.of("/home", "tigre", "data", "rayures.txt");
Path zooPath3 = Paths.get("/home/tigre/data/rayures.txt");
Path zooPath4 = Paths.get("/home", "tigre", "data", "rayures.txt");
System.out.println(Files.exists(zooPath1));

Les deux méthodes permettent de passer un paramètre varargs pour passer des éléments de chemin supplémentaires. Les valeurs sont combinées et automatiquement séparées par le séparateur de fichiers dépendant du système d’exploitation. Nous montrons également la classe d’aide Files, qui peut vérifier si le fichier existe sur le système de fichiers.

Comme vous pouvez le voir, il y a deux façons de faire la même chose ici. La méthode Path.of() a été introduite dans Java 11 comme méthode statique sur l’interface. La classe de fabrique Paths fournit également une méthode get() pour faire la même chose. Notez le s à la fin de la classe Paths pour la distinguer de l’interface Path. Nous utilisons Path.of() et Paths.get() de manière interchangeable dans ce chapitre.

Vous pourriez remarquer que les classes I/O et NIO.2 peuvent interagir avec un URI. Un identifiant de ressource uniforme (URI) est une chaîne de caractères qui identifie une ressource. Il commence par un schéma qui indique le type de ressource, suivi d’une valeur de chemin comme file:// pour les systèmes de fichiers locaux et http://, https://, et ftp:// pour les systèmes de fichiers distants.

Basculer entre File et Path

Puisque File et Path font tous deux référence à des emplacements sur le disque, il est utile de pouvoir convertir entre eux. Heureusement, Java facilite cette tâche en fournissant des méthodes pour le faire :

File fichier = new File("lapin");
Path maintenant = fichier.toPath();
File retourAuFichier = maintenant.toFile();

De nombreuses anciennes bibliothèques utilisent File, ce qui rend pratique de pouvoir obtenir un File à partir d’un Path et vice versa. Lorsque vous travaillez avec des applications plus récentes, vous devriez vous fier à l’interface Path de NIO.2, car elle contient beaucoup plus de fonctionnalités. Par exemple, seul NIO.2 fournit un support FileSystem, comme nous allons le discuter.

Obtention d’un Path à partir de la Classe FileSystems

NIO.2 fait un usage extensif de la création d’objets avec des classes de fabrique. La classe FileSystems crée des instances de la classe abstraite FileSystem. Cette dernière inclut des méthodes pour travailler avec le système de fichiers directement. Paths.get() et Path.of() sont des raccourcis pour cette méthode FileSystem. Réécrivons nos exemples précédents une fois de plus pour voir comment obtenir une instance Path par la méthode longue :

Path zooPath1 = FileSystems.getDefault()
    .getPath("/home/tigre/data/rayures.txt");
Path zooPath2 = FileSystems.getDefault()
    .getPath("/home", "tigre", "data", "rayures.txt");

Revue des Relations I/O et NIO.2

Le modèle pour I/O est plus petit, et vous n’avez besoin de comprendre que la classe File. En revanche, NIO.2 a plus de fonctionnalités et fait un usage extensif du modèle de fabrique. Vous devriez vous familiariser avec cette approche. Beaucoup de vos interactions avec NIO.2 nécessiteront deux types : une classe abstraite ou une interface et une classe de fabrique ou d’aide.

Examinez attentivement la figure. En particulier, gardez un œil sur le fait que le nom de la classe soit au singulier ou au pluriel. Les classes avec des noms au pluriel incluent des méthodes pour créer ou opérer sur des instances de classe/interface avec des noms au singulier. Rappelez-vous, pour plus de commodité (et source de confusion), un Path peut également être créé à partir de l’interface Path en utilisant la méthode de fabrique statique of().

La classe java.io.File est la classe I/O, tandis que Files est une classe d’aide NIO.2. Files opère sur des instances Path, pas des instances java.io.File. Nous savons que c’est déroutant, mais elles proviennent d’API complètement différentes !

Le tableau 14.2 passe en revue les API que nous avons couvertes pour créer des objets java.io.File et java.nio.file.Path. En lisant le tableau, rappelez-vous que les méthodes statiques opèrent sur la classe/interface, tandis que les méthodes d’instance nécessitent une instance d’un objet. Assurez-vous de bien connaître cela avant de poursuivre avec le reste du chapitre.

CréeDéclaré dansMéthode ou Constructeur
FileFile
public File(String pathname)
public File(File parent, String child)
public File(String parent, String child)
FilePath
public default File toFile()
PathFile
public Path toPath()
PathPath
public static Path of(String first, String... more)
public static Path of(URI uri)
PathPaths
public static Path get(String first, String... more)
public static Path get(URI uri)
PathFileSystem
public Path getPath(String first, String... more)
FileSystemFileSystems
public static FileSystem getDefault()