Java输入输出系统之nio(一)通道和缓冲器

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

 

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】
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值