IO一般是指Java 文件IO,采用面向流的操作,每次从流中读/写一个或多个字节,直至读/写完所有字节,所以它不能前后移动流中的数据。 IO可以分为文件IO和网络IO,文件IO是中的read和write都是阻塞的,网络IO中的read、write、accept等都是阻塞的。IO流是阻塞的又叫做BIO
NIO是一种同步非阻塞,主要有三大核心部分:Channel(通道),Buffer(缓冲区), Selector(多路复用器)。 NIO采用面向缓冲区的操作即(基于Channel和Buffer(缓冲区)进行操作),从流中读/写时先将数据都缓存在缓冲区中,需要时可在缓冲区中前后移动,一个线程请求读/写入字节或者字符流到某通道时(有需要可以进行全双工数据通信的方式)时,所需数据流会被写入缓冲区,线程再从缓存区拿数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,这个线程同就会去做别的事情(非阻塞)。Selector(多路复用器)会监听多个通道的事件(比如:连接打开,数据到达),有数据到了再告诉线程回来处理数据。 NIO分为文件NIO和网络NIO。
Selector 一般称 为选择器 ,当然你也可以翻译为 多路复用器 。它是Java NIO核心组件中的一个,用于检查一个或多个NIO Channel(通道)的状态是否处于可读、可写。如此可以实现单线程管理多个channels,也就是可以管理多个网络链接。 使用Selector的好处在于: 使用更少的线程来就可以来处理通道了, 相比使用多个线程,避免了线程上下文切换带来的开销。
网络NIO的ServerSocketChannel可以通过configureBlocking方法来设置非阻塞。NIO读写分为准备读写和真正读写,而在准备读写的阶段:将通道中SelectionKey中接收请求事件、读事件、写事件、连接事件这些注册到Selector选择器上,此时每个键对应的通道已经做好了I/O操作的准备(注册这个操作本身是非阻塞的)。 NIO一个重要的特点是:socket主要的读、写、注册和接收函数,在等待就绪阶段都是非阻塞的,真正的I/O操作是同步阻塞的(消耗CPU但性能非常高)。 NIO是采用了Selector选择器的概念,对于前面准备好I/O操作的通道的键。选择器使用select方法进行死循环轮询使用的键SelectionKey找对应的事件和通道。因为select方法是阻塞的并且进行死循环,所以只能是单线程的操作。然后通过SelectionKey中的方法测试通道是否可读可写等操作再进行对缓冲区读写。
JDK1.7发布了NIO2.0,这就是真正意义上的异步非阻塞,服务器的实现模式为多个有效请求一个线程,客户端的IO请求都是由OS先完成再通知服务器应用去启动线程处理(回调)
IO和NIO通俗区别 流和缓冲区对比:流就是每次去购物,一个东西一个东西去拿到收银台付款。而缓冲区就是将每个东西都放到购物车,然后一次拿到收银台付款。
阻塞:线程持续等待资源中数据读/写完成,直到返回响应结果。 非阻塞:线程直接返回结果,不会持续等待资源准备数据结束后才响应结果。
同步(Synchronous)与异步(Asynchronous)是指访问数据的机制,或指一次方法的调用或者一次请求的调用。 IO操作举例: 同步一般指主动请求并等待IO操作完成的方式。 异步则指主动请求数据后便可以继续处理其它任务,随后等待IO操作完毕的通知。 老王烧开水: 1、普通水壶煮水,站在旁边,主动的看水开了没有?同步的阻塞 2、普通水壶煮水,去干点别的事,每过一段时间去看看水开了没有,水没开就走人。 同步非阻塞 3、响水壶煮水,站在旁边,不会每过一段时间主动看水开了没有。如果水开了,水壶自动通知他。 异步阻塞 4、响水壶煮水,去干点别的事,如果水开了,水壶自动通知他。异步非阻塞
以下内容转载自:https://www.jianshu.com/p/5bb812ca5f8e
使用纯粹的NIO设计相较IO设计,数据处理也受到影响。
在IO设计中,我们从InputStream或 Reader逐字节读取数据。假设你正在处理一基于行的文本数据流,例如:
Name: Anna Age: 25 Email: anna@mailserver.com Phone: 1234567890该文本行的流可以这样处理:
InputStream input = ... ; // get the InputStream from the client socket BufferedReader reader = new BufferedReader(new InputStreamReader(input)); String nameLine = reader.readLine(); String ageLine = reader.readLine(); String emailLine = reader.readLine(); String phoneLine = reader.readLine();请注意处理状态由程序执行多久决定。换句话说,一旦reader.readLine()方法返回,你就知道肯定文本行就已读完, readline()阻塞直到整行读完,这就是原因。你也知道此行包含名称;同样,第二个readline()调用返回的时候,你知道这行包含年龄等。 正如你可以看到,该处理程序仅在有新数据读入时运行,并知道每步的数据是什么。一旦正在运行的线程已处理过读入的某些数据,该线程不会再回退数据(大多如此)。下图也说明了这条原则: 而一个NIO的实现会有所不同,下面是一个简单的例子:
ByteBuffer buffer = ByteBuffer.allocate(48); int bytesRead = inChannel.read(buffer);注意第二行,从通道读取字节到ByteBuffer。当这个方法调用返回时,你不知道你所需的所有数据是否在缓冲区内。你所知道的是,该缓冲区包含一些字节,这使得处理有点困难。假设第一次 read(buffer)调用后,读入缓冲区的数据只有半行,例如,“Name:An”,你能处理数据吗?显然不能,需要等待,直到整行数据读入缓存,在此之前,对数据的任何处理毫无意义。所以,你怎么知道是否该缓冲区包含足够的数据可以处理呢?好了,你不知道。发现的方法只能查看缓冲区中的数据。其结果是,在你知道所有数据都在缓冲区里之前,你必须检查几次缓冲区的数据。这不仅效率低下,而且可以使程序设计方案杂乱不堪。例如:
ByteBuffer buffer = ByteBuffer.allocate(48); int bytesRead = inChannel.read(buffer); while(! bufferFull(bytesRead) ) { //是自定义实现方法bufferFull bytesRead = inChannel.read(buffer); }bufferFull()方法必须跟踪有多少数据读入缓冲区,并返回真或假,这取决于缓冲区是否已满。换句话说,如果缓冲区准备好被处理,那么表示缓冲区满了。
bufferFull()方法扫描缓冲区,但必须保持在bufferFull()方法被调用之前状态相同。如果没有,下一个读入缓冲区的数据可能无法读到正确的位置。这是不可能的,但却是需要注意的又一问题。
如果缓冲区已满,它可以被处理。如果它不满,并且在你的实际案例中有意义,你或许能处理其中的部分数据。但是许多情况下并非如此。下图展示了“缓冲区数据循环就绪”:
https://www.jianshu.com/p/5bb812ca5f8e
待写