Home >Java >javaTutorial >Detailed introduction to Java I/O operations and optimization with pictures and texts

Detailed introduction to Java I/O operations and optimization with pictures and texts

黄舟
黄舟Original
2017-03-06 10:42:581316browse

This article mainly introduces relevant information about Java I/O operations and optimization in detail. Friends in need can refer to the following

Summary:

Stream is A set of sequential bytes with a starting point and an ending point is a general term or abstraction for data transmission. That is, the transmission of data between two devices is called a stream. The essence of a stream is data transmission. The stream is abstracted into various categories according to the data transmission characteristics to facilitate more intuitive data operations.

Java I/O

I/O, the abbreviation of Input/Output (input/output). As far as I/O is concerned, there are conceptually 5 models: blocking I/O, nonblocking I/O, I/O multiplexing (select and poll), signal driven I/O (SIGIO), asynchronous I/O (the POSIX aio_functions). Different operating systems have different support for the above models, and UNIX supports IO multiplexing. Different systems have different names. In FreeBSD, it is called kqueue, and in Linux, it is called epoll. IOCP was born in Windows 2000 to support asynchronous I/O.

Java is a cross-platform language. In order to support asynchronous I/O, NIO was born. NIO1.0 introduced in Java1.4 is based on I/O reuse, and it will choose different methods on each platform. way of reuse. Linux uses epoll, BSD uses kqueue, and Windows uses overlapped I/O.

The relevant methods of Java I/O are as follows:

Synchronization and blocking (I/O method): The server implementation mode starts a thread for a connection, each time A thread handles the I/O personally and waits for the I/O until it is completed. That is, when the client has a connection request, the server needs to start a thread for processing. But if this connection does nothing, it will cause unnecessary thread overhead. Of course, this shortcoming can be improved through the thread pool mechanism. The limitation of I/O is that it is a stream-oriented, blocking, and serial process. Each client's Socket connection I/O requires a thread to process, and during this period, this thread is occupied until the Socket is closed. During this period, TCP connections, data reading, and data return are all blocked. In other words, a lot of CPU time slices and memory resources occupied by threads were wasted during this period. In addition, every time a Socket connection is established, a new thread is created to communicate separately with the Socket (using blocking communication). This method has a fast response speed and is easy to control. It is very effective when the number of connections is small, but generating a thread for each connection is undoubtedly a waste of system resources. If the number of connections is large, there will be insufficient resources;

Synchronous non-blocking (NIO method): The server implementation mode starts a thread for a request, and each thread handles the I/O personally, but other threads poll to check whether the I/O is ready, without waiting for the I/O to complete. That is, the connection requests sent by the client will be registered on the multiplexer, and the multiplexer will only start a thread for processing when there is an I/O request from the connection. NIO is buffer-oriented, non-blocking, and selector-based. It uses a thread to poll and monitor multiple data transmission channels. Which channel is ready (that is, there is a set of data that can be processed) will be processed. The server saves a Socket connection list, and then polls this list. If it is found that there is data to be read on a certain Socket port, the corresponding read operation of the Socket connection is called; if it is found that there is data to be written on a certain Socket port. , the corresponding write operation of the Socket connection is called; if the Socket connection of a certain port has been interrupted, the corresponding destructor method is called to close the port. This can make full use of server resources and greatly improve efficiency;

Asynchronous non-blocking (AIO method, JDK7 release): The server implementation mode starts a thread for a valid request, and the client's I/O request The operating system completes it first and then notifies the server application to start the thread for processing. Each thread does not have to handle the I/O personally, but delegates it to the operating system, and there is no need to wait for the I/O to complete. If the operation is completed The system will notify you later. This mode uses Linux's epoll model.

When the number of connections is small, the traditional I/O mode is easier to write and simpler to use. However, as the number of connections continues to increase, traditional I/O processing requires one thread for each connection. When the number of threads is small, the efficiency of the program increases as the number of threads increases. However, after a certain number, It decreases as the number of threads increases. So the bottleneck of traditional blocking I/O is that it cannot handle too many connections. The purpose of non-blocking I/O is to solve this bottleneck. There is no connection between the number of threads for non-blocking IO processing connections and the number of connections. For example, if the system handles 10,000 connections, non-blocking I/O does not need to start 10,000 threads. You can use 1,000 or 2,000 threads to process it. Because non-blocking IO handles connections asynchronously, when a connection sends a request to the server, the server treats the connection request as a request "event" and assigns this "event" to the corresponding function for processing. We can put this processing function into a thread for execution, and return the thread after execution, so that one thread can process multiple events asynchronously. Blocking I/O threads spend most of their time waiting for requests.

Java NIO

The Java.nio package is a new package added to Java after version 1.4, specifically used to improve the efficiency of I/O operations.

Table 1 shows the comparison between I/O and NIO.

Table 1. I/O VS NIO


##I/O NIOStream-orientedBuffer-orientedBlocking IONon Blocking IONoneSelector

NIO is based on blocks (Blocks), which processes data in blocks as the basic unit. In NIO, the two most important components are buffer and channel. The buffer is a contiguous memory block and is the transfer place for NIO to read and write data. The channel identifies the source or destination of buffered data. It is used to read or write data to the buffer and is the interface for accessing the buffer. Channel is a bidirectional channel, which can be read or written. Stream is one-way. The application cannot directly read and write the Channel, but must do so through the Buffer, that is, the Channel reads and writes data through the Buffer.

Using Buffer to read and write data generally follows the following four steps:

  1. Write data to Buffer;

  2. Call flip () method;

  3. Read data from Buffer;

  4. Call clear() method or compact() method.

When writing data to the Buffer, the Buffer will record how much data is written. Once you want to read data, you need to switch the Buffer from write mode to read mode through the flip() method. In read mode, all data previously written to the Buffer can be read.

Once all the data has been read, the buffer needs to be cleared so that it can be written to again. There are two ways to clear the buffer: calling the clear() or compact() method. The clear() method clears the entire buffer. The compact() method only clears data that has been read. Any unread data is moved to the beginning of the buffer, and newly written data is placed after the unread data in the buffer.

There are many types of Buffers, and different Buffers provide different ways to operate the data in the Buffer.

Figure 1 Buffer interface hierarchy diagram

There are two situations for writing data to Buffer:

  1. Write from Channel to Buffer. For example, in the example, Channel reads data from the file and writes it to Channel;

  2. directly calls the put method to write data into it.

There are two ways to read data from Buffer:

  1. Read data from Buffer to Channel;

  2. Use the get() method to read data from the Buffer.

Buffer's rewin method sets position back to 0, so you can reread all the data in the Buffer. The limit remains unchanged and still indicates how many elements (byte, char, etc.) can be read from the Buffer.

clear() and compact() methods

Once the data in the Buffer has been read, the Buffer needs to be ready to be written again. This can be done via the clear() or compact() methods.

If the clear() method is called, position will be set back to 0 and limit will be set to the value of capacity. In other words, the Buffer is cleared. The data in the Buffer is not cleared, but these marks tell us where to start writing data into the Buffer.

If there is some unread data in the Buffer, call the clear() method, and the data will be "forgotten", which means there will no longer be any markers to tell you which data has been read and which has not. If there is still unread data in the Buffer and the data is needed later, but you want to write some data first, use the compact() method. The compact() method copies all unread data to the beginning of the Buffer. Then set the position to just after the last unread element. The limit attribute is still set to capacity like the clear() method. Now the Buffer is ready for writing data, but unread data will not be overwritten.

Buffer Parameters

Buffer has three important parameters: position, capacity and limit.

capacity refers to the size of the Buffer, which has been determined when the Buffer is created.

limit When the Buffer is in write mode, it refers to how much data can be written; when it is in read mode, it refers to how much data can be read.

position When Buffer is in write mode, it refers to the position of the next data to be written; in read mode, it refers to the position of the current data to be read. Every time a piece of data is read or written, position+1, that is, limit and position have different meanings when reading/writing Buffer. When calling the flip method of Buffer and changing from write mode to read mode, limit (read) = position (write), position (read) = 0.

Scattering&Gathering

NIO provides methods for processing structured data, called Scattering and Gathering. Scattering refers to reading data into a set of Buffers, not just one. Aggregation, on the other hand, writes data into a set of Buffers. The basic usage of scattering and aggregation is quite similar to the usage when operating on a single Buffer. In a scatter read, channels fill each buffer in turn. Once one buffer is filled it starts filling the next one, in a sense the buffer array is like one big buffer. When the specific structure of the file is known, several Buffers can be constructed that conform to the file structure, so that the size of each Buffer exactly matches the size of each segment structure of the file. At this time, the content can be assembled into each corresponding Buffer at one time through scatter reading, thus simplifying the operation. If you need to create a file in a specified format, you only need to construct a Buffer object of appropriate size first and use the aggregate write method to quickly create the file. Listing 1 uses FileChannel as an example to show how to use scattering and gathering to read and write structured files.

Listing 1. Reading and writing structured files using scattering and gathering

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class NIOScatteringandGathering {
 public void createFiles(String TPATH){
 try {
 ByteBuffer bookBuf = ByteBuffer.wrap("java 性能优化技巧".getBytes("utf-8"));
ByteBuffer autBuf = ByteBuffer.wrap("test".getBytes("utf-8"));
int booklen = bookBuf.limit();
int autlen = autBuf.limit();
ByteBuffer[] bufs = new ByteBuffer[]{bookBuf,autBuf};
File file = new File(TPATH);
if(!file.exists()){
try {
file.createNewFile();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
try {
FileOutputStream fos = new FileOutputStream(file);
FileChannel fc = fos.getChannel();
fc.write(bufs);
fos.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

ByteBuffer b1 = ByteBuffer.allocate(booklen);
ByteBuffer b2 = ByteBuffer.allocate(autlen);
ByteBuffer[] bufs1 = new ByteBuffer[]{b1,b2};
File file1 = new File(TPATH);
try {
FileInputStream fis = new FileInputStream(file);
FileChannel fc = fis.getChannel();
fc.read(bufs1);
String bookname = new String(bufs1[0].array(),"utf-8");
String autname = new String(bufs1[1].array(),"utf-8");
System.out.println(bookname+" "+autname);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

 }

 public static void main(String[] args){
 NIOScatteringandGathering nio = new NIOScatteringandGathering();
 nio.createFiles("C://1.TXT");
 }
}

The output is shown in Listing 2 below.

Listing 2. Running results

java 性能优化技巧 test

The code shown in Listing 3 is suitable for traditional I/O, Byte-based NIO, and The performance of three memory-mapped NIO methods was compared, using the time-consuming read and write operations of a file with 4 million data as the evaluation basis.

Listing 3. Comparative test of three methods of I/O

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;

public class NIOComparator {
 public void IOMethod(String TPATH){
 long start = System.currentTimeMillis();
 try {
DataOutputStream dos = new DataOutputStream(
 new BufferedOutputStream(new FileOutputStream(new File(TPATH))));
for(int i=0;i<4000000;i++){
dos.writeInt(i);//写入 4000000 个整数
}
if(dos!=null){
dos.close();
}
 } catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
 } catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
 }
 long end = System.currentTimeMillis();
 System.out.println(end - start);
 start = System.currentTimeMillis();
 try {
DataInputStream dis = new DataInputStream(
 new BufferedInputStream(new FileInputStream(new File(TPATH))));
for(int i=0;i<4000000;i++){
dis.readInt();
}
if(dis!=null){
dis.close();
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

 end = System.currentTimeMillis();
 System.out.println(end - start);
 }

 public void ByteMethod(String TPATH){
 long start = System.currentTimeMillis();
 try {
FileOutputStream fout = new FileOutputStream(new File(TPATH));
FileChannel fc = fout.getChannel();//得到文件通道
ByteBuffer byteBuffer = ByteBuffer.allocate(4000000*4);//分配 Buffer
for(int i=0;i<4000000;i++){
byteBuffer.put(int2byte(i));//将整数转为数组
}
byteBuffer.flip();//准备写
fc.write(byteBuffer);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
 long end = System.currentTimeMillis();
 System.out.println(end - start);

 start = System.currentTimeMillis();
 FileInputStream fin;
try {
fin = new FileInputStream(new File(TPATH));
FileChannel fc = fin.getChannel();//取得文件通道
ByteBuffer byteBuffer = ByteBuffer.allocate(4000000*4);//分配 Buffer
fc.read(byteBuffer);//读取文件数据
fc.close();
byteBuffer.flip();//准备读取数据
while(byteBuffer.hasRemaining()){
byte2int(byteBuffer.get(),byteBuffer.get(),byteBuffer.get(),byteBuffer.get());//将 byte 转为整数
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
 end = System.currentTimeMillis();
 System.out.println(end - start);
 }

 public void mapMethod(String TPATH){
 long start = System.currentTimeMillis();
 //将文件直接映射到内存的方法
 try {
FileChannel fc = new RandomAccessFile(TPATH,"rw").getChannel();
IntBuffer ib = fc.map(FileChannel.MapMode.READ_WRITE, 0, 4000000*4).asIntBuffer();
for(int i=0;i<4000000;i++){
ib.put(i);
}
if(fc!=null){
fc.close();
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
 long end = System.currentTimeMillis();
 System.out.println(end - start);

 start = System.currentTimeMillis();
 try {
FileChannel fc = new FileInputStream(TPATH).getChannel();
MappedByteBuffer lib = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size());
lib.asIntBuffer();
while(lib.hasRemaining()){
lib.get();
}
if(fc!=null){
fc.close();
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
 end = System.currentTimeMillis();
 System.out.println(end - start);

 }

 public static byte[] int2byte(int res){
 byte[] targets = new byte[4];
 targets[3] = (byte)(res & 0xff);//最低位
 targets[2] = (byte)((res>>8)&0xff);//次低位
 targets[1] = (byte)((res>>16)&0xff);//次高位
 targets[0] = (byte)((res>>>24));//最高位,无符号右移
 return targets;
 }

 public static int byte2int(byte b1,byte b2,byte b3,byte b4){
 return ((b1 & 0xff)<<24)|((b2 & 0xff)<<16)|((b3 & 0xff)<<8)|(b4 & 0xff);
 }

 public static void main(String[] args){
 NIOComparator nio = new NIOComparator();
 nio.IOMethod("c://1.txt");
 nio.ByteMethod("c://2.txt");
 nio.ByteMethod("c://3.txt");
 }
}

The running output of Listing 3 is shown in Listing 4.

Listing 4. Run output

1139
906
296
157
234
125

In addition to the above description and the code shown in Listing 3, NIO's Buffer also provides A DirectBuffer class that can directly access the system's physical memory. DirectBuffer inherits from ByteBuffer, but is different from ordinary ByteBuffer. Ordinary ByteBuffer still allocates space on the JVM heap, and its maximum memory is limited by the maximum heap, while DirectBuffer is directly allocated on physical memory and does not occupy heap space. When accessing a normal ByteBuffer, the system always uses a "kernel buffer" for indirect operations. The location of DirectrBuffer is equivalent to this "kernel buffer". Therefore, using DirectBuffer is a method closer to the underlying system, so it is faster than ordinary ByteBuffer. Compared with ByteBuffer, DirectBuffer has much faster read and write access speeds, but the cost of creating and destroying DirectrBuffer is higher than ByteBuffer. Code that compares DirectBuffer to ByteBuffer is shown in Listing 5.

Listing 5. DirectBuffer VS ByteBuffer

import java.nio.ByteBuffer;

public class DirectBuffervsByteBuffer {
 public void DirectBufferPerform(){
 long start = System.currentTimeMillis();
 ByteBuffer bb = ByteBuffer.allocateDirect(500);//分配 DirectBuffer
 for(int i=0;i<100000;i++){
 for(int j=0;j<99;j++){
 bb.putInt(j);
 }
 bb.flip();
 for(int j=0;j<99;j++){
 bb.getInt(j);
 }
 }
 bb.clear();
 long end = System.currentTimeMillis();
 System.out.println(end-start);
 start = System.currentTimeMillis();
 for(int i=0;i<20000;i++){
 ByteBuffer b = ByteBuffer.allocateDirect(10000);//创建 DirectBuffer
 }
 end = System.currentTimeMillis();
 System.out.println(end-start);
 }

 public void ByteBufferPerform(){
 long start = System.currentTimeMillis();
 ByteBuffer bb = ByteBuffer.allocate(500);//分配 DirectBuffer
 for(int i=0;i<100000;i++){
 for(int j=0;j<99;j++){
 bb.putInt(j);
 }
 bb.flip();
 for(int j=0;j<99;j++){
 bb.getInt(j);
 }
 }
 bb.clear();
 long end = System.currentTimeMillis();
 System.out.println(end-start);
 start = System.currentTimeMillis();
 for(int i=0;i<20000;i++){
 ByteBuffer b = ByteBuffer.allocate(10000);//创建 ByteBuffer
 }
 end = System.currentTimeMillis();
 System.out.println(end-start);
 }

 public static void main(String[] args){
 DirectBuffervsByteBuffer db = new DirectBuffervsByteBuffer();
 db.ByteBufferPerform();
 db.DirectBufferPerform();
 }
}

The running output is shown in Listing 6.

Listing 6. Run output

920
110
531
390

It can be seen from Listing 6 that the cost of frequently creating and destroying DirectBuffer is much greater than that of creating and destroying DirectBuffer on the heap. Allocate memory space on. Use the parameters -XX:MaxDirectMemorySize=200M –Xmx200M to configure the maximum DirectBuffer and maximum heap space in VM Arguments. The code requests 200M space respectively. If the set heap space is too small, for example, 1M, an error will be thrown as shown in Listing 7 shown.

List 7. Run error

Error occurred during initialization of VM
Too small initial heap for new size specified

DirectBuffer information will not be printed in the GC because the GC only records the heap space memory recycling. It can be seen that since ByteBuffer allocates space on the heap, its GC array is relatively frequent. In situations where Buffer needs to be created frequently, DirectBuffer should not be used because the code to create and destroy DirectBuffer is relatively expensive. However, if DirectBuffer can be reused, system performance can be greatly improved. Listing 8 is a piece of code for monitoring DirectBuffer.

Listing 8. Run the DirectBuffer monitoring code

import java.lang.reflect.Field;

public class monDirectBuffer {

public static void main(String[] args){
try {
Class c = Class.forName("java.nio.Bits");//通过反射取得私有数据
Field maxMemory = c.getDeclaredField("maxMemory");
maxMemory.setAccessible(true);
Field reservedMemory = c.getDeclaredField("reservedMemory");
reservedMemory.setAccessible(true);
synchronized(c){
Long maxMemoryValue = (Long)maxMemory.get(null);
Long reservedMemoryValue = (Long)reservedMemory.get(null);
System.out.println("maxMemoryValue="+maxMemoryValue);
System.out.println("reservedMemoryValue="+reservedMemoryValue);
}
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchFieldException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}
}

The output is shown in Listing 9.

List 9. Run output

maxMemoryValue=67108864
reservedMemoryValue=0

Because NIO is difficult to use, many companies have launched their own packages that package JDK NIO Frameworks, such as Apache's Mina, JBoss's Netty, Sun's Grizzly, etc., these frameworks directly encapsulate the TCP or UDP protocol of the transport layer, among which Netty is just a NIO framework, it does not require additional support from the Web container, that is to say Web containers are not restricted.

Java AIO

AIO related classes and interfaces:

java.nio.channels.AsynchronousChannel:标记一个 Channel 支持异步 IO 操作;
java.nio.channels.AsynchronousServerSocketChannel:ServerSocket 的 AIO 版本,创建 TCP 服务端,绑定地址,监听端口等;
java.nio.channels.AsynchronousSocketChannel:面向流的异步 Socket Channel,表示一个连接;
java.nio.channels.AsynchronousChannelGroup:异步 Channel 的分组管理,目的是为了资源共享。
一个 AsynchronousChannelGroup 绑定一个线程池,这个线程池执行两个任务:处理 IO 事件和派发 CompletionHandler。AsynchronousServerSocketChannel 
创建的时候可以传入一个 AsynchronousChannelGroup,那么通过 AsynchronousServerSocketChannel 创建的 AsynchronousSocketChannel 将同属于一个组,共享资源;
java.nio.channels.CompletionHandler:异步 IO 操作结果的回调接口,用于定义在 IO 操作完成后所作的回调工作。
AIO 的 API 允许两种方式来处理异步操作的结果:返回的 Future 模式或者注册 CompletionHandler,推荐用 CompletionHandler 的方式,
这些 handler 的调用是由 AsynchronousChannelGroup 的线程池派发的。这里线程池的大小是性能的关键因素。

Here is a program example to briefly introduce AIO How it works.

Listing 10. Server program

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.ExecutionException;

public class SimpleServer {
public SimpleServer(int port) throws IOException { 
final AsynchronousServerSocketChannel listener = 
 AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(port));
//监听消息,收到后启动 Handle 处理模块
listener.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {
public void completed(AsynchronousSocketChannel ch, Void att) { 
listener.accept(null, this);// 接受下一个连接 
handle(ch);// 处理当前连接 
}

@Override
public void failed(Throwable exc, Void attachment) {
// TODO Auto-generated method stub

} 

});
}

public void handle(AsynchronousSocketChannel ch) { 
ByteBuffer byteBuffer = ByteBuffer.allocate(32);//开一个 Buffer 
try { 
 ch.read(byteBuffer).get();//读取输入 
} catch (InterruptedException e) { 
 // TODO Auto-generated catch block 
 e.printStackTrace(); 
} catch (ExecutionException e) { 
 // TODO Auto-generated catch block 
 e.printStackTrace(); 
} 
byteBuffer.flip(); 
System.out.println(byteBuffer.get()); 
// Do something 
} 

}

Listing 11. Client program

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

public class SimpleClientClass {
private AsynchronousSocketChannel client; 
public SimpleClientClass(String host, int port) throws IOException, 
         InterruptedException, ExecutionException { 
 this.client = AsynchronousSocketChannel.open(); 
 Future<?> future = client.connect(new InetSocketAddress(host, port)); 
 future.get(); 
} 

public void write(byte b) { 
 ByteBuffer byteBuffer = ByteBuffer.allocate(32);
 System.out.println("byteBuffer="+byteBuffer);
 byteBuffer.put(b);//向 buffer 写入读取到的字符 
 byteBuffer.flip();
 System.out.println("byteBuffer="+byteBuffer);
 client.write(byteBuffer); 
} 

}

List 12.Main function

import java.io.IOException;
import java.util.concurrent.ExecutionException;

import org.junit.Test;

public class AIODemoTest {

@Test
public void testServer() throws IOException, InterruptedException { 
 SimpleServer server = new SimpleServer(9021); 
 Thread.sleep(10000);//由于是异步操作,所以睡眠一定时间,以免程序很快结束
} 

@Test 
public void testClient() throws IOException, InterruptedException, ExecutionException { 
SimpleClientClass client = new SimpleClientClass("localhost", 9021); 
 client.write((byte) 11); 
}

public static void main(String[] args){
AIODemoTest demoTest = new AIODemoTest();
try {
demoTest.testServer();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
demoTest.testClient();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

}

There will be a follow-up article to introduce the source of AIO in depth Code, design concepts, design patterns, etc.

Conclusion

An important difference between I/O and NIO is that when we use I/O, we often introduce multi-threading. Each connection uses a separate thread, while NIO uses a single thread or only a small number of multi-threads. , each connection shares one thread. Since the non-blocking nature of NIO requires constant polling, which consumes system resources, the asynchronous non-blocking mode AIO was born. This article introduces the three input and output operation modes such as I/O, NIO, and AIO one by one, and strives to enable readers to master basic operations and optimization methods through simple descriptions and examples.

The above is the detailed introduction of Java I/O operation and optimization with pictures and texts. For more related content, please pay attention to the PHP Chinese website (www.php.cn)!


Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn