Netty之缓冲区ByteBuf解读(一)

Stella981
• 阅读 669

Netty之缓冲区ByteBuf解读(一)

> Netty 在数据传输过程中,会使用缓冲区设计来提高传输效率。虽然,Java 在 NIO 编程中已提供 ByteBuffer 类进行使用,但是在使用过程中,其编码方式相对来说不太友好,也存在一定的不足。所以高性能的 Netty 框架实现了一套更加强大,完善的 ByteBuf,其设计理念也是堪称一绝。

ByteBuffer 分析

在分析 ByteBuf 之前,先简单讲下 ByteBuffer 类的操作。便于更好理解 ByteBuf 。

ByteBuffer 的读写操作共用一个位置指针,读写过程通过以下代码案例分析:

// 分配一个缓冲区,并指定大小
ByteBuffer buffer = ByteBuffer.allocate(100);
// 设置当前最大缓存区大小限制
buffer.limit(15);
System.out.println(String.format("allocate: pos=%s lim=%s cap=%s", buffer.position(), buffer.limit(), buffer.capacity()));

String content = "ytao公众号";
// 向缓冲区写入数据
buffer.put(content.getBytes());
System.out.println(String.format("put: pos=%s lim=%s cap=%s", buffer.position(), buffer.limit(), buffer.capacity()));

其中打印了缓冲区三个参数,分别是:

  • position 读写指针位置
  • limit 当前缓存区大小限制
  • capacity 缓冲区大小

打印结果:

Netty之缓冲区ByteBuf解读(一)

当我们写入内容后,读写指针值为 13,ytao公众号英文字符占 1 个 byte,每个中文占 4 个 byte,刚好 13,小于设置的当前缓冲区大小 15。

接下来,读取内容里的 ytao 数据:

buffer.flip();
System.out.println(String.format("flip: pos=%s lim=%s cap=%s", buffer.position(), buffer.limit(), buffer.capacity()));

byte[] readBytes = new byte[4];
buffer.get(readBytes);
System.out.println(String.format("get(4): pos=%s lim=%s cap=%s", buffer.position(), buffer.limit(), buffer.capacity()));

String readContent = new String(readBytes);
System.out.println("readContent:"+readContent);

读取内容需要创建个 byte 数组来接收,并制定接收的数据大小。

在写入数据后再读取内容,必须主动调用ByteBuffer#flipByteBuffer#clear

ByteBuffer#flip它会将写入数据后的指针位置值作为当前缓冲区大小,再将指针位置归零。会使写入数据的缓冲区改为待取数据的缓冲区,也就是说,读取数据会从刚写入的数据第一个索引作为读取数据的起始索引。

ByteBuffer#flip相关源码:

Netty之缓冲区ByteBuf解读(一)

ByteBuffer#clear则会重置 limit 为默认值,与 capacity 大小相同。

Netty之缓冲区ByteBuf解读(一)

接下读取剩余部分内容:

第二次读取的时候,可使用buffer#remaining来获取大于或等于剩下的内容的字节大小,该函数实现为limit - position,所以当前缓冲区域一定在这个值范围内。

readBytes = new byte[buffer.remaining()];
buffer.get(readBytes);
System.out.println(String.format("get(remaining): pos=%s lim=%s cap=%s", buffer.position(), buffer.limit(), buffer.capacity()));

打印结果:

Netty之缓冲区ByteBuf解读(一)

以上操作过程中,索引变化如图:

Netty之缓冲区ByteBuf解读(一)

ByteBuf 读写操作

ByteBuf 有读写指针是分开的,分别是buf#readerIndexbuf#writerIndex,当前缓冲器大小buf#capacity

这里缓冲区被两个指针索引和容量划分为三个区域:

  • 0 -> readerIndex 为已读缓冲区域,已读区域可重用节约内存,readerIndex 值大于或等于 0
  • readerIndex -> writerIndex 为可读缓冲区域,writerIndex 值大于或等于 readerIndex
  • writerIndex -> capacity 为可写缓冲区域,capacity 值大于或等于 writerIndex

如下图所示:

Netty之缓冲区ByteBuf解读(一)

分配缓冲区

ByteBuf 分配一个缓冲区,仅仅给定一个初始值就可以。默认是 256。初始值不像 ByteBuffer 一样是最大值,ByteBuf 的最大值是Integer.MAX_VALUE

ByteBuf buf = Unpooled.buffer(13);
System.out.println(String.format("init: ridx=%s widx=%s cap=%s", buf.readerIndex(), buf.writerIndex(), buf.capacity()));

打印结果:

Netty之缓冲区ByteBuf解读(一)

写操作

ByteBuf 写操作和 ByteBuffer 类似,只是写指针是单独记录的,ByteBuf 的写操作支持多种类型,有以下多个API:

Netty之缓冲区ByteBuf解读(一)

写入字节数组类型:

String content = "ytao公众号";
buf.writeBytes(content.getBytes());
System.out.println(String.format("write: ridx=%s widx=%s cap=%s", buf.readerIndex(), buf.writerIndex(), buf.capacity()));

打印结果:

Netty之缓冲区ByteBuf解读(一)

索引示意图:

Netty之缓冲区ByteBuf解读(一)

读操作

一样的,ByteBuf 写操作和 ByteBuffer 类似,只是写指针是单独记录的,ByteBuf 的读操作支持多种类型,有以下多个API:

Netty之缓冲区ByteBuf解读(一)

从当前 readerIndex 位置读取四个字节内容:

byte[] dst = new byte[4];
buf.readBytes(dst);
System.out.println(new String(dst));
System.out.println(String.format("read(4): ridx=%s widx=%s cap=%s", buf.readerIndex(), buf.writerIndex(), buf.capacity()));

打印结果:

Netty之缓冲区ByteBuf解读(一)

索引示意图:

Netty之缓冲区ByteBuf解读(一)

ByteBuf 动态扩容

通过上面的 ByteBuffer 分配缓冲区例子,向里面添加 [ytao公众号ytao公众号] 内容,使写入的内容大于 limit 的值。

ByteBuffer buffer = ByteBuffer.allocate(100);
buffer.limit(15);

String content = "ytao公众号ytao公众号";
buffer.put(content.getBytes());

运行结果异常:

Netty之缓冲区ByteBuf解读(一)

内容字节大小超过了 limit 的值时,缓冲区溢出异常,所以我们每次写入数据前,得检查缓区大小是否有足够空间,这样对编码上来说,不是一个好的体验。

使用 ByteBuf 添加同样的内容,给定同样的初始容器大小。

ByteBuf buf = Unpooled.buffer(15);
String content = "ytao公众号ytao公众号";
buf.writeBytes(content.getBytes());
System.out.println(String.format("write: ridx=%s widx=%s cap=%s", buf.readerIndex(), buf.writerIndex(), buf.capacity()));

打印运行结果:

Netty之缓冲区ByteBuf解读(一)

通过上面打印信息,可以看到 cap 从设置的 15 变为了 64,当我们容器大小不够时,就是进行扩容,接下来我们分析扩容过程中是如何做的。 进入 writeBytes 里面:

Netty之缓冲区ByteBuf解读(一)

校验写入内容长度:

Netty之缓冲区ByteBuf解读(一)

在可写区域检查里:

  • 如果写入内容为空,抛出非法参数异常。
  • 如果写入内容大小小于或等于可写区域大小,则返回当前缓冲区,当中的writableBytes()函数为可写区域大小capacity - writerIndex
  • 如果写入内容大小大于最大可写区域大小,则抛出索引越界异常。
  • 最后剩下条件的就是写入内容大小大于可写区域,小于最大区域大小,则分配一个新的缓冲区域。

Netty之缓冲区ByteBuf解读(一)

在容量不足,重新分配缓冲区的里面,以 4M 为阀门:

  • 如果待写内容刚好为 4M, 那么就分配 4M 的缓冲区。
  • 如果待写内容超过这个阀门且与阀门值之和不大于最大容量值,就分配(阀门值+内容大小值)的缓冲区;如果超过这个阀门且与阀门值之和大于最大容量值,则分配最大容量的缓冲区。
  • 如果待写内容不超过阀门值且大于 64,那么待分配缓冲区大小就以 64 的大小进行倍增,直到相等或大于待写内容。
  • 如果待写内容不超过阀门值且不大于 64,则返回待分配缓冲区大小为 64。

Netty之缓冲区ByteBuf解读(一)

最后

> Netty 实现的缓冲区,八个基本类型中,除了布尔类型,其他7种都有自己对应的 Buffer,但是实际使用过程中, ByteBuf 才是我们尝试用的,它可兼容任何类型。ByteBuf 在 Netty 体系中是最基础也是最重要的一员,要想更好掌握和使用 Netty,先理解并掌握 ByteBuf 是必需条件之一。

个人博客: https://ytao.top

关注公众号 【ytao】,更多原创好文

Netty之缓冲区ByteBuf解读(一)

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
2年前
Java爬虫之JSoup使用教程
title:Java爬虫之JSoup使用教程date:201812248:00:000800update:201812248:00:000800author:mecover:https://imgblog.csdnimg.cn/20181224144920712(https://www.oschin
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
为什么mysql不推荐使用雪花ID作为主键
作者:毛辰飞背景在mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究
Python进阶者 Python进阶者
5个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这