Eerder maakten we kennis met de IO API (Input/Output Application Programming Interface) en het java.io- pakket, waarvan de klassen voornamelijk bedoeld zijn voor het werken met streams in Java. De sleutel hier is het concept van een stroom .

Vandaag gaan we de NIO API (New Input/Output) overwegen .

Het belangrijkste verschil tussen de twee benaderingen van I/O is dat de IO API stream-georiënteerd is, terwijl de NIO API buffer-georiënteerd is. Dus de belangrijkste concepten om te begrijpen zijn buffers en kanalen .

Wat is een buffer en wat is een kanaal?

Een kanaal is een logisch portaal waardoor gegevens in- en uitgaan, terwijl een buffer de bron of bestemming is van deze verzonden gegevens. Tijdens de uitvoer worden de gegevens die u wilt verzenden in een buffer geplaatst en de buffer geeft de gegevens door aan het kanaal. Tijdens invoer worden de gegevens van het kanaal in de buffer geplaatst.

Met andere woorden:

  • een buffer is gewoon een geheugenblok waarin we informatie kunnen schrijven en waaruit we informatie kunnen lezen,
  • een kanaal is een gateway die toegang biedt tot I/O-apparaten zoals bestanden of sockets.

Kanalen lijken sterk op streams in het java.io-pakket. Alle gegevens die ergens heen gaan (of overal vandaan komen) moeten door een kanaalobject gaan. Om het NIO-systeem te gebruiken, krijgt u over het algemeen een kanaal naar een I/O-entiteit en een buffer voor het opslaan van gegevens. Vervolgens werk je met de buffer en voer je gegevens in of uit als dat nodig is.

Je kunt vooruit en achteruit gaan in een buffer, dwz de buffer "lopen", iets wat je niet zou kunnen doen in streams. Dit geeft meer flexibiliteit bij het verwerken van gegevens. In de standaardbibliotheek worden buffers vertegenwoordigd door de abstracte klasse Buffer en een aantal van zijn afstammelingen:

  • ByteBuffer
  • CharBuffer
  • ShortBuffer
  • IntBuffer
  • FloatBuffer
  • Dubbele Buffer
  • Lange Buffer

Het belangrijkste verschil tussen de subklassen is het gegevenstype dat ze opslaan — bytes , ints , longs en andere primitieve gegevenstypen.

Buffer eigenschappen

Een buffer heeft vier hoofdeigenschappen. Dit zijn capaciteit, limiet, positie en markering.

Capaciteit is de maximale hoeveelheid gegevens/bytes die in de buffer kunnen worden opgeslagen. De capaciteit van een buffer kan niet worden gewijzigd . Zodra een buffer vol is, moet deze worden gewist voordat er meer naar kan worden geschreven.

In de schrijfmodus is de limiet van een buffer gelijk aan de capaciteit, wat de maximale hoeveelheid gegevens aangeeft die naar de buffer kan worden geschreven. In de leesmodus verwijst de limiet van een buffer naar de maximale hoeveelheid gegevens die uit de buffer kan worden gelezen.

De positie geeft de huidige positie van de cursor in de buffer aan. In eerste instantie is deze ingesteld op 0 wanneer de buffer wordt gemaakt. Met andere woorden, het is de index van het volgende element dat moet worden gelezen of geschreven.

De markering wordt gebruikt om een ​​cursorpositie op te slaan. Terwijl we een buffer manipuleren, verandert de cursorpositie voortdurend, maar we kunnen deze altijd terugzetten naar de eerder gemarkeerde positie.

Methoden voor het werken met een buffer

Laten we nu eens kijken naar de belangrijkste set methoden waarmee we met onze buffer (geheugenblok) kunnen werken voor het lezen en schrijven van gegevens van en naar kanalen.

  1. allocate(int capacity) — deze methode wordt gebruikt om een ​​nieuwe buffer toe te wijzen met de gespecificeerde capaciteit. De methode allocate() genereert een IllegalArgumentException als de doorgegeven capaciteit een negatief geheel getal is.

  2. capacity() retourneert de capaciteit van de huidige buffer .

  3. position() geeft de huidige cursorpositie terug. Lees- en schrijfbewerkingen verplaatsen de cursor naar het einde van de buffer. De retourwaarde is altijd kleiner dan of gelijk aan de limiet.

  4. limit() retourneert de limiet van de huidige buffer.

  5. mark() wordt gebruikt om de huidige cursorpositie te markeren (opslaan).

  6. reset() zet de cursor terug naar de eerder gemarkeerde (opgeslagen) positie.

  7. clear() stelt de positie in op nul en stelt de limiet in voor de capaciteit. Deze methode wist de gegevens in de buffer niet . Het initialiseert alleen de positie, limiet en markering opnieuw.

  8. flip() schakelt de buffer van schrijfmodus naar leesmodus. Het stelt ook de limiet in op de huidige positie en zet de positie vervolgens weer op nul.

  9. read() — De leesmethode van het kanaal wordt gebruikt om gegevens van het kanaal naar de buffer te schrijven, terwijl de methode put() van de buffer wordt gebruikt om gegevens naar de buffer te schrijven.

  10. write() — De schrijfmethode van het kanaal wordt gebruikt om gegevens van de buffer naar het kanaal te schrijven, terwijl de methode get() van de buffer wordt gebruikt om gegevens uit de buffer te lezen.

  11. rewind() spoelt de buffer terug. Deze methode wordt gebruikt wanneer u de buffer opnieuw moet lezen - het zet de positie op nul en verandert de limiet niet.

En nu een paar woorden over kanalen.

De belangrijkste kanaalimplementaties in Java NIO zijn de volgende klassen:

  1. FileChannel — Een kanaal voor het lezen en schrijven van gegevens van/naar een bestand.

  2. DatagramChannel — Deze klasse leest en schrijft gegevens over het netwerk via UDP (User Datagram Protocol).

  3. SocketChannel — Een kanaal voor het lezen en schrijven van gegevens via het netwerk via TCP (Transmission Control Protocol).

  4. ServerSocketChannel — Een kanaal voor het lezen en schrijven van gegevens via TCP-verbindingen, net zoals een webserver dat doet. Voor elke inkomende verbinding wordt een SocketChannel aangemaakt.

Oefening

Het is tijd om een ​​paar regels code te schrijven. Laten we eerst het bestand lezen en de inhoud op de console weergeven, en dan een tekenreeks naar het bestand schrijven.

De code bevat veel opmerkingen — ik hoop dat ze je zullen helpen begrijpen hoe alles werkt:


// 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();
}

Probeer de NIO API - je zult het geweldig vinden!