byteBuffer_bytebuffer flip

byteBuffer_bytebuffer flip为什么会在RocketMQ系列里面参杂一篇ByteBuffer的文章呢?因为RocketMQ存储消息,是存储在文件中的,而且刚好使用的是ByteBuffer。这个属于JavaNIO的内容,用到的比较少,如果像我一样没有相关的知识做铺垫,强行看RocketMQ消息存储相关的代码会比较头疼。为了减少学习难度,这里很有必要先介绍一下ByteBuffer相关的知识。…

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE稳定放心使用

为什么会在RocketMQ系列里面参杂一篇ByteBuffer的文章呢?因为RocketMQ存储消息,是存储在文件中的,而且刚好使用的是ByteBuffer。这个属于Java NIO的内容,平时用到的非常少,如果像我一样没有相关的知识做铺垫,强行看RocketMQ消息存储相关的代码会比较头疼。为了减少学习难度,这里很有必要先介绍一下ByteBuffer相关的知识。

Buffer就是缓冲区的意思。如果数据直接来了就写入磁盘,那么肯定I/O操作太频繁,效率上不去。如果先写入缓冲区,等缓冲区数据量够了再一次性写入磁盘,是不是就好多了。

ByteBuffer就是存放字节的缓冲区。ByteBuffer继承了Buffer类,主要属性如下所示:

    private int mark = -1; // 标记位,配合mark()和reset()方法使用,可以记录某一次访问的位置。
    private int position = 0; // 访问位置,每访问一个byte就+1
    private int limit; // 读写限制位置,ByteBuffer的读写都不能超过这个位置
    private int capacity; // 容量,初始化后就不会再变了
    
    long address; // 访问地址,仅仅针对DirectByteBuffer,使用的是堆外内存。
    
    final byte[] hb; // 堆内内存时存放的内容
    final int offset; // 偏移量,使用byte数组初始化ByteBuffer时,数组的偏移量
    boolean isReadOnly; // 是否只读

上面的属性没有完全理解,也没有关系,后面会再次涉及到的,有个初步印象即可。

初始化

虽然ByteBuffer有构造函数,但是我们一般不用,转而使用ByteBuffer下面的几个静态函数来构造ByteBuffer对象。

    /** * 分配一个使用堆外内存的ByteBuffer */
    public static ByteBuffer allocateDirect(int capacity) { 
   
        return new DirectByteBuffer(capacity);
    }

   /**分配一个堆内内存的ByteBuffer*/
    public static ByteBuffer allocate(int capacity) { 
   
        if (capacity < 0)
            throw new IllegalArgumentException();
        return new HeapByteBuffer(capacity, capacity);
    }

   /**把给定的byte数组包装成一个byteBuffer*/
    public static ByteBuffer wrap(byte[] array,
                                    int offset, int length)
    { 
   
        try { 
   
            return new HeapByteBuffer(array, offset, length);
        } catch (IllegalArgumentException x) { 
   
            throw new IndexOutOfBoundsException();
        }
    }

    /**同上*/
    public static ByteBuffer wrap(byte[] array) { 
   
        return wrap(array, 0, array.length);
    }

例如:

        final int CAPACITY = 1024 * 1024 * 50;
        ByteBuffer byteBufferHeap = ByteBuffer.allocate(CAPACITY);
        ByteBuffer byteBufferDirect = ByteBuffer.allocateDirect(CAPACITY);
        
        byte[] array = new byte[]{ 
   1,2,3};
        ByteBuffer byteBuffer = ByteBuffer.wrap(array);

基本读写操作

下面是Thinking in java中关于缓冲器(ByteBuffer,又叫缓冲区)和通道(Channel)之间关系的描述:
在这里插入图片描述
从上述描述中,我们大致可以认为文件资源可以转换成一个通道,然后通过ByteBuffer读写文件。下面是一个简单的文件读取到byteBuffer的例子:

public static void main(String[] args) throws Exception { 
   

        String path = "d:" + File.separator + "bill"; // 文件内容是"abcdefg1234567890"
        FileInputStream fileInputStream = new FileInputStream(path);
        FileChannel fileChannel = fileInputStream.getChannel(); // 我们可以通过FileInputStream和FileOutPutStream提供FileChannel对象。

        final int CAPACITY = 20; // 容量20个字节
        ByteBuffer byteBufferHeap = ByteBuffer.allocate(CAPACITY);

        fileChannel.read(byteBufferHeap); // 读取文件到缓冲区
 
        System.out.println(byteBufferHeap.position()); // 结果是17,读一个byte,position+1
        System.out.println(byteBufferHeap.capacity()); // 结果是20
        System.out.println(byteBufferHeap.limit()); // 结果是20

        byteBufferHeap.flip();// flip(翻转)操作会将当前position记录到limit中, 然后position翻转指回0,mark置为-1.
        // byteBufferHeap.clear(); // 后面会讲解
        
        System.out.println(byteBufferHeap.position()); // 结果是0
        System.out.println(byteBufferHeap.capacity()); // 容量不变,还是20
        System.out.println(byteBufferHeap.limit()); // 结果17
        
        while(byteBufferHeap.hasRemaining()){ 
    // 如果Postion和limit之间还有元素
            System.out.print((char)byteBufferHeap.get());
        }

        fileChannel.close();
    }

下面来一个简单的byteBuffer写文件例子:

public static void main(String[] args) throws Exception{ 
   
        String content = "我是内容";
        ByteBuffer byteBufferHeap = ByteBuffer.wrap(content.getBytes("UTF-8")); // 通过warp创建ByteBuffer

        String path = "d:" + File.separator + "out.txt";
        FileChannel fileChannel = new FileOutputStream(path).getChannel();
        fileChannel.write(byteBufferHeap); // 把ByteBuffer内容写入channel

        String append = "我是追加内容";
        fileChannel.position(fileChannel.size());// 移动到文件尾
        fileChannel.write(ByteBuffer.wrap(append.getBytes("UTF-8")));
        
        fileChannel.close();
    }

flip()和clear()

flip(翻转)操作会将当前position记录到limit中, 然后position翻转指回0,mark置为-1。而clear操作则将limit重置为capacity,position重置为0,mark置为-1。
这里的clear是一种逻辑上的清除操作,内容并没有真的消除。例如上面的读例子中,我们将 byteBufferHeap.flip();替换为 byteBufferHeap.clear();运行结果如下:
在这里插入图片描述
我们可以看到,内容仍然可以读取出来。使用clear的目的是方便我们重新读取数据到ByteBuffer中。当我们理解了flip()和clear()的意思之后,我们才知道什么时候该用哪个方法:
当我们第一次读取Channel内容到ByteBuffer中的时候,这时position已经是读取的长度,为了获取ByteBuffer的内容,我们需要flip()一下,使得position重新回到0,然后就可以读取ByteBuffer中的内容了。当我们需要向ByteBuffer重新写内容的时候(覆盖),我们需要clear()一下,然后读取Channel内容到ByteBuffer中。
当我们需要将ByteBuffer的内容写回Channel的时候,我们需要flip()一下,然后写入Channel。写Channel的时候,clear()没什么使用场景。
为什么需要flip()这么奇怪的方法呢?从Channel读取内容后,直接读取ByteBuffer多好!据Thinking in java说,这么做是为了效率!但是似乎没看到相关的解释,猜测和position的使用有关。

mark()和reset()

mark()可以立即保存当前的position(),方便后面使用reset()复位到之前的position的位置,所以mark()和reset()这里放在了一起进行说明。在RocketMQ中,mark()的一个使用场景是:
写入message前先记录ByteBuffer的position位置,然后保存内容到文件。如果下次读取的内容,文件已经没有剩余的空间保存了,那么需要使用reset()恢复到上次读取的位置。

rewind()

rewind类似于flip(),但是它只重置position=0,mark=-1,对于limit它是不处理的,默认limit已经在正确的位置。

堆内内存和堆外内存

之前的例子,创建的ByteBuffer都是分配的堆内内存,也就是jvm里面的内存。下面的例子,我们通过allocateDirect()申请堆外内存:

 public static void main(String[] args) throws Exception{ 
   
        String path = "d:" + File.separator + "bill";
        FileChannel fileChannel = new FileInputStream(path).getChannel();

        final int CAPACITY = 20;
        ByteBuffer byteBufferHeap = ByteBuffer.allocateDirect(CAPACITY); // 申请堆外内存

        while(fileChannel.read(byteBufferHeap) != -1){ 
   // 读取内容到byteBuffer中,直到文件末尾
            byteBufferHeap.flip();// 翻转byteBuffer,为了后面读取byteBuffer内容

            while(byteBufferHeap.hasRemaining()){ 
    // ByteBuffer还有内容
                System.out.print((char)byteBufferHeap.get());
            }

            byteBufferHeap.clear();// 清空byteBuffer,为了后面读取内容到ByteBuffer。
        }
    }

allocateDirect返回的是DirectByteBuffer对象,其继承关系如下所示(这个图是后面参考文章的):
在这里插入图片描述DirectByteBuffer的使用与ByteBuffer几乎没什么区别,但是它们所蕴含的意思大不一样。DirectByteBuufer使用的是堆外内存,而我们平时的ByteBuffer使用的是堆内内存。我们之前介绍属性的时候,有下面两个属性:

   long address; // 访问地址,仅仅针对DirectByteBuffer,使用的是堆外内存。
   final byte[] hb; // 堆内内存时存放的内容

分配堆外内存,会返回堆外内存的地址,并存放在address里面。如果是堆内内存,那么内容就存放在hb里面。所以这两者是互斥的。
对于需要频繁操作的内存,并且仅仅是临时存在一会的,都建议使用堆外内存,并且做成缓冲池,不断循环利用这块内存。
更详情的内容可以查看后面的参考文章,这里限于篇幅和知识,就不画蛇添足了。

零拷贝技术

RocketMQ号称使用了零拷贝技术,其实就是这里的堆外内存。应用程序读取磁盘文件,看似很简单,其实在操作系统底层做了多次拷贝。首先系统将磁盘文件拷贝进入内核缓冲区,然后再切换到用户态,将内容拷贝到用户的内存缓冲区。这里不仅有系统内核态、用户态的切换,而且还有多次拷贝。如果使用了堆外内存,那么系统内核将与应用共享一片缓存区,那么磁盘文件从磁盘拷贝到共享缓冲区后,应用程序就可以直接访问了,从而免去了拷贝操作。这里讲的很粗糙,如果感兴趣可以查看后面的参考文章。

好了,到这里我们基本上对ByteBuffer有了一个大致的了解,后面就是RockeMQ消息三部曲:消息发送、消息存储和消息消费的内容了,敬请期待。

参考文章

1-浅析Linux中的零拷贝技术
2-JVM源码分析之堆外内存完全解读
3-堆外内存之DirectByteBuffer
4-JAVA NIO之浅谈内存映射文件原理与DirectMemory
5-堆内内存还是堆外内存?

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/186597.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • BAPI 记录

    BAPI 记录采购订单BAPI_PO_CREATE1采购订单创建交货单BAPI_OUTB_DELIVERY_CREATE_STO根据凭证类型获取TVAK-VBTYP判断要创建的SO类别不同类别调用不同BAPI创建SOBAPI_SALESORDER_CREATEFROMDAT2创建SO退货BAPI_CUSTOMERRETURN_CREATE创建SO借贷项凭证SD_SALES…

  • 简述分组密码的加密分组链接模式的工作原理及其特点(密码学移位密码加密解密)

    密钥生成如何安全地生成密钥。即如何生成可信任的密钥,保证用户得到的密钥是安全的,生成密钥的机器或程序是可信的。 如何生成安全的密钥。安全的密钥没有统一准确的定义,但一般来说是指密钥抗猜测和抗穷举等针对密钥攻击的能力。涉及密钥长度和密钥强弱的问题。分组加密对称加密算法按其加密数据的方式一般来说可以分成两种类型:分组加密和序列加密。 分组加密又称为块加密,是将要处理的数据分成固定的长度,然后在这固定长度的数据上使用密码算法进行计算。 序列加密模式又称为流加密方式,是对要处理的数据按位(或字

  • Canny边缘检测

    Canny边缘检测Canny边缘检测是一种非常流行的边缘检测算法,是JohnCanny在1986年提出的。它是一个多阶段的算法,即由多个步骤构成。1.图像降噪2.计算图像梯度3.非极大值抑制4.阈值筛选我们就事后诸葛亮,分析下这个步骤的缘由。首先,图像降噪。我们知道梯度算子可以用于增强图像,本质上是通过增强边缘轮廓来实现的,也就是说是可以检测到边缘的。但是,它们受噪声的影响都很大。那么,我…

  • 前端面试题汇总

    前端面试题汇总歌谣最全前端面试题(建议收藏,欢迎讨论)【Vue】[vue]从0到1自己构架一个vue项目,说说有哪些步骤、哪些重要插件、目录结构你会怎么组织

  • 搏一搏 单车变摩托,是时候捣鼓一下家中的小米电视机啦。[通俗易懂]

    搏一搏 单车变摩托,是时候捣鼓一下家中的小米电视机啦。[通俗易懂]事因因为家中有一台小米电视机,但是平时使用起来体验很差(1G内存,8G存储,CPU型号CortexA534核心1.5GHz),如今视频平台很多,Bilibili、爱奇艺、优酷、腾讯视频、芒果TV等平台。很多视频通过小米电视找不到,一部分原因是小米电视机的界面太炫丽,导致家中老人不太会操作,就是我用起来都觉得麻烦。想了一下,实在不行就干脆换系统。后来看到一些文章,可以更换界面,卸载一些不用的软件。当贝桌面,体验了一下。装完后,发现还不错,接近手机清爽的界面。装完主界面如下,系统自带的一些软件

  • Spring 的循环依赖:真的必须非要三级缓存吗?

    点击上方“全栈程序员社区”,星标公众号 重磅干货,第一时间送达 作者:青石路 www.cnblogs.com/youzhibing/p/14337244.html 写作背景 做 J…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号