Until now, Java IO has been divided into three categories: BIO, NIO, and AIO. The first one was BIO, then NIO, and now AIO stands for Blocking IO. Some articles say that BIO stands for New NIO, and some articles say that it is No Blocking IO. I have looked up some information, and the official website says that it is No Blocking IO. Provides Selector, Channle, SelectionKey abstractions, and AIO (Asynchronous IO), which provides Fauture and other Asynchronous operations.

1 BIO

The figure above is BIO’s architecture diagram. Character streams treat input and output data as characters. Writer and Reader are the highest level of their inheritance. Byte streams treat input and output data as bytes. InputStream and OutputStream are at the top of their hierarchy. The following uses file operations as an example, and the other implementation classes are very similar.

By the way, the entire BIO architecture makes extensive use of decorator patterns, such as BufferedInputStream wrapping InputStream with buffering capabilities.

1.1 the byte stream

public class Main {

    public static void main(String[] args) throws IOException {
		// Write to the file
        FileOutputStream out = new FileOutputStream("E:\\Java_project\\effective-java\\src\\top\\yeonon\\io\\bio\\test.txt");
        out.write("hello,world".getBytes("UTF-8"));
        out.flush();
        out.close();

        // Read the file
        FileInputStream in = new FileInputStream("E:\\Java_project\\effective-java\\src\\top\\yeonon\\io\\bio\\test.txt");
        byte[] buffer = new byte[in.available()];
        in.read(buffer);
        System.out.println(new String(buffer, "UTF-8")); in.close(); }}Copy the code

The FileOutputStream object is created by passing a file name to the FileOutputStream constructor, opening a byte stream, writing data to the byte stream using the write method, flushing the buffer with flush, and closing the byte stream. Reading a file is similar, opening a byte stream, then reading data from the byte stream and storing it in memory (the buffer array), and then closing the byte stream.

Since both InputStream and OutputStream inherit from the AutoCloseable interface, it is highly recommended that you do not need to explicitly call the close method manually if you use the try-resource syntax for byte stream I/O operations. I didn’t do this in the example just for convenience.

1.2 characters flow

The byte stream mainly uses InputStream and OutputStream, while the character stream mainly uses the corresponding Reader and Writer. Let’s look at an example that does the same thing but implements it differently:

public class Main {

    public static void main(String[] args) throws IOException {
        Writer writer = new FileWriter("E:\\Java_project\\effective-java\\src\\top\\yeonon\\io\\bio\\test.txt");
        writer.write("hello,world\n");
        writer.write("hello,yeonon\n");
        writer.flush();
        writer.close();

        BufferedReader reader = new BufferedReader(new FileReader("E:\\Java_project\\effective-java\\src\\top\\yeonon\\io\\bio\\test.txt"));

        String line = "";
        int lineCount = 0;
        while((line = reader.readLine()) ! =null) { System.out.println(line); lineCount++; } reader.close(); System.out.println(lineCount); }}Copy the code

Writer is very simple. You can’t just open the stream, write characters to the stream, and then close it. The key is Reader. The sample code uses BufferedReader to wrap FileReader, so that FileReader, which doesn’t have BufferedReader, has BufferedReader buffering, which is the decorator mode mentioned above, and BufferedReader provides an API that’s easy to use. Take readLine(), which reads one line from the file each time it is called.

So that’s BIO for simple use. Source code involves so much low-level that it’s hard to understand source code if you don’t know the low-level very well.

2 NIO

BIO is synchronous blocking IO, while NIO is synchronous non-blocking IO. There are several important components in NIO: Selector, SelectionKey, Channel, ByteBuffer, where a Selector is what’s called a Selector, a SelectionKey is simply a SelectionKey, This key binds a Selector to a Channle (or registers it with a Selector), and when data reaches a Channel, the Selector recovers from the blocking state and performs an operation on the Channel. We cannot read or write directly to Channle, only to ByteBuffer. As shown below:

Here is an example of Socket network programming:

/ / the server
public class SocketServer {

    private Selector selector;
    private final static int port = 9000;
    private final static int BUF = 10240;

    private void init(a) throws IOException {
        // Get a Selector
        selector = Selector.open();
	    // Get a server socket Channel
        ServerSocketChannel channel = ServerSocketChannel.open();
        // Set to non-blocking mode
        channel.configureBlocking(false);
        // Bind ports
        channel.socket().bind(new InetSocketAddress(port));
	    Register channle with Selector and show interest in the ACCEPT event
        SelectionKey selectionKey = channel.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
            // This method blocks until data arrives from any of its bound channels
            selector.select();
            // Get the SelectionKey of the Selector binding
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                // Remember to delete, otherwise the loop will be infinite
                iterator.remove();
                // If the event is an ACCEPT, the doAccept method is executed, as is everything else
                if (key.isAcceptable()) {
                    doAccept(key);
                } else if (key.isReadable()) {
                    doRead(key);
                } else if (key.isWritable()) {
                    doWrite(key);
                } else if (key.isConnectable()) {
                    System.out.println("Connection successful!"); }}}}// Write method. Note that you cannot read or write channle directly, but only ByteBuffer
    private void doWrite(SelectionKey key) throws IOException {
        ByteBuffer buffer = ByteBuffer.allocate(BUF);
        buffer.flip();
        SocketChannel socketChannel = (SocketChannel) key.channel();
        while (buffer.hasRemaining()) {
            socketChannel.write(buffer);
        }
        buffer.compact();
    }

    // Read the message
    private void doRead(SelectionKey key) throws IOException {
        SocketChannel socketChannel = (SocketChannel) key.channel();
        ByteBuffer buffer = ByteBuffer.allocate(BUF);
        long reads = socketChannel.read(buffer);
        while (reads > 0) {
            buffer.flip();
            byte[] data = buffer.array();
            System.out.println("Read message:" + new String(data, "UTF-8"));
            buffer.clear();
            reads = socketChannel.read(buffer);
        }
        if (reads == -1) { socketChannel.close(); }}// When a connection comes in, get the channle connected to it, register it with the Selector, and set it to be interested in reading the message. When a message comes in, the Selector can tell it to doRead the message and print it.
    private void doAccept(SelectionKey key) throws IOException {
        ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
        System.out.println("Server listening...");
        SocketChannel socketChannel = serverChannel.accept();
        socketChannel.configureBlocking(false);
        socketChannel.register(key.selector(), SelectionKey.OP_READ);
    }
    
    public static void main(String[] args) throws IOException {
        SocketServer server = newSocketServer(); server.init(); }}// The client is simpler to write
public class SocketClient {

    private final static int port = 9000;
    private final static int BUF = 10240;


    private void init(a) throws IOException {
        / / for the channel
        SocketChannel channel = SocketChannel.open();
        // Connect to remote server
        channel.connect(new InetSocketAddress(port));
        // Set non-blocking mode
        channel.configureBlocking(false);
        // Write messages to ByteBuffer
        ByteBuffer buffer = ByteBuffer.allocate(BUF);
        buffer.put("Hello,Server".getBytes("UTF-8"));
        buffer.flip();
        // Write the ByteBuffer to Channle, i.e. send a message
        channel.write(buffer);
        channel.close();
    }


    public static void main(String[] args) throws IOException {
        SocketClient client = newSocketClient(); client.init(); }}Copy the code

Try to start one server, multiple clients, and the result looks something like this:

Server listening... Message read: Hello,Server listening... Message read: Hello,ServerCopy the code

The comments are pretty clear and I’m just using NIO here, but NIO is much more than that. A ByteBuffer can be used for a day. I’ll talk about these components in more detail in a later Netty related article if I get the chance. I will say no more here.

For fun, NIO server and client is too troublesome to write, accidentally will be wrong, or use Netty similar framework better.

3 AIO

Some IO related apis called AIO have been added to JDK7. Because it provides some asynchronous IO functions, but the essence is really NIO, so it can be easily understood as an extension of NIO. The most important thing in AIO is Future, and Future means Future, so this operation might take a long time, but I don’t wait, I’ll tell me when it’s done in the Future, and that’s asynchronous. Here are two examples of using AIO:

    public static void main(String[] args) throws ExecutionException, InterruptedException, IOException {
        Path path = Paths.get("E:\\Java_project\\effective-java\\src\\top\\yeonon\\io\\aio\\test.txt");
        AsynchronousFileChannel channel = AsynchronousFileChannel.open(path);
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        Future<Integer> future = channel.read(buffer,0);
        Integer readNum = future.get(); // block. If this method is not called, the main method continues
        buffer.flip();
        System.out.println(new String(buffer.array(), "UTF-8"));
        System.out.println(readNum);
    }
Copy the code

The first example uses AsynchronousFileChannel to read file contents asynchronously. In the code, I use the future.get() method, which blocks the current thread, in this case the main thread, until the worker thread, the thread reading the file, finishes. And returns the result. The data can then be read from ByteBuffer. This is an example of using the future tense. Let’s look at an example of using a callback:

public class Main {

    public static void main(String[] args) throws ExecutionException, InterruptedException, IOException {
        Path path = Paths.get("E:\\Java_project\\effective-java\\src\\top\\yeonon\\io\\aio\\test.txt");
        AsynchronousFileChannel channel = AsynchronousFileChannel.open(path);
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        channel.read(buffer, 0, buffer, new CompletionHandler<Integer, ByteBuffer>() {
            @Override
            public void completed(Integer result, ByteBuffer attachment) {
                System.out.println("Finish reading");
                try {
                    System.out.println(new String(attachment.array(), "UTF-8"));
                } catch(UnsupportedEncodingException e) { e.printStackTrace(); }}@Override
            public void failed(Throwable exc, ByteBuffer attachment) {
                System.out.println("Read failed"); }}); System.out.println("Continue executing the main thread");
        // Execute the main thread directly without waiting for the task to complete
        while (true) {
            Thread.sleep(1000); }}}Copy the code

The output should look something like this, but not necessarily, depending on thread scheduling:

Continue with the main thread and finish reading Hello, World Hello, YeononCopy the code

When the task is completed, that is, the reading of the file, the completed method is called, and when it fails, the failed method is called, which is the callback. Friends who have been exposed to callback in detail should not be difficult to understand.

4 Differences between BIO, NIO and AIO

  1. BIO is synchronous blocking I/O, NIO is synchronous non-blocking I/O, and AIO is asynchronous non-blocking I/O. These are the basic differences. Blocking mode can lead to other threads by IO thread block, after must wait IO thread execution to continue execution logic, non-blocking and asynchronous is not equal, non-blocking mode, usually adopt the way of event polling to perform IO, namely IO multiplexing, although still is synchronous, but the performance is much better than the traditional BIO, AIO is asynchronous I/o, If the IO work as a task, after submit a task in the current thread, there won’t be blocked, will continue to perform the subsequent logic of the current thread, after the completion of a task, the current thread will receive a notification, then decide how to deal with, this way of IO, CPU efficiency is the highest, CPU happened almost no pause, And the time has been as busy state, so the efficiency is very high, but the programming difficulty will be relatively large.
  2. BIO for flow, flow characters or bytes, popular, BIO at the time of reading and writing data according to the way one by one to read and write, and NIO and AIO (because of AIO is actually the NIO expansion, so from this perspective, you can put them on a piece of) when reading and writing data is in accordance with the read piece by piece. The read data is cached in the memory, and then the data is processed in the memory. In this way, the read and write times of the hard disk or network are reduced, thus reducing the impact on the efficiency caused by the slow speed of the hard disk or network.
  3. BIO’s APIS are low-level, but easy to write once you’re familiar with them. NIO’s or AIO’s apis have a high level of abstraction and should generally be easier to use, but are actually difficult to write “correctly.” DEBUG is also difficult, which is one of the reasons why NIO frameworks like Netty are so popular.

That’s my understanding of the difference between BIO, NIO and AIO.

5 subtotal

This article simply rough spoke about the use of BIO, NIO, AIO, does not relate to the source code, did not involve too much principle, if readers want to know more about the content of the three, suggest see some books, such as foreigners to write the Java NIO, the interpretation of the overall system the various components of the NIO and detail, highly recommend.