nio即new I/O是一种读写I/O的新类库,它有两个重要的概念:通道和缓冲器。通道表示数据的输入源或者输出目的地,本文只关注用来读写文件的FileChannel。缓冲器用来保存将要读写的数据,直接与通道交互的缓冲器是ByteBuffer。
基本使用
FileChannel只能通过FileInputStream、FileOutputStream以及用于即读又写的RandomAccessFile的getChannel方法产生。下面是创建FileChannel示例:
FileChannel fc = new FileOutputStream("fo.txt").getChannel();
fc = new FileInputStream("fo.txt").getChannel();
fc = new RandomAccessFile("fo.txt", "rw").getChannel();
FileChannel使用read和write方法来读写数据,将要读写的数据保存在ByteBuffer中,ByteBuffer是最基本的缓冲器,使用通道读写数据示例如下:
FileChannel fc = new FileOutputStream("fo.txt").getChannel();
fc.write(ByteBuffer.wrap("hello, world!".getBytes()));
fc.close();
ByteBuffer bb = ByteBuffer.allocate(512);
fc = new FileInputStream("fo.txt").getChannel();
fc.read(bb);
bb.flip();
while (bb.hasRemaining())
System.out.println((char)bb.get());
/*输出:
h
e
l
l
o
,
w
o
r
l
d
!
*/
上例使用两种方式创建了ByteBuffer,第一种是使用wrap方法从已有的字节数组创建ByteBuffer对象。另外一种是使用allocate方法创建固定大小的ByteBuffer。调用通道的read方法以后需要调用ByteBuffer的flip方法才能从ByteBuffer中读取数据,hasRemaining表示还有多少数据未读取,使用get()方法可以从ByteBuffer读取一个字节的数据。
缓冲器
缓冲器是nio的核心,它用来从通道读写数据,直接与通道交互的缓冲器是ByteBuffer。ByteBuffer类是一个抽象类,实际使用的是它的子类HeapByteBuffer,下面将以HeapByteBufer为例学习ByteBuffer的原理, HeapByteBuffer有几个重要的成员变量:
byte[] hb;
int position;
int limit;
int capacity;
hb表示存储字节数据的容器,position表示下一个读写位置。limit表示读写的界限,如果读写的position超过该界限就会触发异常。capacity表示缓冲器的容量即最多可以存取的字节数。
缓冲器通常使用静态方法allocate和warp方法创建:
public static ByteBuffer allocate(int capacity)
public static ByteBuffer wrap(byte[] array)
allocate用来创建一个指定容量的缓冲器,warp从已有的字节数组创建缓冲器。下图是调用ByteBuffer.allocate(10)创建缓冲器后各成员变量的值:

缓冲器最基本的存储数据的方法是put和get:
public ByteBuffer put(byte b);
public byte get();
put和get每次从缓冲器的position位置存取一个字节的数据,每次存取后position自增,下面是使用put方法的示例:
ByteBuffer bb = ByteBuffer.allocate(10);
bb.put((byte)7);
bb.put((byte)11);
bb.put((byte)13);
bb.put((byte)17);
上面代码执行完后缓冲器各成员变量的值如下图:

我们怎么获取刚刚存储的数据呢?如果直接调用get方法则从position=4的位置取出数据,这不是我们所需要的,我们需要的是position=0至3的数据,我们需要把position设置为0,有了数据的起始位置后还需一个结束位置才能确定数据是否取完,这时limit就派上用场了,把limit设置为position的值来表示结束位置,本例中limit=4,缓冲器的flip()方法就是用来干这件事情的:
public final Buffer flip() {
limit = position;
position = 0;
return this;
}
调用flip方法以后缓冲器各成员变量的值如下图:

读取数据时需要判断数据是否取完,如果position < limit表示数据尚未取完,否则数据已经取完了,缓冲器有hasRemaining方法来判断数据是否取完,hasRemaining在写数据时表示是否有空间写入数据:
public final boolean hasRemaining() {
return position < limit;
}
下面是取数据的示例代码:
ByteBuffer bb = ByteBuffer.allocate(10);
bb.put((byte)7);
bb.put((byte)11);
bb.put((byte)13);
bb.put((byte)17);
bb.flip();
while (bb.hasRemaining())
System.out.println(bb.get());
/*输出:
7
11
13
17
*/
上面代码执行完成以后缓冲器各成员变量的值如下:

如果想要重置position的位置为0可以调用rewind方法:
public final Buffer rewind() {
position = 0;
return this;
}
如果想要把缓冲器重置为初始状态可以调用clear方法:
public final Buffer clear() {
position = 0;
limit = capacity;
return this;
}
下面是调用rewind方法后缓冲器各成员变量的值:

下面是调用clear方法后缓冲器各成员变量的值:

可以看到调用rewind和clear方法不会清除之前存储数据,所以在读取缓冲器时需要确保position在正确的位置,否则可能取到错误的数据。缓冲器提供了直接修改position和limit的方法:
public final Buffer position(int newPosition);
public final Buffer limit(int newLimit);
position和limit是两个非常重要的成员变量,position表示下一个存取位置,limit表示存取界限,下一个存取位置必须小于limit否则就会抛异常,掌握每个方法调用后position和limit的位置是正确使用ByteBuffer的关键。
接下来
下一篇将学习缓冲器的基本数据类型存取、视图缓冲器、字节序。

本文深入讲解Java NIO的核心概念:通道和缓冲器。详细介绍FileChannel的创建和使用,包括读写操作,以及ByteBuffer的原理和操作流程,如put、get、flip等方法的使用。
1647

被折叠的 条评论
为什么被折叠?



