深入浅出java.nio.MappedByteBuffer

MappedByteBuffer 简称为 MBB。 Mapped Byte Buffer = 映射 字节 缓冲区。

参考链接

深入剖析Linux IO原理和几种零拷贝机制的实现

JDK核心JAVA源码解析(4) - 堆外内存、零拷贝、DirectByteBuffer以及针对于NIO中的FileChannel的思考

主要作用

A direct byte buffer whose content is a memory-mapped region of a file.

一个直接字节缓冲区,它的内容是文件内存映射区域。

优势

本来我们需要操作FileChannel和ByteBuffer的交互来实现文件读写,有了 MBB,可以直接对文件进行操作。

将磁盘上的文件映射为内存物理空间的地址,减少了操作系统缓冲区复制带来的性能开销。

继承结构

在这里插入图片描述

父子关系

mbb的子类是DirectByteBuffer
由于 MBB 是一个抽象类,所以不能通过 new 的方式来获得实例。

public abstract class MappedByteBuffer extends ByteBuffer

从FileChannel中获得 MBB

public abstract MappedByteBuffer map(MapMode mode,long position, long size) throws IOException;

实际上通过map方法获得的是 DirectByteBuffer,根据继承关系知道 DBB 是 MBB 的子类。
在这里插入图片描述

MapMode

    public static class MapMode {

        public static final MapMode READ_ONLY
            = new MapMode("READ_ONLY");

        public static final MapMode READ_WRITE
            = new MapMode("READ_WRITE");

        public static final MapMode PRIVATE
            = new MapMode("PRIVATE");
            //省略.....
    }

有三种权限,只读,读写,私有。

公共方法

isLoaded

true if it is likely that this buffer’s content is resident in physical memory

如果此缓冲区的内容可能驻留在物理内存中返回true。

参考案例1,即使load仍然可能为false;

force

Forces any changes made to this buffer’s content to be written to the storage device containing the mapped file.

将缓冲区的内容强制写入到文件中。

load

Loads this buffer’s content into physical memory.

将此缓冲区的内容加载到物理内存中。

此方法尽最大努力确保在返回时,此缓冲区的内容驻留在物理内存中。

调用此方法可能会导致一些页面错误和I / O操作发生。

slice

Creates a new byte buffer whose content is a shared subsequence of this buffer’s content.
This method makes a best effort(尽最大努力) to ensure that, when it returns, this buffer’s content is resident in physical memory.

源码

    public ByteBuffer slice() {
        int pos = this.position();
        int lim = this.limit();
        assert (pos <= lim);
        //pos <= lim rem = lim -pos
        //pos > lim rem = 0
        int rem = (pos <= lim ? lim - pos : 0);
        //off = pos << 0
        int off = (pos << 0);
        assert (off >= 0);
        return new DirectByteBuffer(this, -1, 0, rem, rem, off);
    }

duplicate

Creates a new byte buffer that shares this buffer’s content.

源码

    public ByteBuffer duplicate() {
        return new DirectByteBuffer(this, this.markValue(), this.position(),
                this.limit(), this.capacity(), 0);
    }

slice和duplicate区别

            ByteBuffer slice = mbb.slice();
            ByteBuffer duplicate = mbb.duplicate();

从变量来看,他们三个都是独立的对象
在这里插入图片描述
从源码来看,duplicate就是创建了一个新DBB,并且指针跟原来的一样。slice是做了一个切分。

我们用一个案例来看一下具体的效果。

        try (
                RandomAccessFile raf = new RandomAccessFile(file, "rw");
                FileChannel fc = raf.getChannel();
        ) {
            MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 0, file.length());
            //第一行输出
            System.out.println(mbb.toString());
            //从第10个位置开始往后读
            fc.read(mbb,10);
            //第二行输出
            System.out.println(mbb.toString());
            ByteBuffer slice = mbb.slice();
            ByteBuffer duplicate = mbb.duplicate();
            //slice输出
            System.out.println(slice.toString());
            //duplicate输出
            System.out.println(duplicate.duplicate());

输出

java.nio.DirectByteBuffer[pos=0 lim=36 cap=36]
java.nio.DirectByteBuffer[pos=26 lim=36 cap=36]
java.nio.DirectByteBuffer[pos=0 lim=10 cap=10]
java.nio.DirectByteBuffer[pos=26 lim=36 cap=36]

总结:本质上都是创建一个新的缓冲区,区别是三个指针的位置。复制是完全不变,slice是 limit - position 的一段缓冲区。

DirectByteBufferR

    public ByteBuffer asReadOnlyBuffer() {

        return new DirectByteBufferR(this,
                                           this.markValue(),
                                           this.position(),
                                           this.limit(),
                                           this.capacity(),
                                           0);

    }

当对buffer指定asReadOnlyBuffer只读buf后,会新建一个DirectByteBufferR,后面这个R就是只读的意思。

RocketMQ

另外,RocketMQ主要通过MappedByteBuffer对文件进行读写操作。其中,利用了NIO中的FileChannel模型将磁盘上的物理文件直接映射到用户态的内存地址中(这种Mmap的方式减少了传统IO将磁盘文件数据在操作系统内核地址空间的缓冲区和用户应用程序地址空间的缓冲区之间来回进行拷贝的性能开销),将对文件的操作转化为直接对内存地址进行操作,从而极大地提高了文件的读写效率(正因为需要使用内存映射机制,故RocketMQ的文件存储都使用定长结构来存储,方便一次将整个文件映射至内存)。

在 RocketMQ 中搜索MappedByteBuffer发现以下用法

在这里插入图片描述
下面我们一个个类来归纳用法

MappedFile

在这里插入图片描述
主要方法:slice,force,clean(mbb)

StoreCheckpoint

在这里插入图片描述
主要方法getLong,clean,putLong,force

StoreTestUtil

在这里插入图片描述

IndexFile

在这里插入图片描述

clean

案例1 使用 force 写入 uuid

public class MappedByteBuffer01 {

    static File file = new File("D://a.txt");

    public static void main(String[] args) throws Exception {
        try (
                RandomAccessFile raf = new RandomAccessFile(file, "rw");
                FileChannel fc = raf.getChannel();
        ) {
            MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 0, file.length());
            System.out.println("init mbb = " + mbb.toString());
            boolean loaded = mbb.isLoaded();
            System.out.println("before load  = " + loaded);
            if (!loaded) {
                mbb.load();
                loaded = mbb.isLoaded();
                System.out.println("after load  = " + loaded);
                System.out.println("load mbb = " + mbb);
                //写入 uuid
                String uuid = UUID.randomUUID().toString();
                System.out.println("uuid = " + uuid);
                byte[] bytes = uuid.getBytes(StandardCharsets.UTF_8);
                mbb.put(bytes);
                mbb.force();
                System.out.println("force mbb = " + mbb);
                mbb.flip();
                System.out.println("flip mbb = " + mbb);
                byte[] readBytes = new byte[uuid.length()];
                mbb.get(readBytes);
                System.out.println("get mbb = " + mbb);
                System.out.println(new String(readBytes));
            }
        }
    }
}

输出

init mbb = java.nio.DirectByteBuffer[pos=0 lim=36 cap=36]
before load  = false
after load  = false
load mbb = java.nio.DirectByteBuffer[pos=0 lim=36 cap=36]
uuid = ac84715f-2b3e-4c94-b02b-7b0da1c10142
force mbb = java.nio.DirectByteBuffer[pos=36 lim=36 cap=36]
flip mbb = java.nio.DirectByteBuffer[pos=0 lim=36 cap=36]
get mbb = java.nio.DirectByteBuffer[pos=36 lim=36 cap=36]
ac84715f-2b3e-4c94-b02b-7b0da1c10142

结论:即使 load方法调用过isLoaded仍然可能返回false;force会将buf中的内容写入文件,并且可以flip后把buf中的内容读出来。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值