你还在用IO吗?
回顾传统
有必要了解传统的I/O操作的方式。以网络应用为例,传统方式需要监听一个ServerSocket,接受请求的连接为其提供服务(服务通常包括了处理请求并发送响应),下图是服务器的生命周期图,其中标有粗黑线条的部分表明会发生I/O阻塞。
?

?
? 线程使得服务器可以处理多个连接,但是它们也同样引发了许多问题。每个线程拥有自己的栈空间并且占用一些CPU时间,耗费很大,而且很多时间是浪费在阻塞的I/O操作上,没有有效的利用CPU。
? 选择NIO? NIO包(java.nio.*)引入了四个关键的抽象数据类型,它们共同解决传统的I/O类中的一些问题。
1. Buffer:它是包含数据且用于读写的线形表结构。其中还提供了一个特殊类用于内存映射文件的I/O操作。
2. Charset:它提供Unicode字符串影射到字节序列以及逆影射的操作。
3. Channels:包含socket,file和pipe三种管道,它实际上是双向交流的通道,你可能注意到现有的java.io类中没有一个能够读写Buffer类型,所以NIO中提供了Channel类来读写Buffer。channel就是一个读写的管道,通过管道的读写来完成IO操作。channel分为ServerSocketChannel和SocketChannel,前者用于监听,获得客户端的连接,后者直接用于操作IO,来看看Channel如何进行Socket操作
4. Selector:它将多元异步I/O操作集中到一个或多个线程中,在过去的阻塞I/O中,我们一般知道什么时候可以向stream中读或写,因为方法调用直到stream准备好时返回。但是使用非阻塞通道,我们需要一些方法来知道什么时候通道准备好了。在NIO包中,设计Selector就是为了这个目的。SelectableChannel可以注册特定的事件,而不是在事件发生时通知应用,通道跟踪事件。然后,当应用调用Selector上的任意一个selection方法时,它查看注册了的通道看是否有任何感兴趣的事件发生?
String host = 127.0.0.1;InetSocketAddress socketAddress = new InetSocketAddress(host, 80);ServerSocketChannel ssc = ServerSocketChannel.open();//配置channel的阻塞模式ssc.configureBlocking(false);ssc.connect(socketAddress);Selector selector = Selector.open(); //将ServerSocketChannel注册到selector上,selector可以检测多路channelssc.register(selctor, SelectionKey.OP_ACCEPT);while(true){ //阻塞等待事件响应 selector.select(); Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<SelectionKey> it = selectionKeys.iterator(); //获得多路的channel,这些channel此时都已准备就绪,工作在非阻塞模式,可以非阻塞读写 while (it.hasNext()) { SelectionKey key = it.next(); it.remove(); if (key.isAcceptable()) { ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel(); //此时的accept是非阻塞的,立马返回 SocketChannel channel = serverSocketChannel.accept(); channel.configureBlocking(false); //可以不断得将这些channel注册成不同的类型,使之即可读,又可写 channel.register(selector, SelectionKey.OP_READ); } else if (key.isReadable()) { SocketChannel channel = (SocketChannel) key.channel(); //非阻塞读,立马返回数据 channel.read(buffer); //... SelectionKey selectionKey = channel.register(selector, SelectionKey.OP_WRITE); selectionKey.attach(new HandleClient(clientName)); } else if (key.isWritable()) { SocketChannel channel = (SocketChannel) key.channel(); HandleClient handleClient = (HandleClient) key.attachment(); ByteBuffer buffer = handleClient.readBlock(); } } }?
在这种模式下,原来负责端口监听的accept()方法换成了select()方法,两者都是阻塞的,本质上没有分别.区别在于select()之后返回的所有channel都是非阻塞的,都是可以马上读写的。而accept()之后的channel则是阻塞的,不能保证此时返回的channel的读写能够马上返回。因此,NIO的非阻塞方式就可以设置比较少的线程,因为这些线程拿到的channel都是立马可以读写的,这些线程的工作都是满负荷的,效率高。反之,阻塞方式需要创建同样较多的线程,因为这些线程很多都处于阻塞休眠状态,大家都不是满负荷在工作。这样NIO的优点就很明显了。