【Netty】字节缓冲区 ByteBuf (六)(上)

news2025/1/11 9:56:54

文章目录

  • 前言
  • 一、ByteBuf类
  • 二、ByteBuffer 实现原理
    • 2.1 ByteBuffer 写入模式
    • 2.2 ByteBuffer 读取模式
    • 2.3 ByteBuffer 写入模式切换为读取模式
    • 2.4 clear() 与 compact() 方法
    • 2.5 ByteBuffer 使用案例
  • 总结

前言

回顾Netty系列文章:

  • Netty 概述(一)
  • Netty 架构设计(二)
  • Netty Channel 概述(三)
  • Netty ChannelHandler(四)
  • ChannelPipeline源码分析(五)

网络数据传输的基本单位是字节,缓冲区就是存储字节的容器。在存取字节时,会先把字节放入缓冲区,再在操作缓冲区实现字节的批量存储以提升性能。

Java NIO 提供了ByteBuffer 作为它的缓冲区,但是这个类用起来过于复杂,而且也有些繁琐。因此,Netty 自己实现了 ByteBuf 以替代 ByteBuffer 。

本篇文章就来介绍Netty自己缓冲区的作用。

一、ByteBuf类

缓冲区可以简单理解为一段内存区域,某些情况下,如果程序频繁的操作一个资源(如文件或数据库),则性能会很低,此时为了提升性能,就可以将一部分数据暂时读入内存的一块区域之中,以后直接从此区域中读取数据即可。因为读取内存速度会很快,这样就可以提升程序的性能。
因此,缓冲区决定了网络数据处理的性能。

ByteBuf 被设计为一个可从底层解决 ByteBuffer 问题,并可满足日常网络应用开发需要的缓冲类型,其特点如下:

  • 允许使用自定义的缓冲区类型。
  • 通过内置的复合缓冲区类型实现了透明的零拷贝。
  • 容量可以按需增长(类似于 JDK 的 StringBuilder)。
  • 在读和写这两种模式之间切换不需要调用 ByteBuffer 的 flip()方法;
  • 正常情况下具有比 ByteBuffer 更快的响应速度。

二、ByteBuffer 实现原理

使用 ByteBuffer 读写数据一般遵循以下4个步骤。

  • 写入数据到 ByteBuffer 。
  • 调用 flip()方法。
  • 从 ByteBuffer 中读取数据。
  • 调用clear()方法或者compact()方法。

当向 ByteBuffer 写入数据时, ByteBuffer 会记录下写了多少数据。一旦读取数据时,需要通过flip()方法将 ByteBuffer 从写入模式切换到读取模式。在读取模式下,可以读取之前写入 ByteBuffer 的所有数据。

一旦读完了所有数据,就需要清空缓冲区,让它可以再次被写入。有两种方式能清空缓冲区:调用clear()方法或者compact()方法。clear()方法会清空整个缓冲区。compact()方法只会清空已经读取过的数据,任何未读的数据都被移到缓冲区的起初处,新写入的数据将放到缓冲区未读数据的后面。

下面是一个 Java NIO 实现 服务器端实例中使用ByteBuffe的例子:

// 可写
if (key.isWritable()) {
    SocketChannel client = (SocketChannel) key.channel();
    ByteBuffer output = (ByteBuffer) key.attachment();
    output.flip();
    client.write(output);

    System.out.println("NonBlokingEchoServer  -> " 
                       + client.getRemoteAddress() + ":" + output.toString());

    output.compact();

    key.interestOps(SelectionKey.OP_READ);
}

对于ByteBuffer,其主要有五个属性:mark,position,limit,capacity和array。这五个属性的作用如下:

  • mark:记录了当前所标记的索引下标。
  • position:对于写入模式,表示当前可写入数据的下标,对于读取模式,表示接下来可以读取的数据的下标。
  • limit:对于写入模式,表示当前可以写入的数组大小,默认为数组的最大长度,对于读取模式,表示当前最多可以读取的数据的位置下标。
  • capacity:表示当前数组的容量大小。
  • array:保存了当前写入的数据。

上述变量存在以下关系:

0 <= mark <= position <=limit <= capacity

这几个数据中,除了 array 是用于保存数据的以外,这里最需要关注的是position,limit和capacity三个属性,因为对于写入和读取模式,这三个属性的表示的含义大不一样。

2.1 ByteBuffer 写入模式

如下图所示为初始状态和写入3个字节之后position,limit和capacity三个属性的状态:
在这里插入图片描述

从图中可以看出,在写入模式下:

  • limit指向的始终是当前可最多写入的数组索引下标。
  • position指向的则是下一个可以写入的数据的索引位置。
  • capacity则始终不会变化,即为数组大小。

2.2 ByteBuffer 读取模式

假设我们按照上述方式在初始长度为6的 ByteBuffer 中写入了三个字节的数据,此时我们将模式切换为读取模式,那么这里的position,limit和capacity则变为如下形式:

在这里插入图片描述

可以看到,当切换为读取模式之后:

  • limit则指向了最后一个可读取数据的下一个位置,表示最多可读取的数据。

  • position则指向了数组的初始位置,表示下一个可读取的数据的位置。

  • capacity还是表示数组的最大容量。

这里当我们一个一个读取数据的时候,position就会依次往下切换,当与limit重合时,就表示当前ByteBuffer中已没有可读取的数据了。

2.3 ByteBuffer 写入模式切换为读取模式

读写切换时要调用flip()方法。flip()方法的核心源码如下:

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

从上述源码可以看出,执行flip()后,将limit设置为position,然后将该position设置为0。

2.4 clear() 与 compact() 方法

一旦读取完 ByteBuffer 中的数据,需要让 ByteBuffer 准备好再次被写入。可以通过clear()或compact()方法来完成。

如果调用的是clear()方法,position将被设置为0,limit将被设置为capacity的值。换句话说,ByteBuffer 被清空了。ByteBuffer 中的数据并未清除,只是这些标记告诉我们可以从哪里开始往 ByteBuffer 里写数据。

如果 ByteBuffer 中有一些未读的数据,调用clear()方法,数据将被遗忘,意味着不再有任何标记标注哪些数据被读过,哪些还没有。

clear()方法的核心源码如下:

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

如果 ByteBuffer 中仍有未读的数据,且后续还需要这些数据,但是此时想要先写些数据,那么使用compact()方法。
compact()方法将所有的未读的数据复制到 ByteBuffer 起始处。然后将 position 设置到最后一个未读元素的后面。limit属性依然像clear()方法一样设置成capacity。现在 ByteBuffer 准备好写数据了,但是不会覆盖未读的数据。
compact()方法核心源码如下:

public ByteBuffer compact() {

    int pos = position();
    int lim = limit();
    assert (pos <= lim);
    int rem = (pos <= lim ? lim - pos : 0);

    unsafe.copyMemory(ix(pos), ix(0), (long)rem << 0);
    position(rem);
    limit(capacity());
    discardMark();
    return this;

}

2.5 ByteBuffer 使用案例

为了更好的理解 ByteBuffer ,我们来看一些示例:

public class ByteBufferDemo {

	public static void main(String[] args) {
		// 创建一个缓冲区
		ByteBuffer buffer = ByteBuffer.allocate(10);
		System.out.println("------------初始时缓冲区------------");
		printBuffer(buffer);

		// 添加一些数据到缓冲区中
		System.out.println("------------添加数据到缓冲区------------");

		String s = "love";
		buffer.put(s.getBytes());
		printBuffer(buffer);

		// 切换成读模式
		System.out.println("------------执行flip切换到读取模式------------");
		buffer.flip();
		printBuffer(buffer);

		// 读取数据
		System.out.println("------------读取数据------------");

		// 创建一个limit()大小的字节数组(因为就只有limit这么多个数据可读)
		byte[] bytes = new byte[buffer.limit()];

		// 将读取的数据装进我们的字节数组中
		buffer.get(bytes);
		printBuffer(buffer);

		// 执行compact
		System.out.println("------------执行compact------------");
		buffer.compact();
		printBuffer(buffer);

		// 执行clear
		System.out.println("------------执行clear清空缓冲区------------");
		buffer.clear();
		printBuffer(buffer);

	}

	/**
	 * 打印出ByteBuffer的信息
	 * 
	 * @param buffer
	 */
	private static void printBuffer(ByteBuffer buffer) {
		System.out.println("mark:" + buffer.mark());
		System.out.println("position:" + buffer.position());
		System.out.println("limit:" + buffer.limit());
		System.out.println("capacity:" + buffer.capacity());
	}
}

输出结果:

------------初始时缓冲区------------
mark:java.nio.HeapByteBuffer[pos=0 lim=10 cap=10]
position:0
limit:10
capacity:10
------------添加数据到缓冲区------------
mark:java.nio.HeapByteBuffer[pos=4 lim=10 cap=10]
position:4
limit:10
capacity:10
------------执行flip切换到读取模式------------
mark:java.nio.HeapByteBuffer[pos=0 lim=4 cap=10]
position:0
limit:4
capacity:10
------------读取数据------------
mark:java.nio.HeapByteBuffer[pos=4 lim=4 cap=10]
position:4
limit:4
capacity:10
------------执行compact------------
mark:java.nio.HeapByteBuffer[pos=0 lim=10 cap=10]
position:0
limit:10
capacity:10
------------执行clear清空缓冲区------------
mark:java.nio.HeapByteBuffer[pos=0 lim=10 cap=10]
position:0
limit:10
capacity:10

Process finished with exit code 0

总结

本文首先展示了ByteBuffer在写入模式和读取模式下内部的一个状态,然后分析了clear() 与 compact() 方法的源码,最后讲解了ByteBuffer的使用案例。

下节我们再来分析ByteBuf实现原理。

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

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

相关文章

【2023 · CANN训练营第一季】昇腾AI入门课(TensorFlow)——第一章 昇腾AI基础知识介绍

一、昇腾AI全栈架构 异腾AI全栈可以分成四个大部分: 1.应用使能层面&#xff0c;此层面通常包含用于部署模型的软硬件&#xff0c;例如API、SDK、部署平 台&#xff0c;模型库等等。 2.AI框架层面&#xff0c;此层面包含用于构建模型的训练框架&#xff0c;例如华为的MindSpore…

Redis-数据结构

前言 ​ 了解Redis&#xff0c;都大概知道Redis有5种基本数据类型&#xff1a;字符串(string)、列表(list)、哈希(hash)、集合(set)、有序集合(zset)、5.0中Stream数据类型。但是这些数据类型的底层都是按照对象结构与对应的编码组合而成。这也就是说有的底层数据结构可以是多…

Python+Yolov5果树上的水果(苹果)检测识别

程序示例精选 PythonYolov5果树上的水果(苹果)检测识别 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对<<PythonYolov5果树上的水果(苹果)检测识别>>编写代码&#xff0c;代码整洁…

Spring Data Mongo更新整个对象

第一步&#xff1a;在pom.xml文件中引入下述依赖&#xff0c;当前Spring Boot的版本为 2.7.6&#xff1a; <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-mongodb</artifactId><version>…

【产品人卫朋】华为IPD体系:IPD相关术语

目录 术语合集 课程 术语合集 BB&#xff1a;building block&#xff0c;组件 BG&#xff1a;business group&#xff0c;业务群 BLM&#xff1a;business leadership model&#xff0c;业务领先模型 BMT&#xff1a;business management team&#xff0c;业务管理团队 B…

这6个超实用的图片素材网站,高清、免费,赶紧马住

推荐6个超实用的图片素材网站&#xff0c;高清无水印&#xff0c;绝对值得收藏&#xff01; 1、菜鸟图库 https://www.sucai999.com/pic.html#?vNTYxMjky 网站主要是为新手设计师提供免费素材的&#xff0c;素材的质量都很高&#xff0c;类别也很多&#xff0c;像平面、UI、…

Restful接口开发与测试—接口测试

开发完接口&#xff0c;接下来我们需要对我们开发的接口进行测试。接口测试的方法比较多&#xff0c;使用接口工具或者Python来测试都可以&#xff0c;工具方面比如之前我们学习过的Postman或者Jmeter &#xff0c;Python脚本测试可以使用Requests unittest来测试。 测试思路…

数据结构中常见的树

二叉树&#xff1a;每个子节点只有两个节点的树&#xff0c;每个结点至多拥有两棵子树(即二叉树中不存在度大于2的结 点)&#xff0c;并且&#xff0c;二叉树的子树有左右之分&#xff0c;其次序不能任意颠倒 我们一般在解题过程中二叉树有两种主要的形式&#xff1a;满二叉树…

徐延涛:医疗健康企业如何重构客户管理的“营销”与“服务”?

随着人口老龄化和生活健康水平的提升&#xff0c;中国的医疗健康行业市场规模前景向好。《2023易凯资本中国健康产业白皮书》显示&#xff0c;从2022年到2030年的八年里&#xff0c;中国健康产业的整体规模将从10万亿元增长到接近20万亿&#xff0c;年复合增长率将达到9.5%-10%…

TS入门(TS类型有哪些?怎么使用?)

TS简介 TS&#xff08;TypeScript&#xff09;是一种由微软开发的开源编程语言&#xff0c;它是 JavaScript 的超集&#xff0c;能够为 JavaScript 添加静态类型检查和面向对象编程的特性。TS 可以在编译时进行类型检查&#xff0c;从而提高代码的可读性、可维护性和可靠性&am…

PMP课堂模拟题目及解析(第12期)

111. 客户拒绝了一项交付成果&#xff0c;因为它不符合约定的质量规格&#xff0c;项目团队调查该问题&#xff0c;并确定供应商提供的零件有问题&#xff0c;供应商拒绝纠正这种情况。项目经理应该审查什么&#xff1f; A. 与供应商订立的服务水平协议 B. 采购管理计划和合…

从领英退出中国,解析融云《社交泛娱乐出海作战地图》从0到1出海方法论

近期&#xff0c;“领英职场”宣布将于 2023 年 8 月 9 日起正式停止服务。移步【融云全球互联网通信云】回复“地图”免费领 一时之间&#xff0c;网友纷纷送上祭文。有人觉得猝不及防&#xff0c;但更多人直言并不意外。 领英在中国的折戟终局&#xff0c;似乎从 2021 年改版…

chatgpt赋能python:PythonSave函数:保存和保护你的数据

Python Save函数&#xff1a;保存和保护你的数据 Python Save函数是Python编程中最常用的函数之一。它允许开发者将数据保存到文件或数据库中&#xff0c;在未来的操作中访问和使用。无论你是处理大数据集还是需要保护数据免受未经授权访问&#xff0c;Python Save函数都可以为…

Hegegraph的Gremlin语言(全)

Hegegraph的Gremlin语言&#xff08;全&#xff09; 内容 • 基本概念 • Step讲解 • HugeGraph特有Gremlin语句&#xff08;schema相关&#xff09;基本概念 • Gremlin • 是一门图的查询语言&#xff0c;地位作用与数据库的 SQL相当 • 支持图数据的增、删、改、查 • 图…

鸿蒙Hi3861学习十九-DevEco Device Tool源码获取、编译、下载

一、简介 在上一篇文章中&#xff0c;已经讲述了如何在Windows通过Remote SSH远程连接Linux下的DevEco Device Tool。这篇文章&#xff0c;来说一下关于源码的获取、编译与下载。建议先按照上一篇文章进行环境搭建。 鸿蒙Hi3861学习十八-DevEco Device Tool环境搭建_t_guest的…

运动员最佳配对问题——算法设计与分析(C实现)

目录 一、问题简述 二、分析 三、代码展示 四、结果验证 一、问题简述 问题描述&#xff1a;羽毛球队有男女运动员各n人。给定2个n*n矩阵P和Q。P[i][j]是男运动员i和女运动员j配对组成混合双打的男运动员竞争优势&#xff1b;Q[i][j]是男运动员i和女运动员j配合的女运动员竞…

msvcr110.dll丢失的解决方法,多种方法助你解决msvcr110.dll丢失

当您在尝试打开某个程序或游戏时&#xff0c;可能会看到一个错误消息&#xff0c;提示您的计算机缺少msvcr110.dll文件。这是因为该文件是Microsoft Visual C Redistributable库的一部分&#xff0c;缺少它可能会导致应用程序无法正常运行。在本文中&#xff0c;我们将详细介绍…

【接口测试】JMeter接口关联测试

‍‍1 前言 我们来学习接口管理测试&#xff0c;这就要使用到JMeter提供的JSON提取器和正则表达式提取器了&#xff0c;下面我们来看看是如何使用的吧。 2 JSON提取器 1、添加JSON提取器 在线程组右键 > 添加 > 后置处理器 > JSON提取器 2、JSON提取器参数说明 N…

19-03 基于业务场景的架构技术选型

Java架构师系列导航目录 金融领域的挑战与架构设计 金融领域的方向 借贷保险证券交易 互联网金融 vs 传统金融 满足更广泛群体的金融需求增强金融普惠性提高金融服务效率 互联网金融前景 近十年蓬勃发展&#xff0c;朝阳行业&#xff1a;花呗、借呗、微粒贷、余额宝双刃剑&am…

【案例教程】基于“遥感+”蓝碳储量估算、红树林信息提取实践技术应用与科研论文写作

光谱和图像是人们观察世界的两种方式&#xff0c;高光谱遥感通过“图谱合一”的技术创新将两者结合起来&#xff0c;大大提高了人们对客观世界的认知能力&#xff0c;本来在宽波段遥感中不可探测的物质&#xff0c;在高光谱遥感中能被探测。以高光谱遥感为核心&#xff0c;构建…