Java中有两套IO模型,分别是传统IO和JDK1.4新引入的NIO(叫New IO或 Non-Blocking IO,后者更能体现它的设计理念)。
在传统IO模型中,网络IO是基于数据流(Stream)的,数据的输入和输出分别对应InputStream和OutputStream。流数据只能逐字节的读取(或写入),直到所有数据读取(或写入)完毕,流数据只能单向的读取(或写入),无缓存区,无法对数据进行前后移动访问。
在NIO模型中,网络IO是基于Channel和IO多路复用技术,实现原理更接近OS的IO模型,网络读IO的两阶段模型(数据准备、数据拷贝)中,数据准备阶段是Non-Blocking的,Channel和Buffer配合主要处理的是数据拷贝阶段的工作。
传统IO模式,在处理数据流过程中,线程处于阻塞状态,直到流处理(读取后写入)完毕,所以一个线程只能处理一个IO任务,如果IO未准备就绪无数据(或不可写)线程只能一直等待,直到可以有数据(或可写);
NIO模式,采用IO多路复用技术,使用选择器(Selector)监控一组IO,虽然Selector线程也处于阻塞状态,但一个线程可以同时处理多个IO任务,当IO就绪时,Selector返回就绪的IO并由程序进行处理。
NIO核心设计
如上图所示,NIO的核心涉及3部分,分别为Selector、Channel和Buffer。在描述数据读写操作时,需要特别注意读写的主体,从Channel的角度,读数据是指把数据从Channel读取数据写入Buffer,写数据是指把Buffer的数据写入Channel;从Buffer的角度,写数据是指把数据写入Buffer,读数据是指从Buffer中读取数据写入Channel或转换为其他数据类型。
Selector
Selector是NIO多理复用的执行组件,系统在处理多个IO任务时,可以把每个IO任务以Channel的方式注册到Selector,通过Selector.select()统一监控向其注册的Channel的状态,select()方法会阻塞,直到其中有Channel的IO状态准备就绪有数据可读(或IO可写)时,select()返回就绪IO的Channel由程序处理相关事件。
Selector只能监听SelectableChannel类型的Channel,主要应用场景为网络IO等数据准备阶段时间较长的IO,SocketChannel属于此类型,而FileChannel不属于。
Selector的主要方法:
- open():静态方法,创建Selector对象
- select():查询IO准备就绪的Channel
Channel
Channel是NIO中数据处理的入口,每个Channel对应着一个IO的底层数据Buffer,负责对Buffer的写入或读取操作。根据IO类型不同,Java中提供了如下通用Channel实现。
- FileChannel:文件读写操作
- SocketChannel:TCP Socket连接
- ServerSocketChannel:TCP Socket服务器端,处理监听端口建立Socket的accept事件
- DatagramChannel:UDP连接
SocketChannel的主要方法:
-
register():向Selector注册Channel感兴趣的事件,事件包括:
- OP_ACCEPT:ServerSocketChannel收到客户端连接事件
- OP_CONNECT:客户端连接服务器成功事件
- OP_READ:Channel收到数据,可读事件
- OP_WRITE:Channel可写事件
- open():静态方法,建立Socket Channel通道
- read():从Channel中读取数据,写入Buffer
- write():把Buffer中数据写入Channel
- map():把Channel中部分数据或者全部数据映射成MappedByteBuffer
当Socket关闭后,会触发对端的OP_READ事件,但此时read()返回-1或throw IOexception,可以通过这两个判断来确定对端是否关闭,并调用close()方法关闭SocketChannel并执行清理工作。
Buffer
Buffer本质上是一块连续的内存区域,提供数据缓存功能和Channel对数据的读写操作能力。NIO提供了 ByteBuffer / CharBuffer / DoubleBuffer / FloatBuffer / IntBuffer / LongBuffer / ShortBuffer
Buffer类型,实现读Java基本类型的缓存支持。对Buffer的读写操作过程中,涉及3个关键参数,即postion、limit和capacity。其中position和limit在读/写操作时具有不同的意义,而capacity读写时都是表示Buffer的大小。Buffer初始处于写状态,数据写入后,通过flip切换的读取状态,读取完后,再通过clear清除数据,准备写入。
Capacity
Buffer是一个固定大小的内存缓存区,Capacity就表示其总长度。向Buffer写入的数据字节(byte/char/int/long等)总长度不能超过Capacity。当Buffer写满时,需要(读取)清空后才能继续写入。
Position
Position初始位置为0,当Buffer读写模式切换时,也会自动重置为0。
读取模式时,Position表示当前读取数据的位置,读取当前位置数据后,Position自动移到下一个可读位置,读取位置不能超过Limit。
写入模式时,Position表达当前写入数据的位置,在当前位置写入数据后,Position自动移到下一个写入位置,写入位置不能超过Capacity - 1。
Limit
读模式时,Limit表示Buffer中可读取数据长度,当Buffer从写模式切换到读模式时,Limit值等于写模式时的Position。
写模式时,Limit表示可写入数据总长度,其值等于Capacity - 1。
Buffer的主要方法:
- allocate():静态方法,分配Buffer空间
- allocateDirect():静态方法,分配Native堆Buffer空间
- flip():调整position和limit到读取数据状态
- clear():清空数据,为再次写入数据准备
- rewind():设置position为0,重置读取位置
- put()/get():写入和读取数据
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
public final Buffer rewind() {
position = 0;
mark = -1;
return this;
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。