Tidigare har vi lärt känna IO API (Input/Output Application Programming Interface) och java.io -paketet, vars klasser främst är för att arbeta med strömmar i Java. Nyckeln här är konceptet med en ström .

Idag ska vi börja överväga NIO API (New Input/Output).

Huvudskillnaden mellan de två tillvägagångssätten för I/O är att IO API är strömorienterat medan NIO API är buffertorienterat. Så de viktigaste begreppen att förstå är buffertar och kanaler .

Vad är en buffert och vad är en kanal?

En kanal är en logisk portal genom vilken data rör sig in och ut, medan en buffert är källan eller destinationen för denna överförda data. Under utmatning läggs data du vill skicka i en buffert, och bufferten skickar data till kanalen. Under inmatning läggs data från kanalen in i bufferten.

Med andra ord:

  • en buffert är helt enkelt ett minnesblock i vilket vi kan skriva information och från vilket vi kan läsa information,
  • en kanal är en gateway som ger åtkomst till I/O-enheter som filer eller uttag.

Kanaler är väldigt lika strömmar i java.io-paketet. All data som går någonstans (eller kommer från var som helst) måste passera genom ett kanalobjekt. I allmänhet, för att använda NIO-systemet, får du en kanal till en I/O-enhet och en buffert för att lagra data. Sedan arbetar du med bufferten, matar in eller matar ut data efter behov.

Man kan röra sig framåt och bakåt i en buffert, dvs "gå" bufferten, vilket är något man inte kunde göra i bäckar. Detta ger mer flexibilitet vid behandling av data. I standardbiblioteket representeras buffertar av den abstrakta buffertklassen och flera av dess ättlingar:

  • ByteBuffer
  • CharBuffer
  • ShortBuffer
  • IntBuffer
  • FloatBuffer
  • Dubbelbuffert
  • LongBuffer

Huvudskillnaden mellan underklasserna är datatypen de lagrar - bytes , ints , longs och andra primitiva datatyper.

Buffertegenskaper

En buffert har fyra huvudegenskaper. Dessa är kapacitet, gräns, position och markering.

Kapacitet är den maximala mängden data/byte som kan lagras i bufferten. En bufferts kapacitet kan inte ändras . När en buffert är full måste den rensas innan du skriver mer till den.

I skrivläge är en bufferts gräns densamma som dess kapacitet, vilket indikerar den maximala mängden data som kan skrivas till bufferten. I läsläge hänvisar en bufferts gräns till den maximala mängd data som kan läsas från bufferten.

Positionen indikerar den aktuella positionen för markören i bufferten . Initialt sätts den till 0 när bufferten skapas. Det är med andra ord indexet för nästa element som ska läsas eller skrivas.

Markeringen används för att spara en markörposition . När vi manipulerar en buffert ändras markörpositionen konstant, men vi kan alltid återställa den till den tidigare markerade positionen.

Metoder för att arbeta med en buffert

Låt oss nu titta på huvuduppsättningen av metoder som låter oss arbeta med vår buffert (minnesblock) för att läsa och skriva data till och från kanaler.

  1. allocate(int kapacitet) — denna metod används för att allokera en ny buffert med angiven kapacitet. Metoden allocate() kastar ett IllegalArgumentException om den godkända kapaciteten är ett negativt heltal.

  2. kapasiteit() returnerar den aktuella buffertens kapacitet .

  3. position() returnerar den aktuella markörpositionen. Läs- och skrivoperationer flyttar markören till slutet av bufferten. Returvärdet är alltid mindre än eller lika med gränsen.

  4. limit() returnerar den aktuella buffertens gräns.

  5. mark() används för att markera (spara) den aktuella markörpositionen.

  6. reset() returnerar markören till den tidigare markerade (sparade) positionen.

  7. clear() sätter positionen till noll och sätter gränsen för kapaciteten. Denna metod rensar inte data i bufferten. Den återinitierar bara positionen, gränsen och markeringen.

  8. flip() växlar bufferten från skrivläge till läsläge. Den sätter också gränsen för den aktuella positionen och sätter sedan tillbaka positionen till noll.

  9. read() — Kanalens läsmetod används för att skriva data från kanalen till bufferten, medan buffertens put()- metod används för att skriva data till bufferten.

  10. write() — Kanalens skrivmetod används för att skriva data från bufferten till kanalen, medan buffertens get()- metod används för att läsa data från bufferten.

  11. rewind() spolar tillbaka bufferten. Den här metoden används när du behöver läsa om bufferten — den sätter positionen till noll och ändrar inte gränsen.

Och nu några ord om kanaler.

De viktigaste kanalimplementeringarna i Java NIO är följande klasser:

  1. FileChannel — En kanal för att läsa och skriva data från/till en fil.

  2. DatagramChannel — Denna klass läser och skriver data över nätverket via UDP (User Datagram Protocol).

  3. SocketChannel — En kanal för att läsa och skriva data över nätverket via TCP (Transmission Control Protocol).

  4. ServerSocketChannel — En kanal för att läsa och skriva data över TCP-anslutningar, precis som en webbserver gör. En SocketChannel skapas för varje inkommande anslutning.

Öva

Det är dags att skriva ett par rader kod. Låt oss först läsa filen och visa dess innehåll på konsolen, och sedan skriva en sträng till filen.

Koden innehåller många kommentarer — jag hoppas att de hjälper dig att förstå hur allt fungerar:


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

Prova NIO API - du kommer att älska det!