NIO相比BIO的优势
NIO(Non-blocking I/O,在Java领域,也称为New I/O),是一种同步非阻塞的I/O模型,也是I/O多路复用的基础,已经被越来越多地应用到大型应用服务器,成为解决高并发与大量连接、I/O处理问题的有效方式。
bio与nio
面向流与面向缓冲
Java NIO和BIO之间第一个最大的区别是,BIO是面向流的,NIO是面向缓冲区的。 JavaIO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。Java NIO的缓冲读取方法略有不同。数据读取到一个缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。
阻塞IO与非阻塞IO
Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或write()时,该线程被阻塞,直到有数据被读取或者数据写入。该线程在阻塞期间不能做其他事情。而Java NIO的非阻塞模式,如果通道没有东西可读,或不可写,读写函数马上返回,而不会阻塞,这个线程可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程可以管理多个输入和输出通道(channel),即IO多路复用的原理。
零拷贝
在传统的文件IO操作中,我们都是调用操作系统提供的底层标准IO系统调用函数read()、write() ,此时调用此函数的进程(在JAVA中即java进程)由当前的用户态切换到内核态,然后OS的内核代码负责将相应的文件数据读取到内核的IO缓冲区,然后再把数据从内核IO缓冲区拷贝到进程的私有地址空间中去,这样便完成了一次IO操作。
IO
而NIO的零拷贝与传统的文件IO操作最大的不同之处就在于它虽然也是要从磁盘读取数据,但是它并不需要将数据读取到OS内核缓冲区,而是直接将进程的用户私有地址空间中的一部分区域与文件对象建立起映射关系,这样直接从内存中读写文件,速度大幅度提升。
NIO
详细的解析,之后会有单独的博客进行讲解NIO的核心部分 Java NIO主要由以下三个核心部分组成:
- Channel
- Buffer
- Selector
![](https://pic1.zhimg.com/80/v2-a75687508fef532bd36d53f4ee531ce6_1440w.png)
- FileChannel(file)
- DatagramChannel(UDP)
- SocketChannel(TCP)
- ServerSocketChannel(TCP)
最后两个channel的关系。通过 ServerSocketChannel.accept() 方法监听新进来的连接。当 accept()方法返回的时候,它返回一个包含新进来的连接的 SocketChannel。因此, accept()方法会一直阻塞到有新连接到达。通常不会仅仅只监听一个连接,在while循环中调用 accept()方法.//打开 ServerSocketChannel ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.socket().bind(new InetSocketAddress(9999)); while(true){ SocketChannel socketChannel = serverSocketChannel.accept(); //do something with socketChannel... } //关闭ServerSocketChannel serverSocketChannel.close(); Buffer 缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该块内存。 Java NIO里关键的Buffer实现:
- ByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
- capacity
- position
- limit
![](https://picx.zhimg.com/80/v2-26bfd011ae09c369140fd794f6764f1f_1440w.png)
![](https://picx.zhimg.com/80/v2-1d24d59dfeb0bf949d6475dd2157951e_1440w.png)
- macosx:KQueueSelectorProvider
- solaris:DevPollSelectorProvider
- Linux:EPollSelectorProvider (Linux kernels >= 2.6)或PollSelectorProvider
- windows:WindowsSelectorProvider
- 采用边缘触发方式
- netty epoll transport暴露了更多的nio没有的配置参数,如 TCP_CORK, SO_REUSEADDR等等。
- C代码,更少GC,更少synchronized
- NIO 模型中通常会有两个线程,每个线程绑定一个轮询器 selector ,在上面例子中serverSelector负责轮询是否有新的连接,clientSelector负责轮询连接是否有数据可读
- 服务端监测到新的连接之后,不再创建一个新的线程,而是直接将新连接绑定到clientSelector上,这样就不用BIO模型中1w 个while循环在阻塞,参见(1)
- clientSelector被一个 while 死循环包裹着,如果在某一时刻有多条连接有数据可读,那么通过clientSelector.select(1)方法可以轮询出来,进而批量处理,参见(2)
- 数据的读写面向 Buffer,参见(3)
public class NIOServer { public static void main(String[] args) throws IOException { Selector serverSelector = Selector.open(); Selector clientSelector = Selector.open(); new Thread(() -> { try { // 对应IO编程中服务端启动 ServerSocketChannel listenerChannel = ServerSocketChannel.open(); listenerChannel.socket().bind(new InetSocketAddress(8000)); listenerChannel.configureBlocking(false); listenerChannel.register(serverSelector, SelectionKey.OP_ACCEPT); while (true) { // 监测是否有新的连接,这里的1指的是阻塞的时间为 1ms if (serverSelector.select(1) > 0) { Set<SelectionKey> set = serverSelector.selectedKeys(); Iterator<SelectionKey> keyIterator = set.iterator(); while (keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); if (key.isAcceptable()) { try { // (1) 每来一个新连接,不需要创建一个线程,而是直接注册到clientSelector SocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept(); clientChannel.configureBlocking(false); clientChannel.register(clientSelector, SelectionKey.OP_READ); } finally { keyIterator.remove(); } } } } } } catch (IOException ignored) { } }).start(); new Thread(() -> { try { while (true) { // (2) 批量轮询是否有哪些连接有数据可读,这里的1指的是阻塞的时间为 1ms if (clientSelector.select(1) > 0) { Set<SelectionKey> set = clientSelector.selectedKeys(); Iterator<SelectionKey> keyIterator = set.iterator(); while (keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); if (key.isReadable()) { try { SocketChannel clientChannel = (SocketChannel) key.channel(); ByteBuffer byteBuffer = ByteBuffer.allocate(1024); // (3) 面向 Buffer clientChannel.read(byteBuffer); byteBuffer.flip(); System.out.println(Charset.defaultCharset().newDecoder().decode(byteBuffer) .toString()); } finally { keyIterator.remove(); key.interestOps(SelectionKey.OP_READ); } } } } } } catch (IOException ignored) { } }).start(); } }
标签:NIO,效率高,Buffer,Selector,线程,IO,position,搞懂 From: https://www.cnblogs.com/fcjedorfjoeij/p/17517875.html