Java NIO - basic details


Standard IO is used to read and write byte streams. Before IO, create a stream object. The stream object reads and writes by byte
, byte by byte to read or write. NIO abstracts IO into blocks, similar to disk reading and writing. The unit of each IO operation is a block. After the block is read into memory, it is a byte []. NIO can read or write multiple bytes at a time.

Stream and block

The most important difference between I/O and NIO is the way of data packaging and transmission. I/O processes data in stream, while NIO processes data in block.

Stream oriented I/O processes one byte of data at a time: an input stream generates one byte of data, and an output stream consumes one byte of data. It's easy to create filters for streaming data, linking several filters so that each filter is responsible for only part of a complex processing mechanism. The downside is that stream oriented I/O is usually quite slow.

Block oriented I/O processes one data block at a time, and processing data by block is much faster than processing data by stream. However, block oriented I/O lacks the elegance and simplicity of stream oriented I/O.

I/O package and NIO have been well integrated, and java.io. * has been re implemented based on NIO, so it can now take advantage of some features of NIO. For example, some classes in the java.io. * package contain methods to read and write data in blocks, which makes processing faster even in stream oriented systems.

Channels and buffers

1. Access

Channel is a simulation of the stream in the original I/O package, through which data can be read and written.

The difference between channels and streams is that streams can only move in one direction (a stream must be a subclass of InputStream or OutputStream), while channels are bidirectional and can be used for reading, writing or both.

Channels include the following types:

  • FileChannel: read and write data from files;
  • Datagram channel: read and write data in the network through UDP;
  • SocketChannel: read and write data in the network through TCP;
  • ServerSocketChannel: it can listen to new TCP connections and create a SocketChannel for each new connection.

2. Buffer zone

All data sent to a channel must be put into the buffer first. Similarly, any data read from the channel must be read into the buffer first. In other words, the channel will not be read or written directly, but will pass through the buffer first.
A buffer is essentially an array, but it is not just an array. Buffers provide structured access to data and can also track the read / write process of the system.
Buffers include the following types:

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

Buffer state variable

  • Capacity: maximum capacity;
  • position: the number of bytes currently read and written;
  • limit: the number of bytes that can be read and written.

File NIO instance

public static void fastCopy(String src, String dist) throws IOException {

    /* Gets the input byte stream of the source file */
    FileInputStream fin = new FileInputStream(src);

    /* Gets the file channel of the input byte stream */
    FileChannel fcin = fin.getChannel();

    /* Gets the output byte stream of the target file */
    FileOutputStream fout = new FileOutputStream(dist);

    /* Gets the channel of the output byte stream */
    FileChannel fcout = fout.getChannel();

    /* Allocate 1024 bytes for the buffer */
    ByteBuffer buffer = ByteBuffer.allocateDirect(1024);

    while (true) {

        /* Read data from the input channel into the buffer */
        int r = fcin.read(buffer);

        /* read() Return - 1 for EOF */
        if (r == -1) {
            break;
        }

        /* Toggle read / write */
        buffer.flip();

        /* Writes the contents of the buffer to the output file */
        fcout.write(buffer);
        
        /* Empty buffer */
        buffer.clear();
    }
}

selector

NIO is often called non blocking IO, mainly because NIO is widely used in network communication.

NIO implements the Reactor model in IO multiplexing. A Thread uses a Selector selector to listen for events on multiple channels through polling, so that one Thread can handle multiple events.

By configuring the monitored Channel channel as non blocking, when the IO event on the Channel has not arrived, it will not enter the blocking state and wait all the time, but continue to poll other channels to find the Channel where the IO event has arrived for execution.
Because creating and switching threads is expensive, using one thread to process multiple events rather than one thread to process one event has better performance. It should be noted that only socket Channel can be configured as non blocking, while FileChannel cannot, because it makes no sense to configure FileChannel as non blocking.

1. Create selector

Selector selector = Selector.open();

2. Register the channel on the selector

ServerSocketChannel ssChannel = ServerSocketChannel.open();
ssChannel.configureBlocking(false);
ssChannel.register(selector, SelectionKey.OP_ACCEPT);

The channel must be configured in the non blocking mode, otherwise using the selector is meaningless, because if the channel is blocked on an event, the server cannot respond to other events and must wait for the event to be processed before processing other events. Obviously, this runs counter to the role of the selector.

When registering the channel to the selector, you also need to specify the specific events to register, mainly including the following categories:

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

Their definitions in SelectionKey are as follows:

public static final int OP_READ = 1 << 0;
public static final int OP_WRITE = 1 << 2;
public static final int OP_CONNECT = 1 << 3;
public static final int OP_ACCEPT = 1 << 4;

It can be seen that each event can be regarded as a bit field to form an event set integer. For example:

int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;

3. Listening events

int num = selector.select();

Use select() to listen for incoming events, which will block until at least one event arrives.

4. Get the arrival event

Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = keys.iterator();
while (keyIterator.hasNext()) {
    SelectionKey key = keyIterator.next();
    if (key.isAcceptable()) {
        // ...
    } else if (key.isReadable()) {
        // ...
    }
    keyIterator.remove();
}

5. Event cycle

Because a single select() call cannot handle all events, and the server may need to listen to events all the time, the code for processing events on the server will generally be placed in an endless loop.

while (true) {
    int num = selector.select();
    Set<SelectionKey> keys = selector.selectedKeys();
    Iterator<SelectionKey> keyIterator = keys.iterator();
    while (keyIterator.hasNext()) {
        SelectionKey key = keyIterator.next();
        if (key.isAcceptable()) {
            // ...
        } else if (key.isReadable()) {
            // ...
        }
        keyIterator.remove();
    }
}

Socket NIO instance

public class NIOServer {

    public static void main(String[] args) throws IOException {
		//Create selector
        Selector selector = Selector.open();
        //Create a channel and register it on the selector
        ServerSocketChannel ssChannel = ServerSocketChannel.open();
        ssChannel.configureBlocking(false);
        ssChannel.register(selector, SelectionKey.OP_ACCEPT);
		//Get his socket (data source) through channel
        ServerSocket serverSocket = ssChannel.socket();
        InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8888);
        serverSocket.bind(address);

        while (true) {
			//Selector on listening
            selector.select();
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> keyIterator = keys.iterator();
			//Continuously traverse the channel monitored by the selector
            while (keyIterator.hasNext()) {

                SelectionKey key = keyIterator.next();
				//If any data reaches the channel
                if (key.isAcceptable()) {

                    ServerSocketChannel ssChannel1 = (ServerSocketChannel) key.channel();

                    // The server creates a SocketChannel for each new connection
                    SocketChannel sChannel = ssChannel1.accept();
                    sChannel.configureBlocking(false);

                    // This new connection is mainly used to read data from the client
                    sChannel.register(selector, SelectionKey.OP_READ);

                } else if (key.isReadable()) {

                    SocketChannel sChannel = (SocketChannel) key.channel();
                    System.out.println(readDataFromSocketChannel(sChannel));
                    sChannel.close();
                }

                keyIterator.remove();
            }
        }
    }

    private static String readDataFromSocketChannel(SocketChannel sChannel) throws IOException {

        ByteBuffer buffer = ByteBuffer.allocate(1024);
        StringBuilder data = new StringBuilder();

        while (true) {

            buffer.clear();
            int n = sChannel.read(buffer);
            if (n == -1) {
                break;
            }
            buffer.flip();
            int limit = buffer.limit();
            char[] dst = new char[limit];
            for (int i = 0; i < limit; i++) {
                dst[i] = (char) buffer.get(i);
            }
            data.append(dst);
            buffer.clear();
        }
        return data.toString();
    }
}
public class NIOClient {

    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1", 8888);
        OutputStream out = socket.getOutputStream();
        String s = "hello world";
        out.write(s.getBytes());
        out.close();
    }
}

Memory mapping file

Memory mapped file I/O is a method to read and write file data. It can be much faster than conventional stream based or channel based I/O.

Writing to a memory mapped file can be dangerous. Simply changing a single element of the array may directly modify the file on disk. Modifying data is not separate from saving data to disk.

The following code line maps the first 1024 bytes of the file to memory, and the map() method returns a MappedByteBuffer, which is a subclass of ByteBuffer. Therefore, the newly mapped buffer can be used like any other ByteBuffer, and the operating system is responsible for performing the mapping when needed.

MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 0, 1024);

contrast

NIO differs from ordinary I/O mainly in the following two points:

  • NIO is non blocking
  • NIO is block oriented and I/O is stream oriented

Tags: Java udp TCP/IP

Posted on Tue, 21 Sep 2021 15:26:57 -0400 by venkyphp