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.mzph.cn/pingmian/6440.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于Spring Boot的在线BLOG网设计与实现

基于Spring Boot的在线BLOG网设计与实现 开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/idea 系统部分展示 前台首页管理界面&#xff0c;用户经过登录前台首页查看通…

【UnityRPG游戏制作】Unity_RPG项目_玩家逻辑相关

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;就业…

HSDB使用教程

HSDB&#xff1a;Hostspot Debugger&#xff0c;JVM内置的工具&#xff0c;用于深入分析JVM运行时的内部状态 启动HSDB java -cp D:/tools/jdk-1.8/lib/sa-jdi.jar sun.jvm.hotspot.HSDB 获取进程id jps 连接到指定进程 查找类 通过查询查找对象 输入查询语句 select d from …

100/篇论文修改?提高营收从拒绝客户开始

进入4月份以来新进询盘客户可谓惨淡&#xff0c;为了完成业绩突破我接了一个并不擅长的订单&#xff0c;最终失败赔钱收场&#xff0c;痛定思痛我决定拒掉不可靠的项目&#xff1a; 不熟悉的领域 超出团队承载范围的 低价绝对低价的项目 今天收到客户询盘我非常的开心&#…

【Java EE】多线程(二)Thread 类与常用方法

&#x1f4da;博客主页&#xff1a;爱敲代码的小杨. ✨专栏&#xff1a;《Java SE语法》 | 《数据结构与算法》 | 《C生万物》 |《MySQL探索之旅》 |《Web世界探险家》 ❤️感谢大家点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb;&#xff0c;您的三连就是我持续更…

直播素材安卓情侣飞行棋v2.22 仿dofm 支持自定义模式—可用直播素材

一个情侣间增进友谊的小游戏非常好玩&#xff0c;适合男孩女孩之间增进感情&#xff01;快和你暗恋的女孩一块玩吧&#xff0c;极速升温 永久免费&#xff01;解锁激活码内容全部畅玩&#xff01;全网最强超级给力&#xff01;真人说书音频 网盘自动获取 链接&#xff1a;http…

重要综述!全文翻译!宫鹏教授、陈镜明教授、梁顺林教授等《Nature Climate Change》!

2013年&#xff0c;由宫鹏教授、陈镜明教授和梁顺林教授等联合发表了一篇《Nature Climate Change》综述文章&#xff0c;其主题是卫星遥感在全球变化中的作用研究。&#xff08;已被引510次&#xff0c;来源谷歌学术&#xff09;。 卫星遥感方式对于气象问题、大气、陆地和海洋…

【机器学习】集成方法---Boosting之AdaBoost

一、Boosting的介绍 1.1 集成学习的概念 1.1.1集成学习的定义 集成学习是一种通过组合多个学习器来完成学习任务的机器学习方法。它通过将多个单一模型&#xff08;也称为“基学习器”或“弱学习器”&#xff09;的输出结果进行集成&#xff0c;以获得比单一模型更好的泛化性…

【中断】【ARM64】学习总结

optee中的异常向量表解读–中断处理解读 https://mp.weixin.qq.com/s/gBsy4YDYTHGRsy2zcVr6Vg

windows ubuntu sed,awk,grep篇:13.其他 awk 命令

目录 85. 使用 printf 格式化输出 86. awk 内置数值函数 87. 随机数生成器 88. 常用字符串函数 89. GAWK/NAWK 的字符串函数 90. GAWK 字符串函数 91.处理参数(ARGC,ARGV,ARGIND) 92. OFMT 93. GAWK 内置的环境变量 94. pgawk – awk 运行分析器 95. 位操作 96.用户…

Linux搭建sqlilabs靶场

提前准备&#xff1a; 文章中所使用到的Linux系统&#xff1a;Ubantu20.4sqlilabs靶场下载地址&#xff1a;GitHub - Audi-1/sqli-labs: SQLI labs to test error based, Blind boolean based, Time based. 一. 安装phpstudy phpstudy安装命令&#xff1a;wget -O install.sh h…

托普利兹矩阵(T矩阵)及其应用(Matlab demo测试)

托普利兹矩阵&#xff08;T矩阵&#xff09;及其应用&#xff08;Matlab demo测试&#xff09; 1. 概念2. Matlab简单测试2.1 生成测试2.2 基本性质及原理2.3 性质验证 3. 其他应用总结3.1 其他性质3.2 文献阅读看到的 参考资料 1. 概念 托普利兹矩阵&#xff0c;简称为T型矩阵…

H3C MSTP 实验

H3C MSTP 实验 实验拓扑 ​​ 实验需求 所有交换机上创建 Vlan10&#xff0c;Vlan20&#xff0c;Vlan30 和 Vlan40所有交换机之间的端口配置为 Trunk&#xff0c;并放行相关 VLAN按照图示分区域配置 MSTP&#xff0c;并配置主备根网桥 实验步骤 VLAN基础配置&#xff08;…

力扣面试150 简化路径 栈 模拟

Problem: 71. 简化路径 思路 &#x1f469;‍&#x1f3eb; 三叶题解 复杂度 时间复杂度: O ( n ) O(n) O(n) 空间复杂度: O ( n ) O(n) O(n) Code class Solution {public String simplifyPath(String path){ArrayDeque<String> d new ArrayDeque<>();…

2022 亚马逊云科技中国峰会,对话开发者论坛

目录 前言 最近整理资料发现还有一些前 2 年的内容没发出来&#xff0c;故补发记录&#xff0c;每年都有新的感悟。 开发者论坛 1. 你认为什么是开发者社区&#xff0c;如何定义一个成功的开发者社区&#xff1f; 我认为可以把开发者社区看成一个 “产品” 来对待&#xff…

【RAG 论文】GenRead:“generate-read“ 可能比 “retrieve-read“ 更有效

论文&#xff1a;Generate rather than Retrieve: Large Language Models are Strong Context Generators ⭐⭐⭐⭐ ICLR 2023 Code: github.com/wyu97/GenRead 一、论文速读 该工作发现&#xff1a;由 LLM 生成的文档中&#xff0c;往往比 retrieved documents 更可能包含正确…

C++校招八股

c类的访问权限与继承方式 公有成员在任何地方都可以被访问&#xff0c;包括类的外部和派生类。受保护成员在类的内部和派生类中可以被访问&#xff0c;但在类的外部不可访问。 私有成员只能在类的内部访问&#xff0c;包括类的成员函数和友元函数&#xff0c;不允许在类的外部…

关于“泼辣”DB 你应该知道的几件事

PolarDB PolarDB for PostgreSQL&#xff08;以下简称 PolarDB&#xff09;是一款阿里云自主研发的企业级数据库产品&#xff0c;采用计算存储分离架构&#xff0c;100% 兼容 PostgreSQL。 PolarDB 的存储与计算能力均可横向扩展&#xff0c;具有高可靠、高可用、弹性扩展等企…

文件(夹)批量重命名数字、字母、日期、中文数字大写小写

首先&#xff0c;需要用到的这个工具&#xff1a; 度娘网盘 提取码&#xff1a;qwu2 蓝奏云 提取码&#xff1a;2r1z 目标是重命名下面5个文件&#xff08;也可以是文件夹等&#xff0c;任意&#xff09;&#xff0c;从大写中文数字“贰”开始 打开工具&#xff0c;找到“文…

使用机器学习确定文本的编程语言

导入必要的库 norman Python 语句&#xff1a;import <span style"color:#000000"><span style"background-color:#fbedbb"><span style"color:#0000ff">import</span> pandas <span style"color:#0000ff&quo…