在正式学习NIO这部分之前有几个常见的需要了解清楚。
1. 什么是同步,异步,阻塞,非阻塞?
一次正常的IO操作分为两个阶段 1.发起IO 2.执行IO

如上图:
- 当发起IO请求时,如果当前线程需要一直等待结果。那么就可以认为当前是同步。反之,当前线程就可以认为异步。
搭配一个经典例子:隔壁老王去烧水,老王一直不放心水是否烧开,一直在这里等。那么就认为老王同步操作。反之,老王打完水之后放在这里烧就去了隔壁。定时回来check水是否烧开,不再这里等待烧水结果。这种场景就是异步。
- 当前线程在执行IO时,是否需要当前线程参与。参与则认为是阻塞 ,否则则为非阻塞操作。
还是老王烧水,进入21世纪之后,老王不再想时刻被这个烧水耽误自己的时间。买了一个可以提醒的水壶,在水开了之后发出语音:老王水开了。这时候老王还是站在这里,还是什么也不干。当听到这个语音提示之后就知道水开了。这种场景就是异步阻塞。
烧了很多年后,老王发现我可以去隔壁干的什么,不需要在这里听语音提示,调大了声音隔壁也能听到。这时候老王并没有在这里干等,可以放开身心去做的其他的事情。这种场景就是异步非阻塞。
第一个集中点:在于老王是否需要主动参与到发现水烧开这个事件上,如果没有需要那么就可以认为是异步操作。延伸到线程的IO操作上,在于线程在执行IO读写这个阶段是否需要主动参与到读写的过程,如果是马上返回那么认为是异步操作,反之则为同步操作
第二个集中点:第二个阶段是否需要老王阻塞在这里等待,如果需要等待水烧开的结果,那么被认为是阻塞操作;反之则是非阻塞。延伸到线程IO操作是否为阻塞操作,那么就是在线程IO的读写阶段是否需要等待IO读写结果。参与等待则认为是阻塞,反之则为非阻塞。
2. 什么是NIO?
Java 1.4 之后,提供了关于NIO API。 Java 中的NIO api 全称为 a multiplexed, non-blocking I/O。那么NIO和IO的区别在哪里?换句话说NIO存在的意义是什么?对比两个传统IO,NIO的两种处理文本的方式来分析一下:
private static String getNormalIORead() throws Exception{
//get file
File file =new File("/Users/code/nio/test.txt");
//get stream
InputStream inputStream=new FileInputStream(file);
StringBuilder stringBuilder=new StringBuilder();
while (inputStream.available()>0){
byte[] bytes=new byte[((int) file.length())];
//output stream to bytes
inputStream.read(bytes);
stringBuilder.append(new String(bytes));
}
System.out.println(stringBuilder.toString());
inputStream.close();
return stringBuilder.toString();
}
private static String getNIORead()throws Exception{
//get file ,可读写模式文件
RandomAccessFile randomAccessFile=new RandomAccessFile("/Users/code/nio/test.txt","rw");
//get channel
FileChannel fileChannel=randomAccessFile.getChannel();
//声明缓冲区
ByteBuffer byteBuffer=ByteBuffer.allocate((int) randomAccessFile.length());
//文件通过渠道读取到缓冲区
int size=fileChannel.read(byteBuffer);
StringBuilder stringBuilder=new StringBuilder(size);
while (size>0){
//缓冲区更换为读取模式,并将位点重置为0
byteBuffer.flip();
while (byteBuffer.hasRemaining()){
stringBuilder.append(byteBuffer.get());
}
byteBuffer.clear();
size=fileChannel.read(byteBuffer);
}
randomAccessFile.close();
fileChannel.close();
System.out.println(stringBuilder.toString());
return stringBuilder.toString();
}
从上面的例子来看,单纯的从代码层面可以发现之前的传统IO方式比NIO在操作基本文本时相比要少一些代码。主要原因在于NIO增加了缓冲区的概念,而传统的IO方式操作基本上只需要File和InputStream两个对象参与。而缓冲区的意义在于在之前的传统IO的基础上可以自由在缓冲区进行操作不受限制,传统IO操作的对象是对应的流(从开始直至读取完成)。在此基础上NIO相比来说支持非阻塞的IO操作和selector模型的引用,传统IO在每次进行IO操作都会建立一个线程来处理。在引用selector模型之后这种情况就可以使用一个线程来单独处理channel的注册和数据读取操作。这样来说NIO在处理并发访问时,支持更多的多线程连接请求的情况也避免了线程因为cpu轮转带来的性能损耗。
3.NIO常用组件
Selector
selector 模型提供了一个单独维护监听所有渠道链接的线程,由他来轮询发现是否可以进行数据IO操作。
上图传统模式在客人和服务员之间都是一对一,延伸在IO操作中应用程序发起IO操作时都会分别申请一个线程来进行处理。而右侧的多路复用模式中,客人来了之后都会在前台进行注册。服务员只会在特定时间和前台进行交互获取需要点菜服务的客人,并可以服务多个客人。延伸到selector模型中,可以转化以下这个图:

如图为单线程selector复用多channel模型。channel 注册到selector上,调用selector 方法之后,依据本地环境不一样返回selector的poll方法阻塞轮询直至有事件返回结果则进行处理。
Buffer
Buffers从字面上来讲就是一个缓冲区,类似于普通IO中的InputStream用于和NIO Channel交互。Channel中读取数据到buffers里,或者Buffer把数据写入到Channels中。与传统IO不同的是Buffer有一个位点的概念可提供位点前后的位置读取。具体的参数为:position,limit.
常见的实现有:
Channel
用户传输数据的渠道,可读可写。主要实现有如下几类:
- FileChannel :从文件中读写数据。
- DatagramChannel :能通过UDP读写网络中的数据。
- SocketChannel :能通过TCP读写网络中的数据。
- ServerSocketChannel:可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel。
4.总结
本节解决了几个在学习NIO之前所需要了解的内容和概念,并简单介绍了NIO的和IO的区别以及NIO常用组件。
下次将继续分享:有关NIO的实现机制。