说到Java NIO, 就应该说说阻塞IO与非阻塞IO:
阻塞IO: Java中的各种IO流是阻塞的, 这意味着每当调用read()与write()方法时,该线程都会被阻塞, 直到有一些数据被读取或者数据完全被写入, 在此期间, 线程不能干任何事.
非阻塞IO(no-blocking IO): NIO是非阻塞模式, 线程从某通道发送请求读取数据, 它仅仅能得到目前能得到的数据, 如果目前没有数据, 则什么也不读取, 但是线程不会阻塞, 而是会去干其他的事. 非阻塞写也是如此. 而空闲时间一般都是用于其他通道执行IO操作, 因此在NIO中一个线程可管理多个IO通道.
Java NIO除了非阻塞特性, 也是面向缓冲的, 在读数据时, 数据被读到一个缓冲区, 需要数据时可在缓冲区读取, 并且数据读取可在缓冲区前后移动, 这增加了数据处理的灵活性.
NIO有三大核心组件: 通道(Channel), 缓冲区(Buffer), 选择器(Selector).
Channel
Channel翻译为通道, 它是一个对象, 可以通过它读取以及写入数据, 与IO中的流相似, 但是流是单向的, 如InputStream与OutputStream, 而Channel是双向的. Channel具有如下特性:
- Channel是双向的, 既可以读又可以写.
- Channel可以进行一步读写.
- Channel读写必须通过Buffer对象.
当进行Channel读时, 线程将数据从Buffer读入Channel, 当进行数据写时, 线程将数据写入Buffer.
在Java NIO中Channel主要有如下几种类型:
- FileChannel:从文件中读写数据。
- DatagramChannel:能通过UDP读写网络中的数据。
- SocketChannel:能通过TCP读写网络中的数据
- ServerSocketChannel:可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel。
Buffer
Buffer就是一个固定大小的数据存储容器, 数据存储在容器内,并用于之后的操作. Buffer 常用的一些子类如下:
- ByteBuffer
- MappedByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
可以看出, 以上的Buffer子类可以处理不同基本类型的数据.
在缓冲区中有三个中要的属性: position, limit, capacity.
position: 指定了下一个将要被写入或者读取的元素索引,它的值由get()/put()方法自动更新,在新创建一个Buffer对象时,position被初始化为0。
limit: 指定还有多少数据需要取出(在从缓冲区写入通道时),或者还有多少空间可以放入数据(在从通道读入缓冲区时)。
capacity: 指定了可以存储在缓冲区中的最大数据容量,实际上,它指定了底层数组的大小,或者至少是指定了准许我们使用的底层数组的容量。
Selector
Selector允许单线程处理多个Channel, 如果应用打开了多个Channel, 但是每个Channel的流量却很低, 就可以使用Selector.
如图所示在单线程中一个Selector管理三个Channel.
要使用Selector管理多个Channel, 就必须向Selector注册Channel, 然后调用它的select()方法调用. 这个方法会一直阻塞到某个注册的通道有事件就绪, 一旦这个方法返回,线程就可以处理这些事件.