The article directories

      • 1. Overview
      • 2. Channel
      • 3. Buffer
      • 4. Scater, Gather
      • 5. Selector
      • 6. Reference

1. Overview

NIO in Java consists of three core concepts:

  • Channels:
  • -Dan: This is my Buffers.
  • Selectors: indicates the Selectors

There are, of course, many other classes and components in NIO, but the three above are at the heart of all the other implementations, which are more like utility classes that glue Channels, Buffers, and Selectors together. A Channel can be regarded as a stream. The program can read data from a Channel and write it to a Buffer, or read data from a Buffer and write it to a Channel.


The main types of channels in NIO are network IO and file IO, and the corresponding implementation classes are as follows:

  • FileChannel
  • DatagramChannel
  • SocketChannel
  • ServerSocketChannel

The main implementations of Buffer in NIO are:

  • ByteBuffer
  • CharBuffer
  • DoubleBUffer
  • FloatBuffer
  • IntBuffer
  • LongBUffer
  • ShortBuffer

It provides implementation classes for different types of data in Java.

A Selector allows a single line to process multiple channels at the same time. It is mainly used when an application has multiple connections, but each connection transmits very little data. Using a Selector can reduce resource consumption and improve processing efficiency. For example, here is a Selector for three channels:


Selector can manage all registered channels by calling the Select method, which blocks until an event occurs for one of the channels. When the event is returned, the corresponding thread of Selector can process the returned event.


2. Channel

A Channel is a Stream. A Channel is a Stream.

  • A Channel is bidirectional. Data can be written to or read from a Channel. Streams are usually one-way, such as an input stream or an output stream
  • A Channel can read and write data asynchronously
  • A Channel is usually used with a Buffer to read and write data

Common implementation classes for channels are:

  • FileChannel: Reads data from a file
  • DatagramChannel: Supports reading and writing data over UDP
  • SocketChannel: Reads and writes data over TCP
  • ServerSocketChannel: TCP connection that supports listening for connections. Each connection corresponds to a SocketChannel

For example, we use FileChannel to read the contents of a file as follows:

/ * * *@Author dyliang
 * @Date2020/10/25 and *@Version1.0 * /
public class ChannelDemo {
    public static void main(String[] args) throws IOException {
        RandomAccessFile file = new RandomAccessFile("test.txt"."rw");
        FileChannel channel = file.getChannel();

        ByteBuffer buffer = ByteBuffer.allocate(48);
        int bytesRead = channel.read(buffer);

        while(bytesRead ! = -1){
            System.out.println("Read " + bytesRead);
            buffer.flip();
            while(buffer.hasRemaining()){
                System.out.println((char) buffer.get()); } buffer.clear(); bytesRead = channel.read(buffer); System.out.println(bytesRead); } file.close(); }}Copy the code

The above example implements data transfer between Buffer and Channel. In addition, Channel also provides transferTo methods and transferFrom methods for direct data transfer between channels. Such as:

RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt"."rw");
FileChannel fromChannel = fromFile.getChannel();

RandomAccessFile toFile = new RandomAccessFile("toFile.txt"."rw");
FileChannel toChannel = toFile.getChannel();

long position = 0;
long count = fromChannel.size();

toChannel.transferFrom(fromChannel, position, count);
Copy the code

In addition, some SocketChannel implementations may only transmit data that the SocketChannel has already prepared in its internal buffer, even though the SocketChannel may have more data available later. Therefore, it may not transfer all of the requested data (count) from SocketChannel to FileChannel.

The transferTo method is used in the same way as the transferFrom method above, except that the form of the method call is different:

fromChannel.transferTo(position, count, toChannel);
Copy the code

Similarly, when SocketChannel calls the transferTo method, it can only transfer data that matches its buffer capacity.


3. Buffer

A Buffer is usually used in conjunction with a Channel. Data can be written from a Buffer to a Channel, and data can be written from a Channel to a Buffer. A Buffer is essentially a piece of memory that can read and write data. It is wrapped as a Buffer object in NIO, and provides a series of methods that make it easy for programs to manipulate this part of memory.

There are four main steps to read and write data through Buffer:

  • Writes data to Buffer
  • callbuffer.flip()methods
  • Read data from Buffer
  • callbuffer.clear()orbuffer.compact()To reclaim buffer space

As the program writes data to Buffer, Buffer keeps track of how much data was written. When you need to read data from the Buffer, you call the flip method to convert the Buffer from write mode to read mode, and then you can read all the data in the Buffer. In addition, once the data has been read, the program needs to clear the buffer for subsequent writing of other data. NIO provides two ways to do this:

  • The clear method clears the entire buffer space
  • Compact: The compact method clears only the memory occupied by the data that has already been read. The remaining data that has not been read is moved to the start of the buffer, and subsequent data written to it is saved after it

Such as:

ByteBuffer buffer = ByteBuffer.allocate(48);
int bytesRead = channel.read(buffer);   // Write data to buffer

while(bytesRead ! = -1){
    System.out.println("Read " + bytesRead);
    buffer.flip();   // Switch to read mode
    while(buffer.hasRemaining()){
        System.out.println((char) buffer.get());   // Get data
    }

    buffer.clear();   // Clear the buffer spaceCopy the code

A Buffer is essentially a block of memory that can be read and written repeatedly. To understand how it uses memory for reading and writing, you need to understand the following three concepts:

  • Capacity: indicates the total length of the buffer. If the buffer is full and data needs to be written, clear the buffer before writing data
  • position: position, the position of the next data element to be manipulated. The starting position is 0 and moves backwards as data is written, up to a maximumcapacity - 1. When data is read from buffer, position is reset back to 0, recording the position from which data is to be read
  • limit: The position of the next non-operable element in the buffer, used to limit the amount of data that can be written or read by a programlimit <=capacity

The amount of data read cannot exceed the amount of data written.

Common implementations of Buffer are:

  • ByteBuffer
  • CharBuffer
  • DoubleBUffer
  • FloatBuffer
  • IntBuffer
  • LongBUffer
  • ShortBuffer

You can choose the corresponding implementation for different types of data. The common methods of Buffer are:

The method name describe
allocate Buf = bytebuffer.allocate (48); allocate(48); allocate(48).
Writes data to Buffer Nio provides two ways to write data to buffer: – Write data from a Channel, e.g. Int bytesRead = inchannel.read (buf) – self-write: buf.put(11)
flip Convert Buffer from write mode to read mode, reset position back to 0, and limit marks the amount of data written
Read data from Buffer Nio provides two methods to read data from Buffer: – Read data write Channel: int WriteCount = channel.write(buf) – Self read: buf.get()
rewind Used to reset position to 0 to facilitate re-reading of data
clear Clear the Buffer memory space
compact It only emptens the memory space occupied by data that has already been read. The remaining data that has not been read is moved to the start of the buffer, and subsequent data written to it is saved after it
mark Marks a given position in the buffer
reset Resets the position back to the position of the marker
equals Compare the contents of two buffers. The contents of two buffers must meet the following requirements: – Same type – Same amount of remaining data – Same amount of remaining data
compareTo In lexicographic order, it returns a negative integer, 0, and positive integer, respectively, if the buffer arguments are less than, equal to, or greater than the object instance referencing compareTo()

When the clear method and compact method clear the Buffer, it does not mean that the data in the Buffer is cleared. The original data is effectively forgotten, and the program cannot read the data from the buffer again. Subsequent new data is written directly over the old data. This refers to another concept, mark, which is used to record the position preceding the current position or -1 by default. When the clear method is called to clear the buffer, it actually does the following:

public final Buffer clear(a) {
    position = 0;  // Set position to 0 to write data directly from the beginning next time
    limit = capacity;  // Limit is the same as capacity
    mark = -1;   // set mark to -1
    return this;
}
Copy the code

4. Scater, Gather

Nio provides support for Scatter and Gather, which are used to write data to or read data from a Channel, for scenarios where multiple parts of data need to be processed separately. Scatter is used to write data from a Channel to multiple Buffers. Gather is used to write data from multiple buffers to a Channel.

Scatter reads data from a single Channel into multiple buffers, as shown below:


For example, write data from a Channel to two Bytebuffers:

ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body   = ByteBuffer.allocate(1024);

ByteBuffer[] bufferArray = { header, body };

channel.read(bufferArray);
Copy the code

Scatter writes data to the buffers in array order. When the buffers are full, it moves to another buffer to perform the writes. It is not suitable for dynamically adjusting the size of the data to be written.

The Gather is used to write data from multiple buffers to a Channel, as shown below:


Such as:

ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body   = ByteBuffer.allocate(1024);

//write data into buffers

ByteBuffer[] bufferArray = { header, body };

channel.write(bufferArray);
Copy the code

At this point, only data is written between the position of the buffer and the limit. For example, if the buffer has a size of 128 bytes but contains only 58 bytes, only 58 bytes are written from that buffer to a Channel.


5. Selector

Selector supports a single thread to manage multiple channels. It is used to detect which managed channels are ready to perform reads or writes, providing a mechanism for IO multiplexing. If channels are not managed using Selector, then each Channel needs a separate thread. Threads require a certain amount of resources, and thread context switching is expensive. As a result, Selector reduces the use of threads by supporting a single thread to manage multiple channels, taking advantage of the multi-core capabilities of hardware and thereby reducing the performance overhead.


Now, let’s go through the program and see how we use Selector. First, create a Selector:

Selector selector = Selector.open();
Copy the code

Call the register method to register the Channel to be managed with the Selector:

channel.configureBlocking(false); // Channel must be of non-blocking type

SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
Copy the code

The last parameter to the register method is the Interest set, which indicates that the program wants to listen for the type of event of interest occurring in the Channel via Selector. It has four values:

  • Connect
  • Accept
  • Read
  • Write

If a Channel successfully connects to the server, it is called Connect Ready; If the server socket channel receives a connection, it is called Accept Ready. If a Channel is ready to read data, it is said to be read ready. If a Channel is ready to write data, it is called write ready. The four values of SelectionKey represent the four events described above:

  • SelectionKey.OP_CONNECT
  • SelectionKey.OP_ACCEPT
  • SelectionKey.OP_READ
  • SelectionKey.OP_WRITE

If you are interested in a variety of types of events, you can use |. The register method returns a SelectionKey object, which contains the following information:

  • interest set
  • ready set
  • Channel
  • Selector
  • an attached object

Where, interest set is the event bound to be monitored when the register method is called. The corresponding event label can be obtained through interestOps method, as shown below:

int interestSet = selectionKey.interestOps();

boolean isInterestedInAccept  = interestSet & SelectionKey.OP_ACCEPT;
boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;
boolean isInterestedInRead    = interestSet & SelectionKey.OP_READ;
boolean isInterestedInWrite   = interestSet & SelectionKey.OP_WRITE;    
Copy the code

In the definition of SelectionKey, different events are represented by different numbers, so it is possible to determine which event is monitored by and operation.

public abstract class SelectionKey {
    public static final int OP_READ = 1;
    public static final int OP_WRITE = 4;
    public static final int OP_CONNECT = 8;
    public static final int OP_ACCEPT = 16;
    private volatile Object attachment = null;
    
    / / to omit
}
Copy the code

A ready set denotes the type of operation a Channel is ready to perform, which is obtained by calling the readyOps method:

int readySet = selectionKey.readyOps();
Copy the code

There are four ways to determine what type of operation a Channel is ready to perform.

selectionKey.isAcceptable();
selectionKey.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();
Copy the code

In addition, you can use methods to get the corresponding Selector and Channel.

Channel  channel  = selectionKey.channel();
Selector selector = selectionKey.selector();  
Copy the code

Attach Object is an object object that is used to attach something extra to the SelectionKey, such as a Buffer to use with the Channel, or whatever else you want to attach.

selectionKey.attach(theObject);
Object attachedObj = selectionKey.attachment();
Copy the code

Alternatively, in the previous register method you can also operate by passing an attach object as a parameter.

SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);
Copy the code

Once multiple channels have been registered with Selector, and events of interest for each Channel have been listened for, the select method can be called. These channels are returned when a listener event occurs in one of the registered channels. Where, the select method has the following three forms:

  • int select()The: method blocks until at least one Channel is ready for the corresponding event operation
  • int select(long timeOut): Similar to the above, but with a maximum wait time instead of blocking all the time
  • int selectNow(): Returns immediately if no Channel is found, without blocking

The return value represents the number of channels that are ready.

Once one or more channels have been returned via the Select method, the selectKeys method can be called to get the corresponding collection of Selectionkeys.

Set<SelectionKey> selectedKeys = selector.selectedKeys();   
Copy the code

These prepared channels can be retrieved by iterating through the resulting collection.

Set<SelectionKey> selectedKeys = selector.selectedKeys();

Iterator<SelectionKey> keyIterator = selectedKeys.iterator();

while(keyIterator.hasNext()) {
    
    SelectionKey key = keyIterator.next();
	
    // Get the corresponding channel through selectionkey.channel ()
    if(key.isAcceptable()) {
        // a connection was accepted by a ServerSocketChannel.

    } else if (key.isConnectable()) {
        // a connection was established with a remote server.

    } else if (key.isReadable()) {
        // a channel is ready for reading

    } else if (key.isWritable()) {
        // a channel is ready for writing
    }

    keyIterator.remove();
}
Copy the code

Note the keyiterator.remove method called at the end of each iteration. Selector does not remove the SelectionKey instance from the collection. When a Channel is processed, it must be removed manually. The next time a Channel becomes ready, selecting Selector will add it to the collection again.

Because the single thread managing the Selector will block all the time if all the channels are not ready. If you want the thread to wakeUp immediately, you can do this by calling Selector. WakeUp by another thread. If another thread calls the wakeUp method and there is no currently blocking thread in the select method, the next thread that calls the select method will wakeUp immediately. Once a Selector is used, you can call Selector’s close method, and all registered instances of SelectionKey are invalidated, but the managed Channel is not closed.


6. Reference

Java NIO Overview

Java NIO Channel

Java NIO Buffer

Java NIO Scatter / Gather

Java NIO Channel to Channel Transfers

Java NIO Selector