面向流的I/O通常是非常慢的,而且InputStream使用的read方法从流中读取数据时,如果数据源中没有数据,它将会一直等待,处于阻塞状态,那么程序中其他处理将会被影响。为了解决I/O流的一些问题,就有了NIO,它新增了很多用于处理I/O的类,并且java.io包中的很多类以NIO为基础进行了改写。NIO与原来的I/O有同样的功能和目的,但是处理I/O的方式不同,块I/O在效率上比流I/O高许多。
Channel(通道)和Buffer(缓冲)是NIO中的核心对象。其中,Buffer可以看作是一个容器对象,它的本质是一个数组,发送给一个Chnnel的所有对象都必须先放到Buffer中,而从通道中读取的数据,也要先读到Buffer中,这就是NIO与之前的I/O不同的一个地方。在原来的java.io包中,对数据的操作全都是直接写入Stream中,或者从中取出。需要注意的是,Buffer是非线程安全类,非线程安全类效率较高。
最常用的Buffer类是ByteBuffer,他可以在底层字节数组上进行get/set操作。除了ByteBuffer外,对应其他简单的数据类型都有相应的缓冲类:
- MappedByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
Boolean类型除外。ByteBuffer是唯一支持对其他类型进行读写的缓冲类,因为其他类都是特定于类型的。一旦连接上,就可以使用ByteBuffer对象从通道中读取数据或者从中取出。
/**
* <code>UNSET_MARK</code> means the mark has not been set.
*/
static final int UNSET_MARK = -1;
/**
* The capacity of this buffer, which never changes.
*/
final int capacity;
/**
* <code>limit - 1</code> is the last element that can be read or written.
* Limit must be no less than zero and no greater than <code>capacity</code>.
*/
int limit;
/**
* Mark is where position will be set when <code>reset()</code> is called.
* Mark is not set by default. Mark is always no less than zero and no
* greater than <code>position</code>.
*/
int mark = UNSET_MARK;
/**
* The current position of this buffer. Position is always no less than zero
* and no greater than <code>limit</code>.
*/
int position = 0;
/**
* The log base 2 of the element size of this buffer. Each typed subclass
* (ByteBuffer, CharBuffer, etc.) is responsible for initializing this
* value. The value is used by JNI code in frameworks/base/ to avoid the
* need for costly 'instanceof' tests.
*/
final int _elementSizeShift;
/**
* For direct buffers, the effective address of the data; zero otherwise.
* This is set in the constructor.
*/
final long effectiveDirectAddress;
在ByteBuffer中,容量(capacity)、界限(limit)、位置(position)、标记(mark)是三个重要概念:
capacity
作为一个内存块,Buffer有一个固定的大小值,也叫“capacity”.它表示可以存储在缓冲区中的最大数据容量。你只能往里写capacity个byte、long,char等类型。一旦Buffer满了,需要将其清空(通过读数据或者清除数据)才能继续写数据往里写数据。
capacity一旦初始化后就不会改变,也不可能为负值,其值一直为常量。在使用中我们一般使用Buffer的抽象子类ByteBuffer.allocate()方法,实际上是生成ByteArrayBuffer类。
limit
第一个不应该被读出或者写入的缓冲区位置索引。
在读模式下,Buffer的limit表示你最多能从Buffer里读多少数据。 写模式下,limit等于Buffer的capacity。
当切换Buffer到读模式时, limit表示你最多能读到多少数据。因此,当切换Buffer到读模式时,limit会被设置成写模式下的position值。换句话说,你能读到之前写入的所有数据(limit被设置成已写数据的数量,这个值在写模式下就是position)
position
当你写数据到Buffer中时,position表示当前的位置。初始的position值为0.当一个byte、long等数据写到Buffer后, position会向前移动到下一个可插入数据的Buffer单元。position最大可为capacity – 1。
当读取数据时,也是从某个特定位置读。当将Buffer从写模式切换到读模式,position会被重置为0。当从Buffer的position处读取数据时,position向前移动到下一个可读的位置。
Mark
一个备忘位置。标记在设定前是未定义的(undefined)。使用场景是,假设缓冲区中有 10 个元素,position 目前的位置为 2(也就是如果get的话是第三个元素),现在只想发送 6 - 10 之间的缓冲数据,此时我们可以 buffer.mark(buffer.position()),即把当前的 position 记入 mark 中,然后 buffer.postion(6),此时发送给 channel 的数据就是 6 - 10 的数据。发送完后,我们可以调用 buffer.reset() 使得 position = mark,因此这里的 mark 只是用于临时记录一下位置用的。
在使用 Buffer 时,我们实际操作的就是这四个属性的值。我们发现,Buffer 类并没有包括 get() 或 put() 函数。但是,每一个Buffer 的子类都有这两个函数,但它们所采用的参数类型,以及它们返回的数据类型,对每个子类来说都是唯一的,所以它们不能在顶层 Buffer 类中被抽象地声明。它们的定义必须被特定类型的子类所遵从。若不加特殊说明,我们在下面讨论的一些内容,都是以 ByteBuffer 为例,当然,它当然有 get() 和 put() 方法了。
public abstract class ByteBuffer extends Buffer implements Comparable {
// This is a partial API listing
public abstract byte get( );
public abstract byte get (int index);
public abstract ByteBuffer put (byte b);
public abstract ByteBuffer put (int index, byte b);
}