本专栏收录于《49天精通Java从入门到就业》,本专栏专门针对零基础和需要进阶提升的同学所准备的一套完整教学,从0开始,不断进阶深入,后续还有《手把手springboot+vue实战项目》,轻松应对面试,专栏订阅地址:https://blog.csdn.net/guorui_java/category_11461823.html。
缓冲区是由具有相同类型的数值构成的数组,Buffer是一个抽象类,它有很多子类,包括ByteBuffer、CharBuffer、DoubleBuffer、IntBuffer、LongBuffer、ShortBuffer。
每个缓冲区都具有4个属性:
position和limit之间的距离指定了可读/写的字节数。
-1 <= mark <= position <= limit <= capacity
0<= position <= limit <= capacity
使用缓冲区的主要目的是执行读写循环操作。
假设我们有一个缓冲区,在一开始,它的位置是0,界限等于容量。我们不断地调用put将值添加到这个缓冲区中,当我们耗尽所有的数据或者写出的数据量达到容量大小时,就该切入到读操作了。
这时可以调用flip方法将界限设置到当前位置,并把位置复位到0.现在在remaining方法返回正数时(它返回的值是界限 - 位置),不断地调用get。在我们将缓冲区中所有的值都写入之后,调用clear使缓冲区为下一次写循环做好准备。clear方法将位置复位到0,并将界限复位到容量。
如果想重读缓冲区,可以使用rewind或mark/reset进行复位。
然后可以 用某个通道的数据填充缓冲区,或者将缓冲区的内容写出到通道中。
ByteBuffer buffer = ByteBuffer.allocate(RECORD_SIZE);
channel.read(buffer);
channel.position(newpos);
buffer.flip();
channel.write(buffer);
Buffer及其子类都不是线程安全的,若多线程操作该缓冲区,则应通过同步来控制对该缓冲区的访问。
1、clear()
position设为0,把limit设为capacity,取消标记,一般在把数据写入Buffer前调用,Buffer中的数据并未清除,只是这些标记告诉我们可以从哪里开始往Buffer里写数据。如果Buffer中有一些未读的数据,调用clear()方法以后,数据将“被遗忘”,意味着不再有任何标记会告诉你哪些数据被读过,哪些还没有。
2、 compact()
如果Buffer中仍有未读的数据,且后续还需要这些数据,但是此时想要先写些数据,那么使用compact()方法。compact()方法将所有未读的数据拷贝到Buffer起始处。然后将position设到最后一个未读元素正后面。limit属性依然像clear()方法一样,设置成capacity。现在Buffer准备好写数据了,但是不会覆盖未读的数据。
3、flip()
把limit设为当前position,把position设为0,取消标记,一般在从Buffer中读取数据前调用。
4、rewind()
把position设为0,limit不变,一般在把数据重写入Buffer前调用。
5、hasRemaining()
当缓冲区至少还有一个元素时,返回true。
6、remaining()
position和limit之间字节个数
7、 reset()
将position的值还原成上次调用mark()方法后的position的值。
8、allocate()
分配一个具有指定大小的底层数组,并将它包装到一个缓冲区对象中,用静态方法 allocate() 来分配缓冲区。
9、wrap()
将一个已有的byte数组包装出一个新的bytebuffer对象。
10、slice()
根据现有的缓冲区创建一个子缓冲区。也就是它创建一个新的缓冲区,新缓冲区与原来的缓冲区的一部分共享数据,子缓冲区和缓冲区 共享同一个底层数据数组,分片操作是根据当前position以及limit的值来确定的。
11、order()
返回ByteBuffer的字节序。
从 FileInputStream 对象中获取的通道是以读的方式打开文件,从 FileOutpuStream 对象中获取的通道是以写的方式打开文件。
FileOutputStream ous = new FileOutputStream(new File("nezha.txt"));
// 获取一个只读通道
FileChannel out = ous.getChannel();
FileInputStream ins = new FileInputStream(new File("nezha.txt"));
// 获取一个只写通道
FileChannel in = ins.getChannel();
从 RandomAccessFaile 中获取的通道取决于 RandomAccessFaile 对象是以什么方式创建的,“r”, “w”, “rw” 分别对应着读模式,写模式,以及读写模式。
RandomAccessFile file = new RandomAccessFile("a.txt", "rw");
// 获取一个可读写文件通道
FileChannel channel = file.getChannel();
通过静态静态方法 FileChannel.open() 打开的通道可以指定打开模式,模式通过 StandardOpenOption 枚举类型指定。
// 以只读的方式打开一个文件 nezha.txt
FileChannel channel = FileChannel.open(Paths.get("nezha.txt"), StandardOpenOption.READ); 的通道
单个缓冲区操作也非常简单,它返回往通道中写入的字节数。
FileChannel channel = FileChannel.open(Paths.get("a.txt"), StandardOpenOption.WRITE);
ByteBuffer buf = ByteBuffer.allocate(5);
byte[] data = "Hello, Java NIO.".getBytes();
for (int i = 0; i < data.length; ) {
buf.put(data, i, Math.min(data.length - i, buf.limit() - buf.position()));
buf.flip();
i += channel.write(buf);
buf.compact();
}
channel.force(false);
channel.close();
FileChannel 实现了 GatherringByteChannel 接口,与 ScatteringByteChannel 相呼应。可以一次性将多个缓冲区的数据写入到通道中。
FileChannel channel = FileChannel.open(Paths.get("nezha.txt"), StandardOpenOption.WRITE);
ByteBuffer key = ByteBuffer.allocate(10), value = ByteBuffer.allocate(10);
byte[] data = "017 Robothy".getBytes();
key.put(data, 0, 3);
value.put(data, 4, data.length-4);
ByteBuffer[] buffers = new ByteBuffer[]{key, value};
key.flip();
value.flip();
channel.write(buffers);
channel.force(false);
channel.close();
读取数据的 read(ByteBuffer buf) 方法返回的值表示读取到的字节数,如果读到了文件末尾,返回值为 -1。读取数据时,position 会往后移动。
和一般通道的操作一样,数据也是需要读取到1个缓冲区中,然后从缓冲区取出数据。在调用 read 方法读取数据的时候,可以传入参数 position 和 length 来指定开始读取的位置和长度。
FileChannel channel = FileChannel.open(Paths.get("nezha.txt"), StandardOpenOption.READ);
ByteBuffer buf = ByteBuffer.allocate(5);
while(channel.read(buf)!=-1){
buf.flip();
System.out.print(new String(buf.array()));
buf.clear();
}
channel.close();
文件通道 FileChannel 实现了 ScatteringByteChannel 接口,可以将文件通道中的内容同时读取到多个 ByteBuffer 当中,这在处理包含若干长度固定数据块的文件时很有用。
ScatteringByteChannel channel = FileChannel.open(Paths.get("a.txt"), StandardOpenOption.READ);
ByteBuffer key = ByteBuffer.allocate(5), value=ByteBuffer.allocate(10);
ByteBuffer[] buffers = new ByteBuffer[]{key, value};
while(channel.read(buffers)!=-1){
key.flip();
value.flip();
System.out.println(new String(key.array()));
System.out.println(new String(value.array()));
key.clear();
value.clear();
}
channel.close();
可以用ByteBuffer对象创建其他类型的缓冲区,新缓冲区共享原始ByteBuffer的全部或者部分内存,这样的缓冲区被叫做视图缓冲区,就是通过源缓冲区创建其他的基础数据类型的缓冲区,新缓冲区和源缓冲区共享数据,但各自维护自己的属性(capacity、limit、position、mark)。
当向buffer写入数据时,buffer会记录下写了多少数据;一旦要读取数据,需要通过flip()方法将Buffer从写模式切换到读模式;在读模式下,可以读取之前写入到buffer的所有数据。一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。
有两种方式能清空缓冲区:调用clear()或compact()方法。clear()方法会清空整个缓冲区。compact()方法只会清除已经读过的数据。任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面。