Buffer缓冲区实际上是内存中开辟的⼀块数组空间,⽤于存放数据。Java NIO中的buffer类提供了对这块数组缓冲区的基本操作。
IO流的操作⾯向的是流对象,⽽NIO操作的数据都是⾯向Buffer缓冲区的。也就是说,读取数据是通过通道将数据存⼊到buffer中。写数据是将buffer缓冲区中的数据通过通道写到⽂件中。
Java NIO提供了所有缓冲区的抽象基类Buffer。Buffer的具体实现类有很多,⽐如ByteBuffer、IntBuffer、LongBuffer、CharBuffer、DoubleBuffer、FloatBuffer、HeapByteBuffer、MappedByteBuffer等等,这些具体的实现类实际上是依据Buffer数组中存放数据的数据类型来决定,⽐如ByteBuffer中存放的是字节数据、LongBuffer中存放的long类型的数据。
Buffer的三⼤关键属性:capactity、limit、position。
capacity:
缓冲区的容量是它包含的元素数。缓冲区的容量永远不会为负,也永远不会改变。
limit:
缓冲区的限制是不应读取或写⼊的第⼀个元素的索引。缓冲区的限制永远不会为负,也永远不会⼤于其容量。
写数据时:limit与capacity相同
读数据时:limit表示可读的数据位置,因此在上次写操作后需要通过flip方法,将position值赋给limit。
position:
缓冲区的位置是要读取或写⼊的下⼀个元素的索引。缓冲器的位置永远不会为负,也永远不会⼤于其极限。
Buffer中的方法如下:
返回值类型 | 方法名 | 描述 |
---|---|---|
abstract Object | array() | 返回缓冲区的组 |
abstract int | arrayOffset() | 返回缓冲区的偏移量大小 |
int | capacity() | 返回缓冲区的容量 |
Buffer | clear() | 清空缓冲区中的内容 |
Buffer | filp() | 翻转缓冲区 |
abstract boolean | hasArray() | 判断缓冲区中是否有可访问的数组 |
boolean | hasRemaining() | 缓冲区中的position和limit之间是否还存在元素 |
abstract boolean | isDirect() | 此缓冲区是否是直接缓冲区 |
abstract boolean | isReadOnly() | 此缓冲区是否是只读缓冲区 |
int | limit() | 返回此缓冲区的limit属性值 |
Buffer | limit(int newLimit) | 设置此缓冲区的limit属性值 |
Buffer | mark() | 标记当前缓冲区的position属性值 |
int | position() | 返回此缓冲区的position属性值 |
Buffer | position(int newPosition) | 设置此缓冲区的position属性值 |
int | remaining() | 返回缓冲区中position和limit之间元素的个数 |
Buffer | reset() | 重置缓冲区中position的值为之前mark的值 |
Buffer | rewind() | 将position设置0,limit不变 |
以ByteBuffer为例,分类介绍ByteBuffer的常⽤⽅法。
创建Buffer
ByteBuffer提供了allocate静态⽅法⽤来创建带有初始化数组的Buffer缓冲区。
ByteBuffer buffer = ByteBuffer.allocate(1024);
向Buffer中写数据
将数据写⼊到buffer中有三种⽅式:
put(数据): 将数据存⼊到buffer,此时position随之变化。
wrap(数据):将数据存⼊数据并返回buffer,此时position为0,limit为数据的⻓度
channel.read(buffer):将数据读⼊到buffer中。
从Buffer中读取数据
从Buffer中读取数据有以下⼏种⽅式:
get相关的⽅法:获得当前position或指定position的数据
array():返回整个数组内容
channel.write(buffer):使⽤channel获得buffer中的内容并写⼊到指定⽬标
子缓冲区
可以为Buffer创建⼦缓冲区,在现有缓冲区上分割出⼀块空间作为新的缓冲区。原缓冲区和⼦缓冲区共享同⼀⽚数据空间。
通过调⽤slice⽅法创建⼦缓冲区 。
package com.my.io.buffer; import java.nio.ByteBuffer; import java.util.Arrays; /** * @author zhupanlin * @version 1.0 * @description: 子缓冲区 * @date 2024/1/26 0:18 */ public class Demo1 { public static void main(String[] args) { // 得到buffer ByteBuffer buffer = ByteBuffer.wrap(new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9}); // 设置position buffer.position(3); // 设置limit buffer.limit(7); // 得到子缓冲区 与原缓冲区共享buffer ByteBuffer slice = buffer.slice(); System.out.println(Arrays.toString(slice.array())); slice.put(0, ((byte) 40)); System.out.println(Arrays.toString(buffer.array())); } }
只读缓冲区
通过buffer的asReadOnlyBuffer()⽅法获得⼀个新的只读缓冲区,所谓的只读缓冲区就是只能读不能写。只读缓冲区与原缓冲区共享同⼀⽚数据空间,原缓冲区数据发⽣改变,只读缓冲区也能看到变化后的数据,因为它们共享同⼀⽚存储空间。
package com.my.io.buffer; import java.nio.ByteBuffer; /** * @author zhupanlin * @version 1.0 * @description: 只读缓冲区 * @date 2024/1/26 0:24 */ public class Demo2 { public static void main(String[] args) { ByteBuffer buffer = ByteBuffer.wrap(new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9}); // 获得只读缓冲区 ByteBuffer readOnlyBuffer = buffer.asReadOnlyBuffer(); // 修改原buffer buffer.put(3, (byte) 40); // 查看readOnlyBuffer while (readOnlyBuffer.hasRemaining()){ System.out.println(readOnlyBuffer.get()); } } }
直接缓冲区
直接缓冲区,Java虚拟机将尽最⼤努⼒直接对其执⾏本机I/O操作。也就是说,它将试图避免在每次调⽤底层操作系统的本机I/O操作之前(或之后),将缓冲区的内容复制到中间缓冲区(或从中间缓冲区复制)。
可以通过调⽤此类的allocateDirect⼯⼚⽅法来创建直接字节缓冲区。此⽅法返回的缓冲区通常⽐⾮直接缓冲区具有更⾼的分配和释放成本。直接缓冲区的内容可能位于正常垃圾收集堆之外,因此它们对应⽤程序内存占⽤的影响可能不明显。
package com.my.io.buffer; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; /** * @author zhupanlin * @version 1.0 * @description: 直接缓冲区 * @date 2024/1/26 0:28 */ public class Demo3 { public static void main(String[] args) throws IOException { FileInputStream fis = new FileInputStream("1.txt"); FileChannel fisChannel = fis.getChannel(); FileOutputStream fos = new FileOutputStream("8.txt"); FileChannel fosChannel = fos.getChannel(); // 创建直接缓冲区 ByteBuffer buffer = ByteBuffer.allocateDirect(1024); int len = 0; while ((len = fisChannel.read(buffer)) > 0){ // 把buffer的数据写到文件中 buffer.flip(); fosChannel.write(buffer); } System.out.println("复制完毕"); } }
基于内存映射的Buffer
MappedByteBuffer采⽤direct buffer的⽅式读写⽂件内容,这种⽅式就是内存映射。这种⽅式直接调⽤系统底层的缓存,没有JVM和系统之间的复制操作,所以效率⾮常⾼,主要⽤于操作⼤⽂件,是直接缓冲区的父类。
通过FileChannel的map⽅法得到MappedByteBuffer,MappedByteBuffer把磁盘中⽂件的内容映射到计算机的虚拟内存中,操作MappedByteBuffer直接操作内存中的数据,⽽⽆需每次通过IO来读取物理磁盘中的⽂件,效率极⾼
package com.my.io.buffer; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; /** * @author zhupanlin * @version 1.0 * @description: 内存映射缓冲区 * @date 2024/1/26 0:34 */ public class Demo4 { public static void main(String[] args) throws IOException { File file = new File("1.txt"); RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw"); // 获得channel FileChannel fileChannel = randomAccessFile.getChannel(); // channel的map来获得内存映射缓冲区 MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, file.length()); while (mappedByteBuffer.hasRemaining()){ System.out.println((char)mappedByteBuffer.get()); } } }