Le package java.nio en Java offre une approche plus performante pour la gestion des entrées/sorties, particulièrement adaptée au traitement de gros volumes de données. Contrairement au modèle traditionnel basé sur les flux, NIO utilise des canaux (Channels) et des tampons (Buffers) pour un accès plus direct et efficace aux données.
Manipulation de Fichiers avec les Canaux
Les canaux permettent d'établir des connexions aux sources de données et d'y écrire ou en lire. Voici un exemple de création et de modification d'un fichier à l'aide de canaux :
package com.java.nio.operations;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class FileChannelOps {
private static final int BUFFER_SIZE = 1024;
public static void main(String[] args) throws Exception {
// Créer un canal pour écrire dans un fichier
try (FileChannel writeChannel = new FileOutputStream("output.txt").getChannel()) {
writeChannel.write(ByteBuffer.wrap("Contenu initial ".getBytes()));
}
// Utiliser RandomAccessFile pour ajouter du contenu à la fin
try (RandomAccessFile raf = new RandomAccessFile("output.txt", "rw");
FileChannel rwChannel = raf.getChannel()) {
rwChannel.position(rwChannel.size()); // Se positionner à la fin du fichier
rwChannel.write(ByteBuffer.wrap("Ajout supplémentaire.".getBytes()));
}
// Lire le contenu du fichier
try (FileChannel readChannel = new FileInputStream("output.txt").getChannel()) {
ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
readChannel.read(buffer);
buffer.flip(); // Préparer le buffer pour la lecture
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
System.out.println();
}
}
}
Lors de l'utilisatino de FileChannel pour la lecture, il est crucial d'appeler buffer.flip() après une opération de lecture pour préparer le tampon à être lu. Si des opérations de lecture successives sont prévues, buffer.clear() doit être appelé avant chaque nouvelle lecture pour réinitialiser le tampon.
Copie de Fichiers Efficace
NIO propose des méthodes optimisées pour transférer des données directement entre canaux, sans passer nécessairement par des tampons intermédiaires explicites. Les méthodes transferTo() et transferFrom() sont idéales pour cela.
package com.java.nio.operations;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.channels.FileChannel;
public class EfficientFileCopy {
public static void main(String[] args) throws Exception {
if (args.length != 2) {
System.err.println("Usage: java EfficientFileCopy <source> <destination>");
System.exit(1);
}
try (FileChannel sourceChannel = new FileInputStream(args[0]).getChannel();
FileChannel destinationChannel = new FileOutputStream(args[1]).getChannel()) {
// Transfère directement les données du canal source vers le canal destination
long transferred = sourceChannel.transferTo(0, sourceChannel.size(), destinationChannel);
System.out.println("Nombre d'octets transférés : " + transferred);
// Alternativement :
// destinationChannel.transferFrom(sourceChannel, 0, sourceChannel.size());
}
}
}
</destination></source>
Cette approche est généralement plus performante que la copie octet par octet via un tampon.
Gestion des Caractères et Encodages
Bien que les tampons NIO travaillent avec des octets, il est souvent nécessaire de les interpréter comme des caractères. La conversion directe d'un ByteBuffer en CharBuffer peut être délicate en raison des différences d'encodage.
package com.java.nio.operations;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
public class BufferTextHandling {
private static final int BUFFER_SIZE = 1024;
public static void main(String[] args) throws Exception {
// Écrire des données initiales
try (FileChannel fc = new FileOutputStream("data_text.txt").getChannel()) {
fc.write(ByteBuffer.wrap("Texte initial".getBytes()));
}
// Lire et décoder
try (FileChannel fc = new FileInputStream("data_text.txt").getChannel()) {
ByteBuffer byteBuffer = ByteBuffer.allocate(BUFFER_SIZE);
fc.read(byteBuffer);
byteBuffer.flip();
// Tentative d'affichage direct (peut ne pas fonctionner comme attendu)
// System.out.println("Affichage direct CharBuffer: " + byteBuffer.asCharBuffer());
// Décoder avec l'encodage par défaut du système
String encoding = System.getProperty("file.encoding");
System.out.println("Décodé avec " + encoding + ": " + Charset.forName(encoding).decode(byteBuffer));
// Écrire en utilisant un encodage spécifique
byteBuffer.clear(); // Réinitialiser pour réutiliser
try (FileChannel writeChannel = new FileOutputStream("data_text.txt").getChannel()) {
writeChannel.write(ByteBuffer.wrap("Texte encodé UTF-16BE".getBytes("UTF-16BE")));
}
// Relire et afficher avec l'encodage UTF-16BE
byteBuffer.clear();
try (FileChannel readChannel = new FileInputStream("data_text.txt").getChannel()) {
readChannel.read(byteBuffer);
byteBuffer.flip();
System.out.println("Affichage UTF-16BE: " + byteBuffer.asCharBuffer());
}
// Écrire en utilisant CharBuffer directement
byteBuffer.clear();
CharBuffer charBuffer = byteBuffer.asCharBuffer();
charBuffer.put("Utilisation de CharBuffer.");
try (FileChannel writeChannel = new FileOutputStream("data_text.txt").getChannel()) {
// Rembobiner le CharBuffer pour qu'il soit prêt à être lu par le ByteBuffer
charBuffer.flip();
writeChannel.write(byteBuffer);
}
// Relire et afficher le contenu écrit via CharBuffer
byteBuffer.clear();
try (FileChannel readChannel = new FileInputStream("data_text.txt").getChannel()) {
readChannel.read(byteBuffer);
byteBuffer.flip();
System.out.println("Affichage via CharBuffer: " + byteBuffer.asCharBuffer());
}
}
}
}
Pour une gestion corrcete des caractères, il est recommandé d'utiliser explicitement la classe Charset pour encoder et décoder les données, ou d'employer CharBuffer pour les opérations spécifiques aux caractères.