По-рано се запознахме с IO API (Input/Output Application Programming Interface) и пакета java.io , чиито класове са основно за работа с потоци в Java. Ключът тук е концепцията за поток .

Днес ще започнем да разглеждаме NIO API (нов вход/изход).

Основната разлика между двата подхода към I/O е, че IO API е ориентиран към поток, докато NIO API е ориентиран към буфер. Така че основните понятия за разбиране са буфери и канали .

Какво е буфер и Howво е канал?

Каналът е логически портал, през който данните влизат и излизат, докато буферът е източникът or дестинацията на тези предадени данни . По време на изхода данните, които искате да изпратите, се поставят в буфер и буферът предава данните към канала. По време на въвеждане данните от канала се поставят в буфера.

С други думи:

  • буферът е просто блок от паметта , в който можем да записваме информация и от който можем да четем информация,
  • каналът е шлюз, който осигурява достъп до I/O устройства като файлове or сокети .

Каналите са много подобни на потоците в пакета java.io. Всички данни, които отиват навсякъде (or идват отвсякъде), трябва да преминат през обект на канал. Като цяло, за да използвате системата NIO, вие получавате канал към I/O обект и буфер за съхраняване на данни. След това работите с буфера, като въвеждате or извеждате данни според нуждите.

Можете да се движите напред и назад в буфер, т.е. да "разхождате" буфера, което е нещо, което не можете да правите в потоци. Това дава повече гъвкавост при обработката на данни. В стандартната библиотека буферите са представени от абстрактния клас Buffer и няколко от неговите наследници:

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

Основната разлика между подкласовете е типът данни, който съхраняват - byteове , ints , дълги и други примитивни типове данни.

Буферни свойства

Буферът има четири основни свойства. Това са капацитет, лимит, позиция и марка.

Капацитетът е максималното количество данни/byteове, които могат да се съхраняват в буфера. Капацитетът на буфера не може да се променя . След като буферът е пълен, той трябва да бъде изчистен, преди да пишете повече в него.

В режим на запис лимитът на буфера е същият като капацитета му, което показва максималното количество данни, които могат да бъдат записани в буфера. В режим на четене ограничението на буфера се отнася до максималното количество данни, които могат да бъдат прочетени от буфера.

Позицията показва текущата позиция на курсора в буфера. Първоначално е зададено на 0, когато буферът е създаден. С други думи, това е индексът на следващия елемент, който трябва да бъде прочетен or написан.

Знакът се използва за запазване на позиция на курсора . Докато манипулираме буфер, позицията на курсора се променя постоянно, но винаги можем да го върнем на предварително маркираната позиция.

Методи за работа с буфер

Сега нека да разгледаме основния набор от методи, които ни позволяват да работим с нашия буфер (блок памет) за четене и запис на данни към и от канали.

  1. allocate(int capacity) — този метод се използва за разпределяне на нов буфер с посочения капацитет. Методът allocate() хвърля IllegalArgumentException , ако предаденият капацитет е отрицателно цяло число.

  2. капацитет() връща капацитета на текущия буфер .

  3. position() връща текущата позиция на курсора. Операциите за четене и запис преместват курсора в края на буфера. Връщаната стойност винаги е по-малка or равна на ограничението.

  4. limit() връща ограничението на текущия буфер.

  5. mark() се използва за маркиране (запазване) на текущата позиция на курсора.

  6. reset() връща курсора на предварително маркираната (запаметена) позиция.

  7. clear() задава позицията на нула и задава лимита на капацитета. Този метод не изчиства данните в буфера. Той само реинициализира позицията, границата и маркировката.

  8. flip() превключва буфера от режим на запис в режим на четене. Той също така задава лимита на текущата позиция и след това я връща на нула.

  9. read() — Методът за четене на канала се използва за запис на данни от канала в буфера, докато методът put() на буфера се използва за запис на данни в буфера.

  10. write() — Методът за запис на канала се използва за запис на данни от буфера в канала, докато методът get() на буфера се използва за четене на данни от буфера.

  11. rewind() пренавива буфера. Този метод се използва, когато трябва да препрочетете буфера - той задава позицията на нула и не променя лимита.

И сега няколко думи за каналите.

Най-важните реализации на канали в Java NIO са следните класове:

  1. FileChannel — Канал за четене и запис на данни от/към файл.

  2. DatagramChannel — Този клас чете и записва данни през мрежата чрез UDP (Протокол за потребителски дейтаграми).

  3. SocketChannel — канал за четене и запис на данни през мрежата чрез TCP (протокол за контрол на предаването).

  4. ServerSocketChannel — канал за четене и запис на данни през TCP връзки, точно Howто прави уеб сървър. За всяка входяща връзка се създаваSocketChannel

Практикувайте

Време е да напишем няколко реда code. Първо, нека прочетем file и да покажем съдържанието му на конзолата и след това да напишем няHowъв низ във file.

Кодът съдържа много коментари — надявам се те да ви помогнат да разберете How работи всичко:


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

Опитайте NIO API — ще ви хареса!