Plus tôt, nous avons découvert l' API IO (Input/Output Application Programming Interface) et le package java.io , dont les classes sont principalement destinées à travailler avec des flux en Java. La clé ici est le concept de flux .

Aujourd'hui, nous allons commencer à considérer l' API NIO (New Input/Output).

La principale différence entre les deux approches des E/S est que l'API IO est orientée flux tandis que l'API NIO est orientée tampon. Ainsi, les principaux concepts à comprendre sont les tampons et les canaux .

Qu'est-ce qu'un tampon et qu'est-ce qu'un canal ?

Un canal est un portail logique par lequel les données entrent et sortent, tandis qu'un tampon est la source ou la destination de ces données transmises. Lors de la sortie, les données que vous souhaitez envoyer sont placées dans un tampon, et le tampon transmet les données au canal. Lors de l'entrée, les données du canal sont placées dans la mémoire tampon.

Autrement dit:

  • un tampon est simplement un bloc de mémoire dans lequel nous pouvons écrire des informations et à partir duquel nous pouvons lire des informations,
  • un canal est une passerelle qui permet d'accéder à des périphériques d'E/S tels que des fichiers ou des sockets.

Les canaux sont très similaires aux flux du package java.io. Toutes les données qui vont n'importe où (ou viennent de n'importe où) doivent passer par un objet de canal. En général, pour utiliser le système NIO, vous obtenez un canal vers une entité d'E/S et un tampon pour stocker les données. Ensuite, vous travaillez avec le tampon, en entrant ou en sortant des données selon vos besoins.

Vous pouvez avancer et reculer dans un tampon, c'est-à-dire "parcourir" le tampon, ce que vous ne pouvez pas faire dans les flux. Cela donne plus de flexibilité lors du traitement des données. Dans la bibliothèque standard, les tampons sont représentés par la classe abstraite Buffer et plusieurs de ses descendants :

  • ByteBuffer
  • CharBuffer
  • ShortBuffer
  • IntBuffer
  • FloatBuffer
  • DoubleBuffer
  • LongBuffer

La principale différence entre les sous-classes est le type de données qu'elles stockent - bytes , ints , longs et autres types de données primitifs.

Propriétés du tampon

Un tampon a quatre propriétés principales. Ce sont la capacité, la limite, la position et la marque.

La capacité est la quantité maximale de données/octets pouvant être stockées dans la mémoire tampon. La capacité d'un tampon ne peut pas être modifiée . Une fois qu'un tampon est plein, il doit être vidé avant d'y écrire davantage.

En mode écriture, la limite d'un tampon est identique à sa capacité, indiquant la quantité maximale de données pouvant être écrites dans le tampon. En mode lecture, la limite d'un tampon fait référence à la quantité maximale de données pouvant être lues à partir du tampon.

La position indique la position actuelle du curseur dans le tampon. Initialement, il est mis à 0 lors de la création du tampon. En d'autres termes, c'est l'indice du prochain élément à lire ou à écrire.

La marque est utilisée pour enregistrer une position de curseur. Lorsque nous manipulons un tampon, la position du curseur change constamment, mais nous pouvons toujours le ramener à la position précédemment marquée.

Méthodes de travail avec un tampon

Examinons maintenant l'ensemble principal de méthodes qui nous permettent de travailler avec notre tampon (bloc de mémoire) pour lire et écrire des données vers et depuis les canaux.

  1. allouer(int capacité) — cette méthode est utilisée pour allouer un nouveau tampon avec la capacité spécifiée. La méthode allow() lève une IllegalArgumentException si la capacité transmise est un entier négatif.

  2. capacity() renvoie la capacité du tampon actuel .

  3. position() renvoie la position actuelle du curseur. Les opérations de lecture et d'écriture déplacent le curseur à la fin du tampon. La valeur de retour est toujours inférieure ou égale à la limite.

  4. limit() renvoie la limite du tampon courant.

  5. mark() est utilisé pour marquer (enregistrer) la position actuelle du curseur.

  6. reset() ramène le curseur à la position précédemment marquée (sauvegardée).

  7. clear () définit la position à zéro et définit la limite de la capacité. Cette méthode n'efface pas les données dans la mémoire tampon. Il réinitialise uniquement la position, la limite et la marque.

  8. flip() fait passer le tampon du mode écriture au mode lecture. Il fixe également la limite à la position actuelle, puis remet la position à zéro.

  9. read() — La méthode read du canal est utilisée pour écrire des données du canal dans le tampon, tandis que la méthode put() du tampon est utilisée pour écrire des données dans le tampon.

  10. write() — La méthode d'écriture du canal est utilisée pour écrire des données du tampon vers le canal, tandis que la méthode get() du tampon est utilisée pour lire les données du tampon.

  11. rewind() rembobine le tampon. Cette méthode est utilisée lorsque vous avez besoin de relire le tampon — elle définit la position à zéro et ne change pas la limite.

Et maintenant quelques mots sur les chaînes.

Les implémentations de canaux les plus importantes dans Java NIO sont les classes suivantes :

  1. FileChannel — Un canal pour lire et écrire des données depuis/vers un fichier.

  2. DatagramChannel — Cette classe lit et écrit des données sur le réseau via UDP (User Datagram Protocol).

  3. SocketChannel — Un canal pour lire et écrire des données sur le réseau via TCP (Transmission Control Protocol).

  4. ServerSocketChannel — Un canal pour lire et écrire des données sur des connexions TCP, tout comme le fait un serveur Web. Un SocketChannel est créé pour chaque connexion entrante.

Pratique

Il est temps d'écrire quelques lignes de code. Tout d'abord, lisons le fichier et affichons son contenu sur la console, puis écrivons une chaîne dans le fichier.

Le code contient beaucoup de commentaires — j'espère qu'ils vous aideront à comprendre comment tout fonctionne :


// Create a RandomAccessFile object, passing in the file path
// and a string that says the file will be opened for reading and writing
try (RandomAccessFile randomAccessFile = new RandomAccessFile("text.txt", "rw");
    // Get an instance of the FileChannel class
    FileChannel channel = randomAccessFile.getChannel();
) {
// Our file is small, so we'll read it in one go   
// Create a buffer of the required size based on the size of our channel
   ByteBuffer byteBuffer = ByteBuffer.allocate((int) channel.size());
   // Read data will be put into a StringBuilder
   StringBuilder builder = new StringBuilder();
   // Write data from the channel to the buffer
   channel.read(byteBuffer);
   // Switch the buffer from write mode to read mode
   byteBuffer.flip();
   // In a loop, write data from the buffer to the StringBuilder
   while (byteBuffer.hasRemaining()) {
       builder.append((char) byteBuffer.get());
   }
   // Display the contents of the StringBuilder on the console
   System.out.println(builder);
 
   // Now let's continue our program and write data from a string to the file
   // Create a string with arbitrary text
   String someText = "Hello, Amigo!!!!!";
   // Create a new buffer for writing,
   // but let the channel remain the same, because we're going to the same file
   // In other words, we can use one channel for both reading and writing to a file
   // Create a buffer specifically for our string — convert the string into an array and get its length
   ByteBuffer byteBuffer2 = ByteBuffer.allocate(someText.getBytes().length);
   // Write our string to the buffer
   byteBuffer2.put(someText.getBytes());
   // Switch the buffer from write mode to read mode
   // so that the channel can read from the buffer and write our string to the file
   byteBuffer2.flip();
   // The channel reads the information from the buffer and writes it to our file
   channel.write(byteBuffer2);
} catch (FileNotFoundException e) {
   e.printStackTrace();
} catch (IOException e) {
   e.printStackTrace();
}

Essayez l' API NIO - vous allez l'adorer !