Introduction to Java NIO

  1. Java NIO stands for Java non-blocking IO, a new API provided by the JDK. Starting with JDK1.4, a New set of improved input/output features, collectively known as NIO (so also called New IO), are synchronous and non-blocking.
  2. Nio-related classes are placed in the java.nio package and subpackages, and many of the classes in the original java.io package are overwritten.
  3. NIO has three core components:
    • Channel
    • Buffer
    • Selector
  4. NIO is buffer-oriented. Data is read into a buffer for its later processing and can be moved back and forth in the buffer as needed, which increases flexibility in processing and provides a highly scalable network that is non-blocking.
  5. Java NIO non-blocking mode, is a thread from one channel to send requests or read the data, but it can only get the currently available data, if there is no data are available, they will not get everything, rather than keep thread block, so until the data can be read before this thread can continue to do other things. Non-blocking writes also work the same way. A thread requests to write some data to a channel, but does not have to wait for it to write completely. The thread can do other things in the meantime.
  6. NIO can handle multiple operations on a single thread. Assuming 10,000 requests come in, 50 or 100 threads can be allocated to handle them, depending on the situation. Unlike the previous blocking IO, you had to allocate 10,000.
  7. HTTP2.0 uses multiplexing techniques to process multiple requests concurrently for the same connection, and the number of concurrent requests is orders of magnitude larger than HTTP1.1.

NIO versus BIO

  1. BIO processes data as a stream, while NIO processes data as a block, and block I/O is much more efficient than stream I/O.
  2. BIO is blocking, NIO is non-blocking.
  3. BIO operates based on byte streams and character streams, while NIO operates based on channels and buffers, where data is always read from a Channel into a Buffer or flushed into a Channel. A Selector listens for events on multiple channels (such as connection requests, data arrivals, etc.), so multiple client channels can be listened for using a single thread.

Java NIO schematic

As you can see from this figure, a Server can enable multiple threads, one thread holds a Selector object, one Selector object controls multiple channels, and there is only a Buffer between a Channel and a Client. So data reads and writes are buffer-oriented.

Buffer the Buffer

Basic introduction

Buffer: A Buffer is essentially a block of memory that can read and write data. It can be thought of as a container object (including an array) that provides a set of methods that make it easier to use a block of memory. A Buffer object has built-in mechanisms to track and record changes in the Buffer’s state. Channel Provides a Channel for reading data from a file or network. However, the data read or written must go through Buffer.

Under the java.nio package, Buffer is a top-level parent class, which is an abstract class. The class hierarchy is as follows:

There are seven classes that directly inherit from the Buffer class. These seven subclasses are the Buffer classes of the other seven data types except Boolean.

Each of the seven subclasses has an array of the corresponding data type. For example, IntBuffer has an array of type int:

final int[] hb;  
Copy the code

In the ByteBuffer class there is an array of type byte:

final byte[] hb;   
Copy the code

When using Buffer to read and write data, the underlying array is used to store data. However, data reading and writing are controlled by the following parameters in the parent Buffer:

// Invariants: Mark <= position <= limit <= capacity
private int mark = -1;
private int position = 0;
private int limit;
private int capacity;
Copy the code

The four attributes are described as follows:

attribute describe
Capacity Capacity: the maximum amount of data that can be held. The buffer is identified when it is created and cannot be changed
Limit Represents the current end of the buffer. No buffer beyond limit can be read or written, and limit can be modified
Position Position, the index of the next element to be read/written, and the value of position changes each time buffer data is read or written in preparation for the next read or write
Mark tag

Code sample

The following code can be used as an example, if you are interested in the IDE break point to see how the four parameters of the Buffer change step by step:

public static void main(String[] args) {
    // Create an IntBuffer instance with a capacity of 5
    IntBuffer buffer = IntBuffer.allocate(5);
    for (int i = 0; i < buffer.capacity(); i++) {
        // Each loop fills the buffer with a value of type int. After 5 cycles, the buffer should contain 5 numbers: 0, 2, 4, 6, and 8
        buffer.put(i * 2);
    }
    // The flip() method is called when converting buffer from writing to reading
    // The flip() method points limit to position and sets position to 0
    // Read back to where the flip() method was called
    buffer.flip();
    The hasRemaining() method indicates whether there are any remaining elements to read
    Position < limit to determine if there are any remaining elements
    while (buffer.hasRemaining()) {
        System.out.println(buffer.get());
    }
    // Set position to 1 and limit to 4
    buffer.position(1);
    buffer.limit(4);
    // Since you cannot read elements beyond limit and start reading from position, 2, 4, 6 will be printed
    while(buffer.hasRemaining()) { System.out.println(buffer.get()); }}Copy the code

Channel

Basic introduction

  1. NIO channels are like streams, but there are differences between the two:
    • Channels can read and write simultaneously, while streams can only read or write
    • Channels can read and write data asynchronously
    • A channel can read data from and write data to a buffer
  2. A STREAM in BIO is unidirectional. For example, a FileInputStream can only read data, while a Channel in NIO is bidirectional and can be read or written.
  3. A Channel is an interface in NIO.
  4. Common Channel classes are:FileChannel,DatagramChannel,ServerSocketChannel,SocketChannel.FileChannelFor reading and writing data to files,DatagramChannelFor UDP data read and write,ServerSocketChannelandSocketChannelData read and write for TCP.

Code sample

public static void main(String[] args) throws Exception {
    // Copy a random image from the desktop
    // Get the stream of the original image and the copied image path
    FileInputStream fileInputStream = new FileInputStream("/Users/connor/Desktop//64535234_p0.png");
    FileOutputStream fileOutputStream = new FileOutputStream("/Users/connor/Desktop//64535234_p0_1.png");
    // Get two channels through the stream's getChannel() method
    FileChannel fileInputStreamChannel = fileInputStream.getChannel();
    FileChannel fileOutputStreamChannel = fileOutputStream.getChannel();
    // Create a buffer of type byte and allocate 1024 length to it
    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
    // Each time the bytes of the image are read into the buffer, when the read returns -1, it is finished
    while (fileInputStreamChannel.read(byteBuffer) > -1) {
        // Call the flip() method to change from the read state to the write state
        byteBuffer.flip();
        // copy to write data from the buffer to the pipe
        fileOutputStreamChannel.write(byteBuffer);
        // Clear the buffer for the next read
        byteBuffer.clear();
    }
    // Close the Closeable object
    fileOutputStreamChannel.close();
    fileInputStreamChannel.close();
    fileOutputStream.close();
    fileInputStream.close();
}
Copy the code

You can copy files using the above methods, but FileChannel also has a method called transferTo/transferFrom that makes copying even faster:

public static void main(String[] args) throws IOException {
    // Copy a random image from the desktop
    // Get the stream of the original image and the copied image path
    FileInputStream fileInputStream = new FileInputStream("/Users/connor/Desktop//64535234_p0.png");
    FileOutputStream fileOutputStream = new FileOutputStream("/Users/connor/Desktop//64535234_p0_1.png");
    // Get two channels through the stream's getChannel() method
    FileChannel fileInputStreamChannel = fileInputStream.getChannel();
    FileChannel fileOutputStreamChannel = fileOutputStream.getChannel();
    // transferTo Similarly, called in fileInputStreamChannel
    fileOutputStreamChannel.transferFrom(fileInputStreamChannel,0,fileInputStreamChannel.size());
    // Close the Closeable object
    fileOutputStreamChannel.close();
    fileInputStreamChannel.close();
    fileOutputStream.close();
    fileInputStream.close();
}
Copy the code

Selector

Basic introduction

  1. Java NIO, with non-blocking IO. You can have one thread, handle multiple client connections, and use a Selector.
  2. Selector can detect whether an event has occurred on multiple registered channels, and if so, get the time and then handle each event accordingly. This allows you to manage multiple channels with a single thread, that is, multiple connections and requests.
  3. Reading and writing occurs only when a read or write event actually occurs on the connection channel, greatly reducing system overhead and eliminating the need to create a thread for each connection and maintain multiple threads. Avoids the overhead of context switching between multiple threads.

SelectionKey

SelectionKey is a Selector, if there’s a Channel registered, it generates a SelectionKey object, and in synchronous non-blocking, the Selector can use the SelectionKey to find the corresponding Channel and process it.

There are four types of selectionkeys in the Selector and Channel registry:

  • Int OP_ACCEPT: there are new network connections to accept, value 16 (1<<4)
  • Int OP_CONNECT: indicates that the connection has been established and the value is 8(1<<3)
  • Int OP_WRITE: represents a write operation and the value is 4(1<<2)
  • Int OP_READ: indicates a read operation and the value is 1(1<<0)

Code example: group chat system

Server:

public class GroupChatServer
{

   /** * defines channels, selectors, and ports */
   private ServerSocketChannel serverSocketChannel;
   private Selector selector;
   public static final int PORT = 6666;

   /** * Initializes the channel and selector in the constructor@throws IOException
    */
   public GroupChatServer(a) throws IOException
   {
      // Create a selector
      serverSocketChannel = ServerSocketChannel.open();
      // Set non-blocking
      serverSocketChannel.configureBlocking(false);
      // Bind ports
      serverSocketChannel.socket().bind(new InetSocketAddress(PORT));
      // Create a selector
      selector = Selector.open();
      // Register the channel with the selector and listen for the Accept event
      serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
   }

   /** **@throws IOException
    */
   private void listen(a) throws IOException
   {
      while (true)
      {
         int select = selector.select();
         // When an event occurs
         if (select > 0)
         {
            // Get the occurred key
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
            while (keyIterator.hasNext())
            {
               SelectionKey selectionKey = keyIterator.next();
               // When the listen event is Accept
               if (selectionKey.isAcceptable())
               {
                  // Accept this channel
                  SocketChannel acceptSocketChannel = serverSocketChannel.accept();
                  System.out.println("A client has been connected at:" + acceptSocketChannel.getRemoteAddress());
                  // Set to non-blocking
                  acceptSocketChannel.configureBlocking(false);
                  // Register this channel with the selector and listen for read events
                  acceptSocketChannel.register(selector, SelectionKey.OP_READ);
               }
               // When the listening event is read
               else if (selectionKey.isReadable())
               {
                  // Read the incoming messagereadMsg(selectionKey); }}// Remove key to avoid reuse
            keyIterator.remove();
         }
         else
         {
            System.out.println("Currently no message notification......"); }}}/** * Reads the message **@param selectionKey
    */
   private void readMsg(SelectionKey selectionKey)
   {
      SocketChannel channel = null;
      try
      {
         channel = (SocketChannel) selectionKey.channel();
         ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
         // Read into the buffer
         int read = channel.read(byteBuffer);
         // When there are messages in the buffer
         if (read > 0)
         {
            String msg = new String(byteBuffer.array());
            System.out.println("Client" + channel.getRemoteAddress() + "The message came:" + msg);
            // Forward to other clientssendToOtherClients(msg, channel); }}catch (IOException e)
      {
         try
         {
            System.err.println(channel.getRemoteAddress() + "Offline...");
            selectionKey.cancel();
            channel.close();
         }
         catch(IOException ioException) { ioException.printStackTrace(); }}}/** * forward to other clients **@param msg
    * @param selfChannel
    * @throws IOException
    */
   private void sendToOtherClients(String msg, SocketChannel selfChannel) throws IOException
   {
      System.out.println("Message forwarding......");
      // Get keys for all channels
      Set<SelectionKey> selectionKeys = selector.keys();
      Iterator<SelectionKey> iterator = selectionKeys.iterator();
      while (iterator.hasNext())
      {
         SelectionKey clientKey = iterator.next();
         Channel clientChannel = clientKey.channel();
         // Exclude the channel from which the message is currently sent
         if (clientChannel instanceofSocketChannel && clientChannel ! = selfChannel) { ((SocketChannel) clientChannel).write(ByteBuffer.wrap(("Client" + selfChannel.getRemoteAddress() + "Say:"+ msg).getBytes(StandardCharsets.UTF_8))); }}}public static void main(String[] args) throws IOException
   {
      GroupChatServer gcs = newGroupChatServer(); gcs.listen(); }}Copy the code

Client:

public class GroupChatClient
{

   /** * Defines channels, selectors, hosts, and ports */
   private SocketChannel socketChannel;
   private Selector selector;
   public static final int PORT = 6666;
   public static final String HOST = "127.0.0.1";

   /** * The constructor initializes the channel and selector@throws IOException
    */
   public GroupChatClient(a) throws IOException
   {
      // Create a selector
      socketChannel = SocketChannel.open(new InetSocketAddress(HOST, PORT));
      // Set non-blocking
      socketChannel.configureBlocking(false);
      // Create a selector
      selector = Selector.open();
      socketChannel.register(selector, SelectionKey.OP_READ);
      // Register the channel with the selector and listen for the Accept event
      System.out.println(socketChannel.getLocalAddress() + "Start connecting to server......");
   }

   /** * Send message **@param msg
    * @throws IOException
    */
   private void sendMsg(String msg) throws IOException
   {
      ByteBuffer byteBuffer = ByteBuffer.wrap(msg.getBytes(StandardCharsets.UTF_8));
      socketChannel.write(byteBuffer);
   }

   /** * accept message */
   private void acceptMsg(a)
   {
      while (true)
      {
         try
         {
            int select = selector.select();
            // When an event occurs
            if (select > 0)
            {
               // Get iterators for all events that occur
               Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
               while ((iterator.hasNext()))
               {
                  SelectionKey key = iterator.next();
                  // When the event is a read event
                  if (key.isReadable())
                  {
                     SocketChannel channel = (SocketChannel) key.channel();
                     ByteBuffer allocate = ByteBuffer.allocate(1024);
                     // Read the message
                     int read = channel.read(allocate);
                     if (read > 0)
                     {
                        System.out.println(newString(allocate.array())); } } iterator.remove(); }}}catch(Exception e) { e.printStackTrace(); }}}public static void main(String[] args) throws IOException
   {
      GroupChatClient gcc = new GroupChatClient();
      // Start a thread that reads messages
      new Thread(() -> {
         gcc.acceptMsg();
      }).start();
      Scanner scanner = new Scanner(System.in);
      while(scanner.hasNextLine()) { gcc.sendMsg(scanner.nextLine()); }}}Copy the code