javaNio学习--基本概念
一句话概述
Java Nio 可以做到用一个线程处理多个网络请求。假设有1000个请求过来,根据实际的情况可以分配20或者80个线程进行处理,不必像阻塞io那样需要启动1000个线程进行处理。
基本介绍
- Nio与Io 有着相同的作用与目的,但是使用方式完全不同,Nio支持面向缓冲区、基于通道的Io操作。Nio为非阻塞Io,传统Io的read和write操作只能阻塞进行,线程在阻塞期间不能干其他的事;比如在调用socket.read时,如果服务器一直没发送数据过来,线程就一直阻塞等待数据的传输。
- Nio的三大组件:Channel、Buffer、Selector
- Java Nio的非阻塞模式,是一个线程从某通道发送数据或者读取数据,但它仅能得到目前可用的数据,如果目前没有数据,就什么也不会获取,而不是让线程阻塞,在数据可读取之前,该线程可以继续做其他的事;非阻塞写也是如此,一个线程请求写入数据到某通道,但不需要完全等待写完,该线程就可以继续做其他的事情。
Nio 与Bio的比较
- Bio以流的方式处理数据,而Nio以块的方式处理数据,块io的效率比流io的高。
- Bio是阻塞的,Nio为非阻塞的
- Bio基于字节流与字符流进行操作,Nio基于通道和缓冲区进行操作,数据从通道写入缓冲区,或者从缓冲区读取到通道中,Selector用与监听多个通道的事件(比如链接请求、数据到达请求),因此单个线程就能监听多个客户端通道。
Bio的缺点也是显而易见的,当有大量请求过来时,需要建立很多的线程用于处理。
Nio 三大核心组件的示意图
Buffer
缓冲区本质上是一块可以读写数据的一块内存。
Channel
通道相比于流,及可以从通道中读取数据,也可以向通道中写入数据。但流的读写通常是单向的。通道可以非阻塞的读取和写入通道,也可以读取和写入缓冲区,也支持一异步的读写。
Selector
能够检查一个或多个Nio通道(监控多个文件描述符含socket连接),并确定哪些通道已经准备好读取或写入。这样一个单独的线程可以管理多个channel,从而管理多个网络链接,提高效率。基于底层选择器的系统调用,需要底层操作系统的支持。
- 每个channel都对应一个buffer
- 每个线程都对应一个selector,一个selector对应多个channel
- 程序切换到那个channel由事件决定
- selector根据不同的事件在各个channel上进行切换
- buffer就是一块内存,底层是一个数组
- 数据的读取和写入是由buffer完成的,Bio中要么事输入流要,要么是输出流,不能双向,但Nio的buffer是可以读写的。
- Java Nio的核心是 Channel和Buffer。通道表示打开到Io的设备(文件、套接字)的连接。使用Java Nio首先获取连接io设备的channel ,并操作该channel对应的buffer,实现数据的处理。简言之:channel负责数据的传输,buffer负责数据的存储。
buffer
Nio的buffer本质上是一个内存块,既可以写入数据也可以读取数据。NIO中,有8种缓冲区类,分别是ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer、MappedByteBuffer。
buffer有三个重要的属性:
- capacity 表示buffer的容量,在初始化的时候会分配译者数组,数组分配好后其大小也就不可变了。
- position 表示当前的位置,在buffer处于读取可写入的时候其含义是不同的。
- limit 表示可以写入或读取的数据量的最大值,在写入的情况下limit=cpacity,在读取的情况下,limit等于可读取数据的值。
channel
java nio 中的一个socket连接可以使用channel表示,更广泛的来说,一个通道可以表示一个底层的文件描述符,例如硬件设备、网络链接、文件等。Java nio中对于不同的网络传输协议对应于不同的Nio Channel:
- FileChannel:文件通道,用于文件的读写;但是不能够设置为非阻塞的方式,因此不能使用selector。
- SocketChannel:套接字通道,用于套接字tcp的连接的数据读写。
- ServerSocketChannel:服务器套接字通道,允许我们监听套接字的连接请求,为每一个监听到的请求创建一个SocketChannel通道。
- DatagramChannel:数据报通道,用于UDP的数据读写。
selector
选择器提供了独特的API方法,能够选出(select)所监控的通道已经发生了哪些IO事件,包括读写就绪的IO操作事件;通道与选择器之间的关联通过register的方式完成,register方法有两个参数:第一个参数指定通道注册到的选择器实例;第二个参数指定选择器要监控的IO事件类型:
- 可读:SelectionKey.OP_READ。
- 可写:SelectionKey.OP_WRITE。
- 连接:SelectionKey.OP_CONNECT。
- 接收:SelectionKey.OP_ACCEPT。(用于监听新的连接,只用于serverSocketChannel)
//轮询,选择感兴趣的IO就绪事件(选择键集合)
while (selector.select() > 0) {
Set selectedKeys = selector.selectedKeys();
Iterator keyIterator = selectedKeys.iterator();
while(keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
//根据具体的IO事件类型执行对应的业务操作
if(key.isAcceptable()) {
//IO事件:ServerSocketChannel服务器监听通道有新连接
} else if (key.isConnectable()) {
//IO事件:传输通道连接成功
} else if (key.isReadable()) {
//IO事件:传输通道可读
} else if (key.isWritable()) {
//IO事件:传输通道可写
}
//处理完成后,移除选择键
keyIterator.remove();
}
}
这里需要注意:注册到选择器的通道必须处于非阻塞模式下,否则将抛出IllegalBlockingModeException异常。这意味着,FileChannel不能与选择器一起使用,因为FileChannel只有阻塞模式,不能切换到非阻塞模式;而socket相关的所有通道都可以。其次,一个通道并不一定支持所有的四种IO事件。例如,服务器监听通道ServerSocketChannel仅支持Accept(接收到新连接)IO事件,而传输通道SocketChannel则不同,它不支持Accept类型的IO事件。