Skip to content

Java NIO

nhan.nguyen edited this page Sep 27, 2023 · 29 revisions

Overview

  • Java IO
    • Java IO use byte streams, character stream for IO operations.
    • Disadvantages:
      • Blocking: The read() and write() methods won't return results until they finish reading or writing.
  • Java has provided a second I/O system called NIO (New I/O). Java NIO provides the different way of working with I/O than the standard I/O API's. It is an alternate I/O API for Java.
    • Non-blocking IO operation: Java NIO performs non-blocking IO operations. This means that it reads the data whichever is ready. For instance, a thread can ask a channel to read the data from a buffer and the thread can go for other work during that period and continue again from the previous point where it has left. In the meantime, the reading operation is complete which increases the overall efficiency.
    • Buffer oriented approach: Java NIO’s buffer oriented approach allows us to move forth and back in the buffer as we need. The data is read into a buffer and cached there. Whenever the data is required, it is further processed from the buffer.

Components of Java NIO

  • Channels and Buffers: In standard I/O API the character streams and byte streams are used. In NIO we work with channels and buffers. Data is always written from a buffer to a channel and read from a channel to a buffer.

nio-tutorial7

  • Selectors: It is an object that can be used for monitoring the multiple channels for events like data arrived, connection opened etc. Therefore single thread can monitor the multiple channels for data.

nio-tutorial8

  • Non-blocking I/O: Java NIO enables you to do non-blocking IO. For instance, a thread can ask a channel to read data into a buffer. While the channel reads data into the buffer, the thread can do something else. Once data is read into the buffer, the thread can then continue processing it. The same is true for writing data to channels.

NIO Channel

  • Some characteristics of channels:

    • You can both read and write to a Channels. Streams are typically one-way (read or write).
    • Channels can be read and written asynchronously.
    • Channels always read to, or write from, a Buffer.
  • The class hierarchy for java.nio.channels:

nio-tutorial9

  • Channel Implementations
    • FileChannel: reads data from and to files.
    • DatagramChannel: can read and write data over the network via UDP (User Datagram Protocol).
    • SocketChannel: can read and write data over the network via TCP (Transmission Control Protocol).
    • ServerSocketChannel: allows you to listen for incoming TCP connections, like a web server does. For each incoming connection a SocketChannel is created.

NIO Buffer

  • Java NIO Buffers are used when interacting with NIO Channels. As you know, data is read from channels into buffers, and written from buffers into channels.
  • A buffer is essentially a block of memory into which you can write data, which you can then later read again. This memory block is wrapped in a NIO Buffer object, which provides a set of methods that makes it easier to work with the memory block.
  • Java NIO provides a separate type of buffer for each primitive type.

java-nio-buffer

  • A Buffer has three properties you need to be familiar with, in order to understand how a Buffer works. These are:

    • capacity: Capacity refers to the total size of the buffer and always remains constant.
    • position: The position determines the index from where reading or writing to a buffer should be initiated.
    • limit: The limit determines how many elements you can read or write from the buffer.
  • Methods

    • allocate(capacity)

    • Wrapping:

      • Wrapping allows an instance to reuse an existing byte array

      byte[] bytes = new byte[10]; ByteBuffer buffer = ByteBuffer.wrap(bytes);

      • Any changes made to the data elements in the existing byte array will be reflected in the buffer instance, and vice versa.
    • Writing Data to a Buffer:

      • Write data from a Channel into a Buffer. Ex int bytesRead = inChannel.read(buf)
      • Write data into the Buffer yourself, via the buffer's put() methods. Ex: buf.put(127)
    • flip(): switches a Buffer from writing mode to reading mode.

    • Reading Data from a Buffer:

      • Read data from the buffer into a channel. Ex: int bytesWritten = inChannel.write(buf)
      • Read data from the buffer yourself, using one of the get() methods. Ex: byte aByte = buf.get()
    • clear() and compact():

      • Once you are done reading data out of the Buffer you have to make the Buffer ready for writing again. You can do so either by calling clear() or by calling compact().
      • clear() the position is set back to 0 and the limit to capacity. In other words, the Buffer is cleared. The data in the Buffer is not cleared. Only the markers telling where you can write data into the Buffer are. If there is any unread data in the Buffer when you call clear() that data will be "forgotten", meaning you no longer have any markers telling what data has been read, and what has not been read.
      • compact() copies all unread data to the beginning of the Buffer. Then it sets position to right after the last unread element. The limit property is still set to capacity, just like clear() does. Now the Buffer is ready for writing, but you will not overwrite the unread data.
    • Remain:

ByteBuffer-remain

Example Channel, Buffer

Screen Shot 2023-09-20 at 14 30 17

NIO Scatter / Gather

  • Scatter: A "scattering read" reads data from a single channel into multiple buffers
  • Gather: A "gathering write" writes data from multiple buffers into a single channel.

Ex: Screen Shot 2023-09-20 at 14 35 00

NIO Data transfer

  • In Java NIO you can transfer data directly from one channel to another, if one of the channels is a FileChannel. The FileChannel class has a transferTo() and a transferFrom() method which does this for you.
  • transferFrom(ReadableByteChannel src, long position, long count); method allows the data transfer from a source channel into the FileChannel.
  • transferTo (long position, long count, WritableByteChannel target); method allows the data transfer from a FileChannel into some other channel.

Charset

  • Java NIO package defines an abstract class named as Charset which is mainly used for encoding and decoding of charset and UNICODE.
  • Standard charsets:
    • US-ASCII − Seven bit ASCII characters.
    • ISO-8859-1 − ISO Latin alphabet.
    • UTF-8 − This is 8 bit UCS transformation format.
    • UTF-16BE − This is 16 bit UCS transformation format with big endian byte order.
    • UTF-16LE − This is 16 bit UCS transformation with little endian byte order.
    • UTF-16 − 16 bit UCS transformation format.
  • Methods:
    • forName() − This method creates a charset object for the given charset name.The name can be canonical or an alias.
    • displayName() − This method returns the canonical name of given charset.
    • canEncode() − This method checks whether the given charset supports encoding or not.
    • decode() − This method decodes the string of a given charset into charbuffer of Unicode charset.
    • encode() − This method encodes charbuffer of unicode charset into the byte buffer of given charset.

Charset Classes

  • CharsetDecoder An engine that can transform a sequence of bytes in a specific charset into a sequence of sixteen-bit Unicode characters.
  • CharsetEncoder An engine that can transform a sequence of sixteen-bit Unicode characters into a sequence of bytes in a specific charset.

Example

Screen Shot 2023-09-26 at 23 22 34

Path, Files

Path interface

  • A Java Path instance represents a path in the file system. A path can point to either a file or a directory. A path can be absolute or relative.
  • java.nio.file.Path interface is similar to the java.io.File class

Files class

  • This class consists exclusively of static methods that operate on files, directories, or other types of files.
  • basically using its static methods which mostly works on Path object.

Java IO File vs Java NIO Path

  • Error Handling
    • IO: many methods don’t tell us any details about the encountered problem or even throw exceptions at all.
    • NIO: the compiler requires us to handle an IOException
  • pathname
    • IO: system-dependent prefix string
    • NIO: provider static method to handle prefix

NIO Selector

  • Selector is a component which can examine one or more Channel instances, and determine which channels are ready for e.g. reading or writing.

selector

  • The advantage of using just a single thread to handle multiple channels is that you need less threads to handle the channels.
  • Any channel we register with a selector must be a sub-class of SelectableChannel. These are a special type of channels that can be put in non-blocking mode.

How to use Selector?

  • When register a channel to selector, what events you are interested in listening for in the Channel. There are four different events you can listen for:

    • Connect – when a client attempts to connect to the server. Represented by SelectionKey.OP_CONNECT
    • Accept – when the server accepts a connection from a client. Represented by SelectionKey.OP_ACCEPT
    • Read – when the server is ready to read from the channel. Represented by SelectionKey.OP_READ
    • Write – when the server is ready to write to the channel. Represented by SelectionKey.OP_WRITE
  • Selecting Channels via a Selector:

    • select a channel which is ready for the events we want to perform i.e. connect, read, write or accept.
    • methods:
      • int select() - blocks until at least one channel is ready for the events you registered for.
      • int select(long timeout) - does the same as select() except it blocks for a maximum of timeout milliseconds (the parameter).
      • int selectNow() - doesn't block at all. It returns immediately with whatever channels are ready.
  • Once we have called any one of the select() methods and it will return a value as indicated that one or more channels are ready, then we can access the ready channels by using the selected key set