mars
2011
Dans le cadre de la JSR 203, plus connu sous le nom de NIO.2, Java 7 va enfin être doté d’une toute nouvelle API pour l’accès au système de fichier, en remplacement de la classe File
, qui malgré le fait qu’elle soit logiquement très utilisée, reste très problématique et incomplète dans de nombreux cas…
Mais que reproche-t-on à l’API de la classe File
?
- L’absence d’exception utile ! En effet mis à part quelques exceptions basiques (NullPointerException, SecuityException), les exceptions sont très peu utilisées et beaucoup de méthodes se contentent de renvoyer un booléen pour indiquer leur bon déroulement.
Le problème c’est qu’en cas d’erreur on se retrouve totalement démunis car dans l’impossibilité d’obtenir la moindre information sur l’origine du problème, et donc de le traiter comme il se doit ! - Un accès limité aux propriétés des fichiers. On ne dispose en effet que du
minimum syndical
sans véritable accès à bon nombre de propriétés du système de fichier (propriétaire du fichier, groupe, permissions, ACL, attributs archive ou système, …) voir un accès biaisé ou partiel (propriétés readable/writable/executable).
La gestion d’un simple attribut peut devenir un vrai parcours du combattant, car cela implique soit l’utilisation de code natif, soit l’exécution de programme externe… - Une API un peu incomplète. Certaines fonctionnalités pourtant basique sont absente (copie/déplacement de fichier, gestion des liens, résolution de chemin relatif) et d’autres s’avèrent problématique (comme
listFiles()
qui se montre très peu performant dans des répertoires de taille importante).
La classe File
est donc destinée à être remplacée par une nouvelle classe (Path
) qui viendra résoudre tous ces problèmes, ou plus précisément par tout un package : java.nio.file
…
[edit] Attention : depuis la date de rédaction de cet article, l’API java.nio.file a été grandement remanié.
Les codes sources présentées ici ne sont donc plus tout à fait correct.
Cette nouvelle API prend une approche relativement différente. Plutôt que de représenter un fichier sur le système hôte (comme avec la classe File
), on utilisera désormais des chemins absolu ou relatif (Path
) représentant un fichier présent sur un système de fichier quelconque (FileSystem
) utilisant un ou plusieurs emplacement de stockage (FileStorage
).
En effet l’API est basé sur des fabriques, qui permettent de manipuler les objets au mieux sans forcément connaitre les spécificités de leurs implémentations :
// Ancien code :
File file = new File("file.txt");
// Nouveau code :
Path path = Paths.get("file.txt");
Par défaut on manipulera bien sûr des chemins (Path
) vers des fichiers présent sur le système de fichier de l’OS hôte (FileSystem
), c’est à dire sur les disques et autre périphériques de stockage (FileStorage
). Mais l’API est complètement évolutive et on pourrait très bien voir arriver des implémentations utilisant d’autres « sources », comme un stockage en mémoire, directement depuis une archive, ou sur une source distante… Le tout de manière totalement transparente !
Note : de la même manière que la classe File
, on représentera un chemin logique vers un fichier, même si ce dernier n’existe pas réellement.
Au niveau de son API, on retrouve une partie des fonctionnalités de la classe File
, à la différence près que celles-ci utilisent désormais massivement les mécanismes d’exceptions. Ainsi on peut désormais être informé des problèmes et en avertir l’utilisateur, voir de mettre en place une solution de contournement. Fini les heures de recherche pour déterminer la cause de l’échec d’une suppression de fichier :
// Ancien code :
boolean deleted = file.delete();
if (!deleted) {
// Pourquoi ????
}
// Nouveau code :
try {
path.delete();
} catch (IOException e) {
// On a désormais une explication
}
A noter au passage que pour faciliter la transition, la classe File
se voit doter d’une nouvelle méthode toPath()
permettant d’utiliser facilement les nouvelles méthodes depuis un objet File
:
try {
file.toPath().delete();
} catch (IOException e) {
// On a désormais une explication
}
Des « vues » pour accéder aux propriétés
La grosse différence vient surtout de l’accès aux propriétés des fichiers. Là où l’on devait se contenter des propriétés communes à l’ensemble des systèmes d’exploitations, on a désormais accès à toutes les propriétés spécifiques au système, en lecture/écriture.
Pour cela les accès aux attributs passent par des vues, qui permettent de lire les attributs et d’en modifier (lorsque cela s’avère possible). Par exemple pour accéder aux propriétés de base il faudra utiliser le code suivant :
BasicFileAttributeView basicView = path.getFileAttributeView(BasicFileAttributeView.class);
if (basicView != null) {
BasicFileAttributes basic = basicView.readAttributes();
System.out.println(basic.isRegularFile());
System.out.println(basic.isDirectory());
System.out.println(basic.isSymbolicLink());
System.out.println(basic.isOther());
System.out.println(basic.size());
System.out.println(basic.creationTime());
System.out.println(basic.lastAccessTime());
System.out.println(basic.lastModifiedTime());
}
Selon le système de fichier et l’OS, on pourra accéder à des propriétés différentes. Par défaut l’API propose six types de vues :
- BasicFileAttributeView permet d’accéder à un certains nombres de propriétés basiques, qui sont généralement commune à un grand nombre de système de fichier (comme le type du fichier, sa taille ou ses dates d’accès/modif).
- DosFileAttributeView nous offre enfin un accès sur les attributs « DOS » spécifiques aux systèmes Windows (readonly, hidden, system et archive).
- PosixFileAttributeView permet l’accès aux informations relatives aux systèmes implémentant le standard POSIX sur les systèmes de fichiers, que l’on retrouve dans le monde Unix au sens large (propriétaire, groupe et permissions de base du fichier).
- FileOwnerAttributeView permet uniquement de manipuler le propriétaire du fichier, si le système de fichier comporte cette notion.
- AclFileAttributeView supporte la gestion de l’ACL, c’est à dire de gérer finement les permissions d’accès aux fichiers.
- Enfin UserDefinedFileAttributeView permet d’accéder aux attributs étendus, c’est à dire des méta-données quelconques associés au fichier.
Il est ainsi possible d’accéder (et de modifier) des propriétés hautement dépendantes du système voir même du support de stockage. Bien sûr ces vues ne sont pas disponible sur tous les supports ni sur tous les systèmes…
A noter l’existence de méthodes statiques dans la classe Attributes
permettant accéder directement aux attributs :
BasicFileAttributes basic = Attributes.readBasicFileAttributes(path);
Ainsi que la possibilité d’accéder aux attributs via leurs noms (précédé du nom de la vue) :
System.out.println( path.getAttribute("basic:creationTime") );
System.out.println( path.getAttribute("posix:group") );
System.out.println( path.getAttribute("owner:owner") );
System.out.println( path.getAttribute("dos:archive") );
Ce système de vue peut sembler un peu plus complexe, mais il permet une évolution simplifié et un meilleur support des sources alternatives.
Une API plus complète
Support des liens symboliques
La classe supporte pleinement les liens symboliques (si le système hôte les supporte bien sûr). Ainsi en plus des méthodes createSymbolicLink()
et readSymbolicLink()
, il est possible d’utiliser la valeur LinkOption.NOFOLLOW_LINKS
pour manipuler le lien en lui-même et non pas sa cible…
Fabrique de flux
On remarquera également que la classe comporte un ensemble de méthode permettant de créer différents types de flux (newInputStream()
, newOutputStream()
, newByteChannel()
). Ceci permet de laisser au système la possibilité de fournir un type de flux plus adapté.
DirectoryStream / FileVisitor
L’API vient également comblé un des gros problème de la classe File
: le parcours des éléments d’un répertoire ou d’une arborescence pouvait s’avérer très consommateur en mémoire, car les différentes méthodes list()/listFiles()
renvoient un tableau contenant tous les éléments trouvés.
Désormais on utilisera un Iterator
afin de parcourir la liste des éléments un à un sans tous les charger en mémoire :
DirectoryStream directory = path.newDirectoryStream();
try {
for (Path p : directory) {
System.out.println(p);
}
} finally {
directory.close();
}
On a enfin la possibilité d’utiliser une syntaxe similaire aux meta-caractères des shells pour filtrer les résultats plus facilement :
// On ne recherche que les fichiers java :
DirectoryStream directory = path.newDirectoryStream("*.java");
Et la classe Files
propose des méthodes permettant de parcourir récursivement une hiérarchie de répertoire via un FileVisitor
, éventuellement en prenant en compte la détection des cycles…
Notification de changement
L’API intègre également la notification de changement sur le système de fichier :
// On crée un WatchService :
WatchService watcher = path.getFileSystem().newWatchService();
// Puis on l'enregistre sur un répertoire avec certain évènement :
path.register(watcher,
StandardWatchEventKind.ENTRY_CREATE,
StandardWatchEventKind.ENTRY_MODIFY,
StandardWatchEventKind.ENTRY_DELETE);
Ceci permet de surveiller les modifications apportées à un système de fichier sans avoir à le re-scanner perpétuellement. Ce mécanisme utilisera bien sûr les API de notifications spécifiques au système hôte (Notification, inotify, FSEvents) ou une implémentation pure Java s’il n’en comporte aucune.
Il ne reste plus qu’à surveiller les évènements dans un thread séparé :
while (true) {
WatchKey key = watcher.take();
for (WatchEvent event : key.pollEvents()) {
System.out.println(event.kind() + " : " + event.context());
}
key.reset();
}
11 Commentaires + Ajouter un commentaire
Tutoriels
Discussions
- Difference de performances Unix/Windows d'un programme?
- L'apparition du mot-clé const est-il prévu dans une version à venir du JDK?
- [REFLEXION] Connaitre toutes les classes qui implémentent une interface
- jre 1.5, tomcat 6.0 et multi processeurs
- Recuperation du nom des parametres
- [ fuite ] memoire
- Définition exacte de @Override
- Possibilité d'accéder au type générique en runtime
- Classes, méthodes private
Salut,
A priori, tu saurais pas si NIO.2 est issue d’une librairie qui serait utilisable avec le JDK 1.6?
Je n’ai pas dit qu’il était nécessaire d’endormir le thread, mais simplement que c’était fait automatiquement
a++
Bonjour,
sauf erreur de ma part, il n’est pas nécessaire d’endormir le Thread.
La boucle n’est là que pour « réécouter » les evenements après qu’un/plusieurs évenements aie eu lieu.
Le code WatchKey key = watcher.take(); met en effet le Thread courrant en « sommeil ».
Cela simplifie grandement la vie…
Stéphane
Bonne journée.
Merci pour ta réponse !
A+
Les événements graphiques sont également géré via une boucle… sauf que ce n’est pas toi qui la fait directement, mais le thread d’affichage (EDT). En effet grosso modo l’EDT correspond à une boucle qui traite les évènements les uns à la suite des autres, en appelant les méthodes qui vont bien…
Pour reproduire cela ici, il aurait fallut créer un thread implicite qui effectuerait la boucle et appellerait les listeners… ce qui ne serait pas forcément adapté à tous les besoins…
Un exemple : en mono-thread cette boucle permet d’endormir le process en attendant les nouveaux fichiers
a++
Hello
Tout ceci est bien !
Il n’y a qu’un truc qui me semble un peu bizarre, le traitement des événements avec un while
Pourquoi ce n’est pas géré comme les clic de souris ? avec des Listeners et des Handlers ?
(Je ne fais plus très souvent du java mais il me semble que c’était géré comme ça les événements…)
En effet il est normalement prévu une API de Date au sens large (avec notion de Date, durée, période, etc.), ainsi qu’une API de « mesures » il me semble (monnaies, distances, etc.).
Mais rien n’est encore intégré, donc wait & see
a++
Il me semble qu’ils devaient intégrer une nouvelle API des dates (un peu comme ils avaient fait pour la concurrence : Prendre une API existante reconnue et l’intégrer).
Je crois qu’il s’agissait de Joda Time ? C’est toujours prévu ?
Attention ne confondez pas tout : ce n’était pas un compte-rendu des ajouts de Java 7, mais simplement des modifications du langage.
Les APIs vont bien sûr elles aussi évolué et s’enrichir…
Je vais essayer de les présenter dans divers billets, mais seulement une fois qu’elle seront intégré dans les builds du JDK (ce qui est déjà le cas pour les FileSystems).
Par exemple NIO.2 devrait également intégré la gestion des I/O asynchrones, ainsi qu’un support étendu des SocketChannel.
Enfin en ce qui concerne la possibilité d’étendre les FileSystems, c’est assez simple dans l’idée puisqu’il « suffit » de redéfinir une classe (FileSystemProvider) qui servira de fabriques pour les FileSystems. Mais bien sûr l’implémentation complète risque d’être un peu plus complexe…
a++
Haaa, comme le dis benwit : « enfin des améliorations utiles.« .
Le premier compte rendu des ajouts de Java7 ne m’avait pas vraiment rendu impatient mais ici ça deviens intéressant. Il faut voir comment il est possible d’étendre le système pour supporter d’autres
.
Ah enfin des améliorations utiles.
C’est vrai que l’ancienne classe File était affreusement nommée (cf Thinking in Java) et je m’étais écris mes propres classes Path et FileSystem, ajouter les méthodes qui manquaient, etc…
Là, c’est intégré de base et ils ont enfin compris qu’au lieu de niveler par le bas, on peut proposer différentes vues. Dans le pire des cas (non disponibilité), c’est comme avant et dans le meilleur des cas, on a plus d’informations.
Je retiens également le système d’évènement très utile pour être prévenu des modifications.
Merci pour ce compte rendu ;o)