Netty 网络编程深入学习【一】:ByteBuffer 源码解析

ByteBuffer源码阅读

ByteBuffer是一个用于处理字节数据的缓冲区类。它是Java NIO 包的一部分,提供了一种高效的方式来处理原始字节数据。
ByteBuffer 可以用来读取、写入、修改和操作字节数据,它是一种直接操作字节的方式,比起传统的 InputStreamOutputStream ,它更加灵活和高效。

01. 阅读前准备

Buffer 一般配合另一个类 Channel 配合使用完成读写的操作,这里先来创建读取使用的 Channel 和缓冲使用的 Buffer。

FileChannel channel = new FileInputStream("data.txt").getChannel();
ByteBuffer buffer = ByteBuffer.allocate(10); // 准备缓冲区
channel.read(buffer); // 将文件的内容读取到缓冲区

02. 基本构造解析

ByteBuffer 底层维护了一个存放数据的 Byte 数组,同时使用了多个属性来配合完成写入、读取的操作,底层的大致结构是这样的:

在这里插入图片描述

Position 是一个指向当前读或者写位置的指针(通过 filp 方法来切换读写模式)

Limit 是限制位置,根据读写模式的不同,限制的内容也不同

Capacity 就是底层数组的容量。

03. ByteBuffer 对象的构造

通过上面的案例不难看出,ByteBuffer 的获取方法并不是直接 new 的,而是调用了类中提供的静态方法 allocate(int capacity) 来构造对象,这个方法具体实现是这样的:

    public static ByteBuffer allocate(int capacity) {// 检测传入数据是否正确if (capacity < 0)throw new IllegalArgumentException();// 调用 HeapByteBuffer 的构造方法return new HeapByteBuffer(capacity, capacity);}

ByteBuffer 是一个抽象类,而 HeapByteBufferByteBuffer 的一个具体实现类之一,它是在堆内存中分配的 ByteBuffer。 HeapByteBuffer是通过**ByteBuffer.allocate(int capacity)**方法创建的,它将字节数据存储在Java虚拟机的堆内存中。

    HeapByteBuffer(int cap, int lim) {            // package-privatesuper(-1, 0, lim, cap, new byte[cap], 0);/*hb = new byte[cap];offset = 0;*/}ByteBuffer(int mark, int pos, int lim, int cap,   // package-privatebyte[] hb, int offset){super(mark, pos, lim, cap);this.hb = hb;this.offset = offset;}// 最终调用的,Buffer 抽象类中的构造方法Buffer(int mark, int pos, int lim, int cap) {       // package-private// 检查 capif (cap < 0)throw new IllegalArgumentException("Negative capacity: " + cap);this.capacity = cap; // 设置容量limit(lim); // 设置 limitposition(pos); // 设置 position// 检测 mark 的值,如果 mark > positionif (mark >= 0) {if (mark > pos)throw new IllegalArgumentException("mark > position: ("+ mark + " > " + pos + ")");this.mark = mark;}}

方法中调用了父类,也就是 ByteBuffer 的构造方法来构造对象,最终调用的是 Buffer 中的构造方法,其中出现了一个之前没有提到的参数**mark ,这个**参数用于设置初始标记,这个标记是一个可选的位置,它允许你在某个位置上设置一个标记,然后稍后可以通过调用 reset() 方法将位置恢复到这个标记处;这样就能理解上面的检测 mark 的值,这个值是不应该放到 position 的后面的,因为它代表的含义是回退。

所以在初始化的时候一般是将其设置为 -1,也就是约定的未设置值的状态,在 Buffer 抽象类中也是这样定义的:

    // Invariants: mark <= position <= limit <= capacityprivate int mark = -1;

将文件中的内容读取到 Buffer 需要调用 Channel 中的 read(ByteBuffer dst) 方法,下面来看一下方法具体的实现,因为本文是 Buffer 的源码解析,所以将重点聚焦在 Buffer 类上,关于 Channel 以注释的方式说明:

public int read(ByteBuffer dst) throws IOException {// 检测 Channel 是否开启,若未开启会抛出 ClosedChannelException 异常ensureOpen(); // 检测文件权限,如果不可读,抛出 NonReadableChannelException 异常if (!readable)throw new NonReadableChannelException();// 锁,类型为 Objectsynchronized (positionLock) {int n = 0; // 用于存储实际读取的字节数目int ti = -1; // 用于存储当前线程的标识符try {begin();ti = threads.add();// 再次检测 Channel 是否开启if (!isOpen())return 0;// 执行读取的操作    do {n = IOUtil.read(fd, dst, -1, nd);} while ((n == IOStatus.INTERRUPTED) && isOpen());// 返回读取的字节数return IOStatus.normalize(n);} finally {threads.remove(ti);end(n > 0);assert IOStatus.check(n);}}}

04. 将 Channel 中的内容读取到 Buffer

其中最重要的方法就是 IOUtil 类的静态 read 方法,来看一下它的具体实现:

    static int read(FileDescriptor fd, ByteBuffer dst, long position,NativeDispatcher nd)throws IOException{// 检查 ByteBuffer 是否为只读的if (dst.isReadOnly())throw new IllegalArgumentException("Read-only buffer");// 检查是否为直接缓冲区if (dst instanceof DirectBuffer)return readIntoNativeBuffer(fd, dst, position, nd);// 创建一个临时的直接缓冲区,大小为 dst 的剩余大小ByteBuffer bb = Util.getTemporaryDirectBuffer(dst.remaining());try {// 将数据读取到临时的直接缓冲区int n = readIntoNativeBuffer(fd, bb, position, nd);bb.flip();if (n > 0)dst.put(bb); // 将数据放入传入的 Buffer 中return n;} finally {// 释放临时直接缓冲区Util.offerFirstTemporaryDirectBuffer(bb);}}// 计算剩余大小的方法public final int remaining() {int rem = limit - position;return rem > 0 ? rem : 0;}

上面的方法将数据读取到直接缓冲区中,直接缓冲区的优势是在于其数据存储在堆外(off-heap memory)而不是堆内存中。这种特性使得直接缓冲区在进行I/O操作时能够更有效地与操作系统进行交互,从而提高了I/O操作的性能和效率。

上面方法使用了 put 方法将存储在直接缓冲区的内容读取到了传入的 Buffer 中:

    public ByteBuffer put(ByteBuffer src) {// 如果是传入的类型堆缓冲区,可以通过数组拷贝的方式复制if (src instanceof HeapByteBuffer) {// 自己读取自己if (src == this)throw new IllegalArgumentException();HeapByteBuffer sb = (HeapByteBuffer)src;int pos = position();int sbpos = sb.position();// 源 Buffer 中内容大于当前 Buffer 的剩余容量int n = sb.limit() - sbpos;if (n > limit() - pos)throw new BufferOverflowException();// 数组拷贝复制System.arraycopy(sb.hb, sb.ix(sbpos),hb, ix(pos), n);sb.position(sbpos + n);position(pos + n);// 传入的类型是直接缓冲区} else if (src.isDirect()) {int n = src.remaining(); // 源 Buffer 的内容容量int pos = position(); // 写指针// 源 Buffer 中内容大于当前 Buffer 的剩余容量if (n > limit() - pos)throw new BufferOverflowException();// 该方法的作用是从当前 ByteBuffer 中读取数据,// 并将其存储到指定的字节数组 dst 中的指定位置 offset 开始的 length 个位置中。src.get(hb, ix(pos), n);position(pos + n);} else {super.put(src);}return this;}

这样就完成了将直接缓冲区中的内容复制到新的 Buffer 中。

总结一下,read 方法的原理就是将文件中的内容读取到更有效与系统进行 IO 交互的直接缓冲区中,然后将直接缓冲区中的内容复制到传入的堆缓冲区中。

05. flip 转换方法

为什么在读取和写入数据之前需要调用 flip() 方法来转换模式呢?上面我们提到,Buffer 的读取和存储都依赖指针进行的,flip 方法实质上就是对指针进行操作来实现模式的转换的。

  public final Buffer flip() {limit = position;position = 0;mark = -1;return this;}

无论是读模式还是写模式的转换,position 都是位于 0 的位置,也就是读取也要从头开始读,写也要从头开始写(覆盖,后面提到的 compact 方法可以将未读的内容保留下来)

limit 即或者写的限制,如果是从读模式转到写模式,将 limit 设置为 position,也就是最后写入的位置,但是当从读取切换到写入模式的时候,因为 limit 会直接切换成 position 的值,所以有可能会导致写入量大于 limit 而抛出 BufferOverflowException 异常,比如下面的操作:

public class ByteBufferFlipTest {public static void main(String[] args) {ByteBuffer buffer = ByteBuffer.allocate(10);buffer.flip(); // 切换到读模式buffer.flip(); // 切换到写模式buffer.put((byte) 1);}
}

运行后就会这样:

Exception in thread "main" java.nio.BufferOverflowExceptionat java.nio.Buffer.nextPutIndex(Buffer.java:525)at java.nio.HeapByteBuffer.put(HeapByteBuffer.java:173)at org.example.ByteBufferFlipTest.main(ByteBufferFlipTest.java:10)

所以切换的时候一定要注意避免这种情况,因为对 Buffer 的操作比较底层,需要根据合理的设计来实现需求,比如当我们明确的知道读取的是多少字节的时候,可以通过 Buffer 类提供的
limit(int newLimit) 方法来设置限制的值。

06. compact() 保留未读内容的切换方法

上面提到,如果使用 filp 方法来转换读取的模式,实质上是不会保留未读取的内容的,这里要将的 compact 方法则会保留未读取的内容,下面来看一下具体的实现。

在这里插入图片描述

    public ByteBuffer compact() {int pos = position(); // 获取 positionint lim = limit(); // 获取 limit(读限制)assert (pos <= lim); // 断言int rem = (pos <= lim ? lim - pos : 0); // 剩余未读的内容// 从 hb 中读取从 pos 长度为 rem 的内容到 hb 从 0 开始长度为 rem 中System.arraycopy(hb, ix(pos), hb, ix(0), rem);position(rem); // 设置新的指针(写指针)limit(capacity()); // 将 limit 设置为容量discardMark(); // 将 mark 设置为 -1return this;}

方法中使用 void arraycopy(Object src, int srcPos, Object dest, int destPos, int length); 方法将未读取的内容保留到 byte 数组中,实现了上面图片中的效果。

07. put 方法和 clear 方法

通过上面的阅读,再来看两个比较轻松的方法,put 方法用于将 byte 存入 Buffer 中,clear 用于将 Buffer 重置到初始状态。

    public ByteBuffer put(byte x) {hb[ix(nextPutIndex())] = x; // 获取下一个存放数据的位置return this;}final int nextPutIndex() {int p = position;if (p >= limit)throw new BufferOverflowException();position = p + 1; // 自增操作return p;}protected int ix(int i) {return i + offset;}

put 中使用到的 ix 方法是 ByteBuffer 类中的一个受保护的辅助方法,用于计算指定索引位置在底层数组中的实际位置;offset 是一个成员变量,表示底层数组中的偏移量。

    public final Buffer clear() {position = 0;limit = capacity;mark = -1;return this;}

clear 方法则是将指针直接重置到初始位置(数据并未删除)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1411245.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

使用OpenCV绘制两幅图验证DSC和IoU以及BCELoss的计算程序

1.创作灵感 很多小伙伴在玩深度学习模型的时候,需要计算Groudtruth和predict图的dsc、IOU以及BCELoss。这两个关键的指标的程序有很多种写法,今天使用OpenCV绘制两张已知分布的图像,计算其dsc、IOU以及BCELoss。 2、图像如图所示 在一个100100的区域内,红色框范围为预测…

我的毕业实习经历

我的毕业实习经历 前言求职之路成为社畜重获自由结语 前言 这篇博客原本我想以实习生找工作踩坑指南&#xff1a;我的毕业实习经历为文章标题的&#xff0c;原因是跟我前面发布的一篇博客《实习生找工作踩坑指南&#xff1a;租房篇》做一个呼应收尾&#xff0c;奈何标题略显臃肿…

c3 笔记8 css排版技巧

相关内容&#xff1a;边界、边框、位置&#xff08;absolute、relative、static&#xff09;、overflow、z-index、超链接、鼠标光标特效、…… margin:上边界值 右边界值 下边界值 左边界值 笔记来源&#xff1a; ©《HTML5CSS3JavaScript网页设计》陈婉凌编&#xff…

固定资产管理系统

固定资产管理系统 摘 要 随着计算机信息技术的发展以及对资产、设备的管理科学化、合理化的高要求&#xff0c;利用计算机实现设备及资产的信息化管理已经显得非常重要。 固定资产管理系统是一个单位不可缺少的部分。但一直以来人们使用传统的人工方式管理固定资产的信息&…

【大数据】学习笔记

文章目录 [toc]NAT配置IP配置SecureCRT配置PropertiesTerminal Java安装环境变量配置 Hadoop安装修改配置文件hadoop-env.shyarn-env.shslavescore-site.xmlhdfs-site.xmlmapred-site.xmlyarn-site.xml 环境变量配置 IP与主机名映射关系配置hostname配置映射关系配置 关闭防火墙…

[每日AI·0501]GitHub 版 Devin,Transformer的强力挑战者 Mamba,Sora 制作细节与踩坑,OpenAI 记忆功能

AI 资讯 国资委&#xff1a;加快人工智能等新技术与制造全过程、全要素深度融合GitHub版 Devin 上线&#xff0c;会打字就能开发应用&#xff0c;微软 CEO&#xff1a;重新定义 IDE在12个视频理解任务中&#xff0c;Mamba 先打败了 TransformerSora 会颠覆电影制作吗&#xff…

Linux POSIX消息队列遇到的问题和使用方法

目录 一、开发环境及消息队列介绍二、问题描述三、解决办法四、测试代码 一、开发环境及消息队列介绍 开发板&#xff1a;nuc980 1.ARM Linux中消息队列的原理   在ARM Linux中&#xff0c;消息队列是通过POSIX&#xff08;Portable Operating System Interface&#xff09…

idea 新建spring maven项目、ioc和依赖注入

文章目录 一、新建Spring-Maven项目二、在Spring-context使用IOC和依赖注入 一、新建Spring-Maven项目 在pom.xml文件中添加插件管理依赖 <build><plugins><plugin><artifactId>maven-compiler-plugin</artifactId><version>3.1</ver…

来一篇错题集(虽然简单吧)

一.Assembly via Remainders #include<bits/stdc.h> using namespace std; typedef long long ll; int a[2000]; int b[2000]; int main(){int t;cin>>t;while(t--){int n;cin>>n;for(int i1;i<n-1;i){cin>>b[i];}int x1000000000;//使用1000000000…

算法打卡day40

今日任务&#xff1a; 1&#xff09;139.单词拆分 2&#xff09;多重背包理论基础&#xff08;卡码网56携带矿石资源&#xff09; 3&#xff09;背包问题总结 4&#xff09;复习day15 139单词拆分 题目链接&#xff1a;139. 单词拆分 - 力扣&#xff08;LeetCode&#xff09; …

LLaVA:分析图像和文本数据的开源模型

原文地址&#xff1a;analyzing-images-with-llava 2024 年 3 月 20 日 在过去的几个月里&#xff0c;ChatGPT 等各种大型语言模型&#xff08;LLM&#xff09;已进入商业市场&#xff0c;许多公司已成功地将 LLM 集成到其产品和服务中&#xff0c;极大地改变了我们与设备和互…

RocketMQ SpringBoot 3.0不兼容解决方案

很多小伙伴在项目升级到springBoot3.0版本以上之后&#xff0c;整合很多中间件会有很多问题&#xff0c;下面带小伙伴解决springBoot3.0版本以上对于RocketMQ 不兼容问题 报错信息 *************************** APPLICATION FAILED TO START *************************** Des…

【JVM】JMM 内存模型

JMM 概述 内存模型 java[内存模型](Java Memory Model) 和 [内存结构]JMM规定了在多线程下对共享数据的读写时&#xff0c;对数据的原子性 有序性 可见性的规则和保障。 原子性 原子性问题: i和i–不是原子性操作! 所以一个i指令会在执行过程中被另一个线程执行! 问题分…

C++ | Leetcode C++题解之第67题二进制求和

题目&#xff1a; 题解&#xff1a; class Solution { public:string addBinary(string a, string b) {string ans;reverse(a.begin(), a.end());reverse(b.begin(), b.end());int n max(a.size(), b.size()), carry 0;for (size_t i 0; i < n; i) {carry i < a.siz…

最佳实践|Apifox 中通过 CryptoJS 给请求参数进行 AES 加密!

假如现在要在 Apifox 中发送一个「登录」的请求&#xff0c;然后我需要将接口中的 password 参数使用 AES 加密算法加密以后&#xff0c;再传给后台服务&#xff0c;这要怎么做&#xff1f; 要在 Apifox 中使用 AES 加密算法对 password 参数进行加密&#xff0c;你需要在「前置…

4 Spring AOP

目录 AOP 简介 传统开发模式 先来看一个需求 解决方案 AOP 图示 Spring 启用 AspectJ 基于 xml 配置 创建 pom.xml 创建 UserService 借口和 UserServiceImpl实现类 创建 LogAdvice 日志通知 创建 log4j.properties 重点&#xff1a;创建 spring-context-xml.xml 配…

如何让 PDF 书签从杂乱无序整洁到明丽清新

1、拉取书签&#xff08;详细步骤看文末扩展阅读&#xff09; 原状态 —— 杂乱无序 自动整理后的状态 —— 错落有致&#xff0c;但摩肩接踵 2、开始整理 全选自动整理后的书签&#xff0c;剪切 访问中英混排排版优化 - 油条工具箱 https://utils.fun/cn-en 1 粘贴 → 2 …

国科大模版修订模式冲突

定位到 \documentclass[twoside]{Style/ucasthesis}%前添加\PassOptionsToPackage{changes,usenames,dvipsnames,table}{xcolor} 完整如下&#xff1a; \PassOptionsToPackage{changes,usenames,dvipsnames,table}{xcolor} \documentclass[twoside]{Style/ucasthesis}% \us…

Sarcasm detection论文解析 |A2Text-Net:一种用于讽刺检测的新型深度神经网络

论文地址 论文地址&#xff1a;A2Text-Net: A Novel Deep Neural Network for Sarcasm Detection | IEEE Conference Publication | IEEE Xplore github:lliyuan1117/A2Text-Net (github.com) 论文首页 A2Text-Net&#xff1a;一种用于讽刺检测的新型深度神经网络 &#x1f4c5…

QT:label标签/进度条的使用

文章目录 设置不同格式的文本显示图片文本对齐/自动换行/缩进/边距LCDNumber倒计时 ProgressBar进度条 设置不同格式的文本 在文本格式中&#xff0c;存在富文本&#xff0c;makedown格式的文本&#xff0c;还有纯文本&#xff0c;下面就依据这三个进行举例 #include "w…