Until now, Java IO can be divided into three categories: BIO, NIO, and AIO. The earliest one was BIO, then NIO, and the most recent one is AIO. BIO is Blocking IO. Some articles of NIO say New NIO, and some articles say No Blocking IO. I checked some information, and the official website should say No. Blocking IO provides Selector, Channel, and SelectionKey abstractions. AIO is Asynchronous IO (asynchronous IO), and provides asynchronous operations such as Fauture.
1 BIO

The above figure is the architecture diagram of BIO. It can be seen that BIO is mainly divided into two types of IO, namely character stream IO and byte stream IO. Character stream treats input and output data as characters, Writer and Reader are the highest level of its inheritance system, and byte stream is the input and output data. Output is treated as bytes, InputStream and OutputStream are the highest level of its inheritance system. The following takes file operation as an example, and other implementation classes are also very similar.
By the way, the entire BIO system uses a lot of decorator patterns, such as BufferedInputStream wrapping InputStream, so that it has the ability to buffer.
1.1 Byte Stream
public class Main { public static void main(String[] args) throws IOException { //write to 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 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 code
Pass the file name to the FileOutputStream constructor to create a FileOutputStream object, that is, open a byte stream, then use the write method to write data to the byte stream, call flush to refresh the buffer after completion, and finally remember to close the byte stream . Reading a file is similar, first open a byte stream, then read data from the byte stream and store it in memory (buffer array), and then close the byte stream.
Because InputStream and OutputStream both inherit the AutoCloseable interface, if you use the try-resource syntax for byte stream IO operations, you don't need to explicitly call the close method manually. This is also a very recommended practice. In the example, I This is not done just for convenience.
1.2 Character stream
The byte stream mainly uses InputStream and OutputStream, and the character stream mainly uses the corresponding Reader and Writer. Let's look at an example, the function of this example is the same as the above example, but the implementation method is different:
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 code
Writer is very simple, just open the character stream, then write characters to the character stream, then close. The key is Reader. In the sample code, BufferedReader is used to wrap FileReader, so that FileReader without buffering function has buffering function. This is the decorator mode mentioned above. BufferedReader also provides convenient API s, such as readLine() , this method reads one line from the file each time it is called.
The above is the simple use of BIO. Because the source code involves too many bottom layers, it will be difficult to understand the source code if you don't know the bottom layer 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 Selector is the so-called selector. Selector), when data arrives on the Channel, the Selector will recover from the blocking state and operate on the Channel, and we cannot directly read and write to the Channel, but only operate on the ByteBuffer. As shown below:

The following is an example of Socket network programming:
//Server public class SocketServer { private Selector selector; private final static int port = 9000; private final static int BUF = 10240; private void init() 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 port channel.socket().bind(new InetSocketAddress(port)); //Register the channel to the Selector and express interest in the ACCEPT event SelectionKey selectionKey = channel.register(selector, SelectionKey.OP_ACCEPT); while (true) { //This method will block until data comes from any of the channel s bound to it selector.select(); //Get the SelectionKey bound by the Selector Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<SelectionKey> iterator = selectionKeys.iterator(); while (iterator.hasNext()) { SelectionKey key = iterator.next(); //Remember to delete, otherwise it will loop infinitely iterator.remove(); //If the event is an ACCEPT, execute the doAccept method, and the same for others 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 succeeded!"); } } } } //Write method, note that you cannot directly read and write to channel, but only to 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 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 the message: " + new String(data, "UTF-8")); buffer.clear(); reads = socketChannel.read(buffer); } if (reads == -1) { socketChannel.close(); } } //When there is a connection, get the connected channel, register it on the Selector, and set it to be interested in reading messages. When the client has a message, the Selector can let it execute the doRead method, and then read the message. and print. private void doAccept(SelectionKey key) throws IOException { ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel(); System.out.println("Server is 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 = new SocketServer(); server.init(); } } //Client, it is relatively simple to write public class SocketClient { private final static int port = 9000; private final static int BUF = 10240; private void init() throws IOException { //get channel SocketChannel channel = SocketChannel.open(); //connect to remote server channel.connect(new InetSocketAddress(port)); //set non-blocking mode channel.configureBlocking(false); //Write message to ByteBuffer ByteBuffer buffer = ByteBuffer.allocate(BUF); buffer.put("Hello,Server".getBytes("UTF-8")); buffer.flip(); //Write ByteBuffer content to Channel, i.e. send message channel.write(buffer); channel.close(); } public static void main(String[] args) throws IOException { SocketClient client = new SocketClient(); client.init(); } } copy code
Try to start a server and multiple clients, the results are roughly as follows:
Server is listening... Read the message: Hello,Server Server is listening... Read the message: Hello,Server copy code
The comments are very clear. I just use NIO here, but in fact NIO is far more than these things. One ByteBuffer alone can say a day. If there is a chance, I will talk about this in detail in the articles related to Netty later. several components. I won't say more here.
Tucao, the server and client written by pure NIO is too troublesome, and it will be wrong if you are not careful. It is better to use a framework similar to Netty.
3 AIO
Some IO-related APIs have been added in JDK7, and these APIs are called AIO. Because it provides some functions for asynchronous operation of IO, but the essence is actually NIO, so it can be simply understood as an extension of NIO. The most important thing in AIO is Future, Future means the future, that is, this operation may last for a long time, but I will not wait, but come to notify me when the operation is completed in the future, this is the meaning of 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(); //Blocking, if the method is not called, the main method will continue to execute buffer.flip(); System.out.println(new String(buffer.array(), "UTF-8")); System.out.println(readNum); } copy code
The first example uses AsynchronousFileChannel to read the file content asynchronously. In the code, I use the future.get() method, which blocks the current thread, which is the main thread in the example. When the worker thread is reading the file The thread will recover from the blocking state and return the result after the execution of the thread is completed. After that, the data can be read from the ByteBuffer. This is an example of using the future tense, let's see 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"); //After the call is completed, there is no need to wait for the task to complete, it will continue to execute the main thread directly while (true) { Thread.sleep(1000); } } } copy code
The output will look roughly like this, but not necessarily, it depends on thread scheduling:
continue executing the main thread finish reading hello,world hello,yeonon copy code
When the task is completed, that is, when the file is read, the completed method will be called, and if it fails, the failed method will be called, which is the callback. Friends who have been exposed to callbacks in detail should not be difficult to understand.
4 Differences between BIO, NIO, and AIO
- BIO is synchronous blocking IO, NIO is synchronous non-blocking IO, and AIO is asynchronous non-blocking IO. This is the most basic difference. The blocking mode will cause other threads to be blocked by the IO thread. You must wait for the IO thread to complete the execution before continuing to execute the logic. Non-blocking and asynchronous are not the same. In non-blocking mode, event polling is generally used to perform IO, that is, more IO. Although multiplexing is still synchronous, its execution efficiency is much higher than that of traditional BIO. AIO is asynchronous IO. If IO work is regarded as a task, there will be no blocking after submitting a task in the current thread. It will continue to execute the follow-up logic of the current thread. After the task is completed, the current thread will receive a notification and then decide how to deal with it. This way of IO and CPU efficiency is the highest, and the CPU hardly ever pauses, and the Busy state, so the efficiency is very high, but the programming difficulty will be relatively large.
- BIO is oriented to streams, whether it is a character stream or a byte stream. Generally speaking, BIO reads and writes data one by one when reading and writing data, while NIO and AIO (because AIO is actually an extension of NIO, So from this point of view, they can be put together) when reading and writing data, it is read block by block, the read data will be cached in the memory, and then the data will be processed in the memory. The advantage of this method is that the number of reads and writes of the hard disk or the network is reduced, thereby reducing the efficiency impact caused by the slow speed of the hard disk or the network.
- Although the API of BIO is relatively low-level, it will be easier to write if you are familiar with it. The API of NIO or AIO has a high level of abstraction and should be easier to use in general, but it is actually difficult to write "correctly", and DEBUG It is also more difficult, which is one of the reasons why NIO frameworks such as Netty are popular.
The above is the difference between BIO, NIO and AIO as I understand.
5 Summary
This article briefly and roughly talks about the use of BIO, NIO, and AIO. It does not involve the source code, nor does it involve too many principles. If readers want to know more about the three, it is recommended to refer to some books, such as the book "Written by foreigners" Java NIO", this book comprehensively and systematically explains the various components and details of NIO, and is highly recommended.
Author: yeonon
Link: https://juejin.cn/post/6844903941977686029
Source: Nuggets
Copyright belongs to the author. For commercial reprints, please contact the author for authorization, and for non-commercial reprints, please indicate the source.