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并由程序进行处理。

Stream IO vs NIO.PNG

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不属于。

Channel继承关系.PNG

Selector的主要方法:

  1. open():静态方法,创建Selector对象
  2. select():查询IO准备就绪的Channel

Channel

Channel是NIO中数据处理的入口,每个Channel对应着一个IO的底层数据Buffer,负责对Buffer的写入或读取操作。根据IO类型不同,Java中提供了如下通用Channel实现。

  1. FileChannel:文件读写操作
  2. SocketChannel:TCP Socket连接
  3. ServerSocketChannel:TCP Socket服务器端,处理监听端口建立Socket的accept事件
  4. DatagramChannel:UDP连接

SocketChannel的主要方法:

  1. register():向Selector注册Channel感兴趣的事件,事件包括:

    1. OP_ACCEPT:ServerSocketChannel收到客户端连接事件
    2. OP_CONNECT:客户端连接服务器成功事件
    3. OP_READ:Channel收到数据,可读事件
    4. OP_WRITE:Channel可写事件
  2. open():静态方法,建立Socket Channel通道
  3. read():从Channel中读取数据,写入Buffer
  4. write():把Buffer中数据写入Channel
  5. 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清除数据,准备写入。

Buffer.PNG

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的主要方法:

  1. allocate():静态方法,分配Buffer空间
  2. allocateDirect():静态方法,分配Native堆Buffer空间
  3. flip():调整position和limit到读取数据状态
  4. clear():清空数据,为再次写入数据准备
  5. rewind():设置position为0,重置读取位置
  6. 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;
}

乘着风
107 声望12 粉丝

五岁时,妈妈告诉我,人生的关键在于快乐。上学后,人们问我长大了要做什么,我写下“快乐”。他们告诉我,我理解错了题目,我告诉他们,他们理解错了人生。——约翰·列侬


引用和评论

0 条评论