在上一篇中,我们介绍了NIO中的两个核心对象:缓冲区和通道。本文为NIO入门学习的第二篇,将会分析NIO中的缓冲区Buffer的内部原理。
在谈到缓冲区时,我们说缓冲区对象本质上是一个数组,但它其实是一个特殊的数组,缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况,如果我们使用get()方法从缓冲区获取数据或者使用put()方法把数据写入缓冲区,都会引起缓冲区状态的变化。
在缓冲区中,最重要的属性有下面三个,它们一起合作完成对缓冲区内部状态的变化跟踪:
position
指定了下一个将要被写入或者读取的元素索引。在从Channel读取数据到Buffer时,position变量用来跟踪截止目前为止从Channel中读出了多少数据,在从Buffer中向Channel写数据时,position变量用来跟踪截止目前为止向Channel写入了多少数据。
limit
在从Channel中读取数据到Buffer中时,limit变量指示了还剩多少空间可供存放数据,在从Buffer向Channel写数据时,limit变量指示了还剩多少数据可以写入。position正常情况下小于或者等于limit。
capacity
指示Buffer最多能够存储的数据。实际上,它指示了底层array的容量,或者至少是底层array允许使用的空间数量。Limit永远不会大于capacity。
接下来我们将逐一检查每个细节,并且也看看为什么这样的设计适合典型的读/写(输入/输出)处理。我们假设从一个Channel拷贝数据到另一个Channel。
首先新建一个容量大小为10的ByteBuffer对象,在初始化的时候,position设置为0,如果我们读一些数据到缓冲区中,那么下一个读取的数据就进入索引为0的字节。如果我们从缓冲区写一些数据,从缓冲区读取的下一个字节就来自索引为0的字节。limit和 capacity被设置为10,在以后使用ByteBuffer对象过程中,capacity的值不会再发生变化,而其它两个将会随着使用而变化。
现在我们可以从读通道中读取一些数据到缓冲区中。如果读取到4个字节数据,则此时position的值为4,即下一个将要被写入的字节索引为4,而limit仍然是10,如下图所示:
下一步把读取到的数据写入到写通道中,在此之前必须调用flip()方法,该方法将会完成两件事情:
1. 把limit设置为当前的position值
2. 把position设置为0
position 被设置为 0,这意味着我们得到的下一个字节是第一个字节。limit 已被设置为原来的 position,这意味着它包括以前读到的所有字节,并且一个字节也不多,如下图所示:
我们现在可以将数据从缓冲区写入通道了,这会导致position的增加而limit保持不变,但position不会超过limit的值,所以在读取我们之前写入到缓冲区中的4个字节之后,position和limit的值都为4,如下图所示:
在数据写入到写通道完毕后,调用clear()方法能够把所有的状态变化设置为初始化时的值,该方法将会完成两件事情:
1. 把limit设置为capacity值
2. 把position设置为0
clear()方法会重置Buffer以便接收更多的字节,如下图所示:
最后我们用一段代码来验证这个过程,如下所示:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class TestBufferField {
public static void main(String[] args) throws Exception {
FileInputStream fileInStream = new FileInputStream("D:\\test1.txt");
// 获取读通道
FileChannel fcin = fileInStream.getChannel();
FileOutputStream fileOutStream = new FileOutputStream("D:\\test2.txt");
// 获取写通道
FileChannel fcout = fileOutStream.getChannel();
// 创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(10);
output("初始化", buffer);
// 从通道fcin读取数据到缓冲区
fcin.read(buffer);
output("调用read()", buffer);
// 重设buffer,将limit设置为position,然后将position设置为0
buffer.flip();
output("调用flip()", buffer);
// 将缓冲区中的数据写入通道fcout
fcout.write(buffer);
output("调用write()", buffer);
// 重设buffer,将limit设置为容量capacity,position设置为0
buffer.clear();
output("调用clear()", buffer);
fileInStream.close();
fileOutStream.close();
}
public static void output(String step, Buffer buffer) {
System.out.println(step + " : ");
System.out.print("position: " + buffer.position() + ", ");
System.out.print("limit: " + buffer.limit() + ", ");
System.out.println("capacity: " + buffer.capacity());
System.out.println();
}
}
输出结果为:
这与我们上面演示的过程一致。在后面的文章中,我们继续介绍NIO中关于缓冲区一些更高级的使用。
参考: