基础 | NIO - [Buffer]
@
§1 概述
- 缓冲区是一块内存空间,可以将它直观的理解为一个数组
- 这块内存空间直接和
Channel相连接 - 可以向这块内存空间中写或读取数据
常用实现
ByteBufferCharBufferDoubleBufferFloatBufferIntBufferLongBufferShortBufferMappedByteBuffer
常规使用流程
- 向
Buffer中写入数据 - 调用
flip()方法,将Buffer从写方向切换为读方向 - 从
Buffer中读取数据 - 调用
clear()或compact()方法清空Bufferclear()清空整个Buffercompact()清空Buffer中读过的部分
常规使用流程示例
public void read(String path, String file, int buffer) {
try (
RandomAccessFile raf = new RandomAccessFile(path + file,"rw");
// 获取 FileChannel
FileChannel channel = raf.getChannel()
){
// 创建读取时的 缓冲区
ByteBuffer buf = ByteBuffer.allocate(buffer);
// 读取 channel.read(buf)
for(int count = channel.read(buf); count != -1; count = channel.read(buf)){
buf.flip();
//读取后的动作
for(;buf.hasRemaining();)
System.out.print((char) buf.get());
buf.clear();
}
} catch (Exception e){ /* 异常处理 */ }
}
§2 重要成员
§2.1 property
mark
- 标记
- mark 可以设置一个标记,以供 buffer 在读取时重新回到这个标记
capacity
- 容量,即
Buffer针对当前类型的大小- 每个
Buffer中有一个对应属性的数组 - 这个数组就是
Buffer存放数据的容器 - capacity 就是这个数组的 length
- 每个
- 当容量写满,就必须在清空后才能继续写入
position
- 位置,表示即将读写数据的当前位置
- 从 0 开始,到
capacity-1 为止 - 每次从
position读写后,position会移动到下一个可读写的位置
limit
- 写模式时,表示最多写入的数据量,即
capacity - 读模式时,表示最多读取的数据量,即
position
§2.2 method
为 Buffer 分配空间
allocate(capacity)
向 Buffer 写入数据
- 直接写入,
buffer.put(content) - 通过
Channel向Buffer读取,channel.read(buffer)
切换 Buffer 为读模式
flip()
flip() 会将 limit 置为 position 的位置,并将 limit position 置为 0
从 Buffer 读取数据
get()
重读
rewind()
rewind() 将 position 置为 0,但 limit 不变
因此可以使用此方法重读已经读过的数据
清空
clear()
会清除整个缓冲区的数据,若缓冲区中有未读取的数据,这些数据会被丢弃
压缩
compact()
会清除缓冲区中已经读取过的数据,以便缓冲区可以继续写入
- 复制未读取过的数据到缓冲区开头
- limit 指向缓冲区末尾,即 capacity
- position 指向最后一个未读数据的后面
标记 & 重设标记
mark()
标记一个Buffer中的positionreset()
回到之前标记的position
缓冲区分片
slice()
slice()可以在当前缓冲区范围内创建一个子缓冲区- 子缓冲区相当于当前缓冲区的窗口
- 子缓冲区范围是
position到limit之间
只读缓冲区
asReadOnlyBuffer()
asReadOnlyBuffer()会返回当前缓冲区的只读版本- 只读缓冲区在读取前需要先进行一次
rewind() - 只读缓冲区与当前缓冲区共享数据,当前缓冲区数据变化时,只读缓冲区同步变化
直接缓冲区
allocateDirect()
allocateDirect()会在直接内存中分配缓冲区- 直接缓冲区避免了操作系统读取数据后,将数据写入 JVM 的开销,因此更快
- 直接缓冲区依赖 Full GC 进行垃圾回收,因此一直不触发 Full GC 可能导致
OOM: direct buffer
内存映射缓冲区
- 速度远高于常规流、通道
- 通过映射文件中数据为内存中数组实现
常规读写是将整个文件载入内存,此方式下只有实际读写内容映射到内存
§3 示例
缓冲区分片
public void subbuffer(){
IntBuffer buffer = IntBuffer.allocate(16);
buffer.put(new int[]{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15});
buffer.position(8);
// 分片
IntBuffer sub = buffer.slice();
for(int i=0;i<sub.capacity();i++){
sub.put(i,sub.get(i)*10);
}
buffer.rewind();
while(buffer.hasRemaining()){
System.out.println(buffer.get());
}
}
只读缓冲区
public void readonly(){
IntBuffer buffer = IntBuffer.allocate(16);
buffer.put(new int[]{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15});
// 只读
IntBuffer ro = buffer.asReadOnlyBuffer();
ro.rewind();
while(ro.hasRemaining()){
System.out.print(ro.get());
}
System.out.println();
// 重读缓冲区,观察原缓冲区修改后,只读缓冲区同步修改
buffer.rewind();
ro.rewind();
for(int i=0; i<buffer.capacity();i++){
buffer.put(i,buffer.get(i)*10);
}
while(ro.hasRemaining()){
System.out.print(ro.get());
}
}
直接缓冲区
public void directBuffer(String srcFilePath,String descFilePath){
try (
RandomAccessFile src = new RandomAccessFile(srcFilePath,"r");
RandomAccessFile desc = new RandomAccessFile(descFilePath,"rw");
FileChannel read = src.getChannel();
FileChannel write = desc.getChannel();
){
// 直接
ByteBuffer buf = ByteBuffer.allocateDirect(1024);
for(int length=read.read(buf);;buf.clear(),length=read.read(buf)){
if(-1==length)
break;
buf.flip();
while(buf.hasRemaining()){
write.write(buf);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
内存映射缓冲区复制文件
public void mappedByteBuffer(String srcFilePath,String descFilePath){
try (
RandomAccessFile src = new RandomAccessFile(srcFilePath,"r");
RandomAccessFile desc = new RandomAccessFile(descFilePath,"rw");
FileChannel read = src.getChannel();
FileChannel write = desc.getChannel();
){
long pos = 0; // 每轮读起始位置
long max = read.size(); // 文件长度
long size = Math.min(1024,max); // 缓冲区大小,又可能文件本身还没缓冲区大
for(;pos<max;pos += size){
// 只读内存映射缓冲区用来读,下面是个读写内存映射缓冲区用来写
// 每一轮起始位置是上一轮起始位置+上一轮缓冲区大小
// 每一轮映射大小是 缓冲区大小 和 文件长度-当前起始位置 的较小值,又可能最后一次映射不足一个缓冲区
MappedByteBuffer ib = read.map(FileChannel.MapMode.READ_ONLY,pos,Math.min(size,max-pos));
MappedByteBuffer ob = write.map(FileChannel.MapMode.READ_WRITE,pos,Math.min(size,max-pos));
// 真正的复制就这一句
ob.put(ib);
}
} catch (Exception e) {
e.printStackTrace();
}
}

浙公网安备 33010602011771号