借助之前使用FileVisitor<T>接口来删除文件的一个契机,来开始学习了解一下NIO的文件读取。
简单介绍一下他的这个缓冲区
一、简单介绍
Buffer是各种缓冲区类的点击父类,它包含的子类有:ByteBuffer、CharBuffer、FloatBuffer、IntBuffer等等。
以ByteBuffer为例,因为NIO中的文件读写是以ByteBuffer为缓冲区的。下面来看一下它的信息:
public abstract class ByteBuffer
extends Buffer
implements Comparable<ByteBuffer>
ByteBuffer本身是一个抽象类,继承了Buffer,实现了Comparable<ByteBuffer>接口,也就是说ByteBuffer具有比较功能。由于ByteBuffer是一个抽象类,所以ByteBuffer本身不能被实例化,但又并不是由其子类来进行实例化,而是由静态的工厂方法来实例化的。
使用静态工厂方法实例化大体分为以下几种:
1、allocateDirect(int capacity)
通过allocateDirect工厂方法来创建一个直接字节缓冲区,
此方法返回的缓冲区通常比非直接缓冲区具有更高的分配和释放成本。 直接缓冲区的内容可能驻留在正常的垃圾回收堆之外(内存的开销是不再JVM中的),因此它们对应用程序的内存占用的影响可能不明显。 因此,建议直接缓冲区主要用于受基础系统本机I / O操作影响的大型长寿命缓冲区。 一般来说,最好只在产生程序性能可测量的增益时才分配直接缓冲区。
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
2、allocate(int capacity)
通过allocate静态工厂方法来创建一个非直接字节缓冲区,此方法返回的缓冲区内存开销是由JVM负责的。
ByteBuffer buffer = ByteBuffer.allocate(1024);
3、wrap(byte[] array)
将一个字节数组包装到缓冲区里,那么这个缓冲区将是由这个字节数组支持的,对缓冲区的修改,直接导致这个数组被修改,反之也是一样的。起始位置为0,缓冲区的容量与限制为这个数组的长度:array.length。
byte[] array = new byte[1024];
ByteBuffer buffer = ByteBuffer.wrap(array);
4、wrap(byte[] array,int offset,int length)
这与第三种方式一样,只是设置了偏移量:起始位置为offset,容量为length,限制为offset+length.( length<=array.length-offset )
byte[] array = new byte[1024];
ByteBuffer buffer = ByteBuffer.wrap(array,0,1024);
二、缓冲区的创建
ByteBuffer的声明以allocate为例。
先引入几个概念:
capacity | 此缓冲区的容量,缓冲区能容的最大量,创建时声明,不可以修改 |
position | 此缓冲区的位置,下一次读写标记的索引,每次的读写,position值都会改变。如:put()一个字节数据,position+1 |
limit | 此缓冲区的限制,读写的一个终点,读写内容长度不能超过limt限制,这个限制可修改,但limit<=capacity |
mark | 将此缓冲区的标记设置在其位置上,使用mark():mark=position,再调用reset()可以让position恢复到标记的位置 |
当使用allocate静态工厂方法声明ByteBuffer缓冲区时,经过一下的源码:
public static ByteBuffer allocate(int capacity) {
if (capacity < 0)
throw new IllegalArgumentException();
return new HeapByteBuffer(capacity, capacity);
}
HeapByteBuffer(int cap, int lim) { // package-private
super(-1, 0, lim, cap, new byte[cap], 0);
/*
hb = new byte[cap];
offset = 0;
*/
}
ByteBuffer(int mark, int pos, int lim, int cap, // package-private
byte[] hb, int offset){
super(mark, pos, lim, cap);
this.hb = hb;
this.offset = offset;
}
经过上面源码的一顿操作,形成了下面如图所示的缓冲对象:
经过上面代码,是进行了日常声明对象的一些参数初始化:position = 0,mark = -1,limit = capacity;
三、使用
先直接上一个读取文件的代码:
public class BufferDemo {
public static void main(String[] args){
buffer();
}
/**
* 如果不对byteBuffer进行flip或者clear操作,那么position指定到最后的位置,也就没有空间进行文件数据读取,
* 就导致在while循环里进行死循环
*/
public static void buffer(){
FileChannel fileChannel = null;
try {
ByteBuffer byteBuffer = ByteBuffer.allocate(5); // 通过allocate()工厂方法建立非直接字节缓冲区
// 建立文件的NIO连接通道
fileChannel = FileChannel.open(Paths.get("F:\\Web_project\\io_demo.txt"), StandardOpenOption.READ);
while (true){
// 通过连接通道,读取文件数据到缓冲区,如果读到末尾,返回 -1
int length = fileChannel.read(byteBuffer);
if (length == -1)
break;
// 翻转就是将一个处于存数据状态的缓冲区变为一个处于准备取数据的状态
byteBuffer.flip(); // 翻转这个缓冲区
System.out.println(Arrays.toString(byteBuffer.array()));
byteBuffer.clear(); // 重置一下缓冲区
}
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
try {
if (fileChannel != null)
fileChannel.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
好吧,使用的话,无非就那么几个步骤,通过静态工厂声明缓冲对象,将这个对象丢到通道里存储数据,存储的数据使用前先翻转一下这个缓冲区,然后使用,最后需要的话可以clear重置一下。就这几个步骤,是不是很简单。至于其他方法,可以在具体使用的时候查看API文档即可。讲一下一个很重要的flip()方法。
flip()这个方法,翻转这个缓冲区。 该限制设置为当前位置,然后将该位置设置为零。 如果标记被定义,则它被丢弃。在通道读取或放置操作的序列之后,调用此方法来准备一系列通道写入或相对获取操作
就是用于翻转这个缓冲区,将limit移动到position位置,position归置为0,这个flip翻转的目的就是为了读缓冲里面的数据做准备,不翻转就是个坑。看一下源码:
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}