Tidligere lærte vi IO API (Input/Output Application Programming Interface) og java.io -pakken at kende, hvis klasser hovedsageligt er til at arbejde med streams i Java. Nøglen her er begrebet en strøm .

I dag vil vi begynde at overveje NIO API (New Input/Output).

Den største forskel mellem de to tilgange til I/O er, at IO API er strømorienteret, mens NIO API er bufferorienteret. Så de vigtigste begreber at forstå er buffere og kanaler .

Hvad er en buffer, og hvad er en kanal?

En kanal er en logisk portal, hvorigennem data bevæger sig ind og ud, mens en buffer er kilden eller destinationen for disse overførte data. Under output lægges de data, du vil sende, i en buffer, og bufferen sender dataene til kanalen. Under input bliver data fra kanalen lagt i bufferen.

Med andre ord:

  • en buffer er simpelthen en hukommelsesblok, som vi kan skrive information i, og hvorfra vi kan læse information,
  • en kanal er en gateway, der giver adgang til I/O-enheder såsom filer eller sockets.

Kanaler minder meget om streams i java.io-pakken. Alle data, der går hvor som helst (eller kommer fra hvor som helst), skal passere gennem et kanalobjekt. Generelt, for at bruge NIO-systemet, får du en kanal til en I/O-entitet og en buffer til lagring af data. Derefter arbejder du med bufferen, indlæser eller udskriver data efter behov.

Man kan bevæge sig frem og tilbage i en buffer, altså "gå" bufferen, hvilket er noget man ikke kunne i vandløb. Dette giver større fleksibilitet ved behandling af data. I standardbiblioteket er buffere repræsenteret af den abstrakte bufferklasse og flere af dens efterkommere:

  • ByteBuffer
  • CharBuffer
  • Kort Buffer
  • IntBuffer
  • FloatBuffer
  • Dobbeltbuffer
  • Lang Buffer

Den største forskel mellem underklasserne er den datatype, de gemmer - bytes , ints , longs og andre primitive datatyper.

Bufferegenskaber

En buffer har fire hovedegenskaber. Disse er kapacitet, grænse, position og mærke.

Kapacitet er den maksimale mængde data/bytes, der kan lagres i bufferen. En buffers kapacitet kan ikke ændres . Når en buffer er fuld, skal den ryddes, før du skriver mere til den.

I skrivetilstand er en buffers grænse den samme som dens kapacitet, hvilket angiver den maksimale mængde data, der kan skrives til bufferen. I læsetilstand refererer en buffers grænse til den maksimale mængde data, der kan læses fra bufferen.

Positionen angiver den aktuelle position for markøren i bufferen. Til at begynde med er den sat til 0, når bufferen oprettes. Det er med andre ord indekset for det næste element, der skal læses eller skrives.

Mærket bruges til at gemme en markørposition. Når vi manipulerer en buffer, ændres markørens position konstant, men vi kan altid returnere den til den tidligere markerede position.

Metoder til at arbejde med en buffer

Lad os nu se på hovedsættet af metoder, der lader os arbejde med vores buffer (hukommelsesblok) til at læse og skrive data til og fra kanaler.

  1. allocate(int kapacitet) — denne metode bruges til at allokere en ny buffer med den specificerede kapacitet. Allocate ()- metoden kaster en IllegalArgumentException , hvis den beståede kapacitet er et negativt heltal.

  2. capacity() returnerer den aktuelle buffers kapacitet .

  3. position() returnerer den aktuelle markørposition. Læse- og skrivehandlinger flytter markøren til slutningen af ​​bufferen. Returværdien er altid mindre end eller lig med grænsen.

  4. limit() returnerer den aktuelle buffers grænse.

  5. mark() bruges til at markere (gemme) den aktuelle markørposition.

  6. reset() returnerer markøren til den tidligere markerede (gemte) position.

  7. clear() sætter positionen til nul og sætter grænsen for kapaciteten. Denne metode rydder ikke dataene i bufferen. Det geninitialiserer kun positionen, grænsen og mærket.

  8. flip() skifter bufferen fra skrivetilstand til læsetilstand. Den sætter også grænsen til den aktuelle position og sætter derefter positionen tilbage til nul.

  9. read() — Kanalens læsemetode bruges til at skrive data fra kanalen til bufferen, mens bufferens put()- metode bruges til at skrive data til bufferen.

  10. write() — Kanalens skrivemetode bruges til at skrive data fra bufferen til kanalen, mens bufferens get() metode bruges til at læse data fra bufferen.

  11. rewind() spoler bufferen tilbage. Denne metode bruges, når du skal genlæse bufferen - den sætter positionen til nul og ændrer ikke grænsen.

Og nu et par ord om kanaler.

De vigtigste kanalimplementeringer i Java NIO er følgende klasser:

  1. FileChannel — En kanal til at læse og skrive data fra/til en fil.

  2. DatagramChannel — Denne klasse læser og skriver data over netværket via UDP (User Datagram Protocol).

  3. SocketChannel — En kanal til læsning og skrivning af data over netværket via TCP (Transmission Control Protocol).

  4. ServerSocketChannel — En kanal til at læse og skrive data over TCP-forbindelser, ligesom en webserver gør. En SocketChannel oprettes for hver indgående forbindelse.

Øve sig

Det er tid til at skrive et par linjer kode. Lad os først læse filen og vise dens indhold på konsollen, og derefter skrive en streng til filen.

Koden indeholder en masse kommentarer - jeg håber, de vil hjælpe dig med at forstå, hvordan alt fungerer:


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

Prøv NIO API - du vil elske det!