Wcześniej spotkaliśmy się z IO API (Input & Output Application Programming Interface) oraz pakietem java.io , w którego klasach skoncentrowana jest główna funkcjonalność pracy ze strumieniami w Javie. Kluczem jest tu koncepcja strumienia .

Dzisiaj zaczniemy przyglądać się NIO API (New Input & Output).

Główna różnica między tymi dwoma podejściami we/wy polega na tym, że interfejs API IO jest zorientowany na strumień, podczas gdy interfejs API NIO jest zorientowany na bufor. Głównymi pojęciami w tym przypadku są pojęcia bufora ( bufor ) i kanału ( kanał ).

Co to jest bufor i kanał?

Kanał to logiczny portal, za pośrednictwem którego odbywa się wejście/wyjście danych, a bufor to źródło lub miejsce docelowe tych transmitowanych danych. Podczas wysyłania dane, które chcesz wysłać, są buforowane i przekazywane do potoku. Na wejściu dane z kanału są buforowane.

Innymi słowy:

  • bufor to po prostu blok pamięci, w którym możemy zapisywać informacje iz którego możemy odczytywać informacje,
  • kanał to brama, która umożliwia dostęp do urządzeń we/wy, takich jak plik lub gniazdo.

Kanały są bardzo podobne do strumieni w pakiecie java.io. Wszystkie dane, które trafiają gdziekolwiek (lub pochodzą z dowolnego miejsca) muszą przechodzić przez obiekt kanału. Ogólnie rzecz biorąc, aby korzystać z systemu NIO, otrzymujesz kanał do urządzenia wejścia/wyjścia oraz bufor do przechowywania danych. Następnie pracujesz z buforem, wprowadzając lub wyprowadzając dane w razie potrzeby.

Możesz poruszać się do przodu i do tyłu wzdłuż bufora, czyli „chodzić” po nim, czego nie mogłeś robić w strumieniach. Daje to większą elastyczność w przetwarzaniu danych. W bibliotece standardowej bufor jest reprezentowany przez abstrakcyjną klasę Buffer i jej wielu następców:

  • ByteBuffer
  • CharBuffer
  • Krótki Bufor
  • IntBuffer
  • FloatBuffer
  • Podwójny bufor
  • Długi Bufor

Główną różnicą między spadkobiercami jest typ danych, który będą przechowywać - byte , int , long i inne prymitywne typy danych.

Właściwości bufora

Bufor ma cztery główne właściwości. Są to pojemność, limit, pozycja i znacznik.

Pojemność ( Pojemność ) - maksymalna ilość danych/bajtów, które mogą być przechowywane w buforze. Nie można zmienić pojemności bufora . Po zapełnieniu bufora należy go wyczyścić przed zapisem do niego.

Limit ( Limit ) - W trybie zapisu bufora Limit jest równy pojemności, która wskazuje maksymalną ilość danych, które można zapisać w buforze. W trybie odczytu bufora Limit odnosi się do maksymalnej ilości danych, które można odczytać z bufora.

Position ( Position ) - wskazuje aktualną pozycję kursora w buforze. Początkowo ustawiona na 0 podczas tworzenia bufora. Innymi słowy, jest to indeks elementu, który ma zostać odczytany lub zapisany.

Marker ( Mark ) - służy do zaznaczania aktualnej pozycji kursora. W procesie manipulowania buforem pozycja kursora ciągle się zmienia, ale zawsze możemy przywrócić go do wcześniej zaznaczonej pozycji.

Metody buforowe

Przyjrzyjmy się teraz głównemu zestawowi metod, które pozwalają nam pracować z naszym buforem (blokiem pamięci) do odczytu i zapisu danych do iz kanałów.

  1. allocate(int pojemność) — Metoda służy do przydzielenia nowego bufora z pojemnością jako parametrem. Metoda allocate() zgłasza wyjątek IllegalArgumentException , jeśli przekazana pojemność jest ujemną liczbą całkowitą.

  2. Capacity() - zwraca pojemność ( pojemność ) bieżącego bufora.

  3. position() - zwraca aktualną pozycję kursora. Obie operacje odczytu i zapisu przesuwają kursor na koniec bufora. Wartość zwracana jest zawsze mniejsza lub równa limitowi.

  4. limit() - zwraca limit bieżącego bufora.

  5. mark() - służy do zaznaczania (zaznaczania) aktualnej pozycji kursora.

  6. reset() - przywróci kursor do poprzednio zaznaczonej (zaznaczonej) pozycji.

  7. clear() - ustawia pozycję na zero i ogranicza ją do pojemności. W tej metodzie dane w buforze nie są czyszczone , tylko ponownie inicjowane są znaczniki.

  8. flip() - Przełącza tryb buforowania z trybu zapisu na tryb odczytu. Ustawia również pozycję z powrotem na zero i ustala limit, w którym pozycja znajdowała się w momencie pisania.

  9. read() to metoda odczytu kanału używana do zapisywania danych z kanału do bufora, a put() to metoda bufora, która służy do zapisywania danych do bufora.

  10. write() - Metoda write kanału służy do zapisu danych z bufora do kanału, natomiast get() to metoda buffer, która służy do odczytu danych z bufora.

  11. rewind() - metoda przewijania do tyłu. Używany, gdy wymagany jest ponowny odczyt, ponieważ ustawia pozycję na zero i nie zmienia wartości granicznej.

A teraz - trochę o kanale.

Najważniejszymi implementacjami kanałów w Javie NIO są następujące klasy:

  1. FileChannel to kanał do odczytu i zapisu danych do pliku.

  2. DatagramChannel - odczytuje i zapisuje dane przez sieć poprzez UDP (User Datagram Protocol).

  3. SocketChannel to kanał do odczytu i zapisu danych przez sieć za pośrednictwem protokołu TCP (Transmission Control Protocol).

  4. ServerSocketChannel to kanał do odczytu i zapisu danych przez połączenia TCP, tak jak robi to serwer WWW. Dla każdego połączenia przychodzącego tworzony jest kanał SocketChannel .

Ćwiczyć

Czas napisać kilka linijek kodu. Najpierw wczytajmy plik i wyświetlmy jego zawartość w konsoli, a następnie wpiszmy do pliku dowolną linię, którą stworzyliśmy.

Kod zawiera dużo komentarzy, mam nadzieję, że pomogą Ci zrozumieć jak to wszystko działa:


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

Wypróbuj NIO API , a pokochasz to!