learn.lianglianglee.com/专栏/Netty 核心原理剖析与 RPC 实践-完/11 另起炉灶:Netty 数据传输载体 ByteBuf 详解.md.html
2022-05-11 18:46:27 +08:00

1171 lines
31 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>11 另起炉灶Netty 数据传输载体 ByteBuf 详解.md</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/00 学好 Netty是你修炼 Java 内功的必经之路.md">00 学好 Netty是你修炼 Java 内功的必经之路.md.html</a>
</li>
<li>
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/01 初识 Netty为什么 Netty 这么流行?.md">01 初识 Netty为什么 Netty 这么流行?.md.html</a>
</li>
<li>
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/02 纵览全局:把握 Netty 整体架构脉络.md">02 纵览全局:把握 Netty 整体架构脉络.md.html</a>
</li>
<li>
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/03 引导器作用:客户端和服务端启动都要做些什么?.md">03 引导器作用:客户端和服务端启动都要做些什么?.md.html</a>
</li>
<li>
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/04 事件调度层:为什么 EventLoop 是 Netty 的精髓?.md">04 事件调度层:为什么 EventLoop 是 Netty 的精髓?.md.html</a>
</li>
<li>
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/05 服务编排层Pipeline 如何协调各类 Handler .md">05 服务编排层Pipeline 如何协调各类 Handler .md.html</a>
</li>
<li>
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/06 粘包拆包问题:如何获取一个完整的网络包?.md">06 粘包拆包问题:如何获取一个完整的网络包?.md.html</a>
</li>
<li>
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/07 接头暗语:如何利用 Netty 实现自定义协议通信?.md">07 接头暗语:如何利用 Netty 实现自定义协议通信?.md.html</a>
</li>
<li>
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/08 开箱即用Netty 支持哪些常用的解码器?.md">08 开箱即用Netty 支持哪些常用的解码器?.md.html</a>
</li>
<li>
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/09 数据传输writeAndFlush 处理流程剖析.md">09 数据传输writeAndFlush 处理流程剖析.md.html</a>
</li>
<li>
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/10 双刃剑:合理管理 Netty 堆外内存.md">10 双刃剑:合理管理 Netty 堆外内存.md.html</a>
</li>
<li>
<a class="current-tab" href="/专栏/Netty 核心原理剖析与 RPC 实践-完/11 另起炉灶Netty 数据传输载体 ByteBuf 详解.md">11 另起炉灶Netty 数据传输载体 ByteBuf 详解.md.html</a>
</li>
<li>
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/12 他山之石:高性能内存分配器 jemalloc 基本原理.md">12 他山之石:高性能内存分配器 jemalloc 基本原理.md.html</a>
</li>
<li>
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/13 举一反三Netty 高性能内存管理设计(上).md">13 举一反三Netty 高性能内存管理设计(上).md.html</a>
</li>
<li>
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/14 举一反三Netty 高性能内存管理设计(下).md">14 举一反三Netty 高性能内存管理设计(下).md.html</a>
</li>
<li>
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/15 轻量级对象回收站Recycler 对象池技术解析.md">15 轻量级对象回收站Recycler 对象池技术解析.md.html</a>
</li>
<li>
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/16 IO 加速:与众不同的 Netty 零拷贝技术.md">16 IO 加速:与众不同的 Netty 零拷贝技术.md.html</a>
</li>
<li>
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/17 源码篇:从 Linux 出发深入剖析服务端启动流程.md">17 源码篇:从 Linux 出发深入剖析服务端启动流程.md.html</a>
</li>
<li>
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/18 源码篇:解密 Netty Reactor 线程模型.md">18 源码篇:解密 Netty Reactor 线程模型.md.html</a>
</li>
<li>
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/19 源码篇:一个网络请求在 Netty 中的旅程.md">19 源码篇:一个网络请求在 Netty 中的旅程.md.html</a>
</li>
<li>
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/20 技巧篇Netty 的 FastThreadLocal 究竟比 ThreadLocal 快在哪儿?.md">20 技巧篇Netty 的 FastThreadLocal 究竟比 ThreadLocal 快在哪儿?.md.html</a>
</li>
<li>
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/21 技巧篇:延迟任务处理神器之时间轮 HashedWheelTimer.md">21 技巧篇:延迟任务处理神器之时间轮 HashedWheelTimer.md.html</a>
</li>
<li>
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/22 技巧篇:高性能无锁队列 Mpsc Queue.md">22 技巧篇:高性能无锁队列 Mpsc Queue.md.html</a>
</li>
<li>
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/23 架构设计:如何实现一个高性能分布式 RPC 框架.md">23 架构设计:如何实现一个高性能分布式 RPC 框架.md.html</a>
</li>
<li>
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/24 服务发布与订阅:搭建生产者和消费者的基础框架.md">24 服务发布与订阅:搭建生产者和消费者的基础框架.md.html</a>
</li>
<li>
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/25 远程通信:通信协议设计以及编解码的实现.md">25 远程通信:通信协议设计以及编解码的实现.md.html</a>
</li>
<li>
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/26 服务治理:服务发现与负载均衡机制的实现.md">26 服务治理:服务发现与负载均衡机制的实现.md.html</a>
</li>
<li>
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/27 动态代理:为用户屏蔽 RPC 调用的底层细节.md">27 动态代理:为用户屏蔽 RPC 调用的底层细节.md.html</a>
</li>
<li>
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/28 实战总结RPC 实战总结与进阶延伸.md">28 实战总结RPC 实战总结与进阶延伸.md.html</a>
</li>
<li>
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/29 编程思想Netty 中应用了哪些设计模式?.md">29 编程思想Netty 中应用了哪些设计模式?.md.html</a>
</li>
<li>
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/30 实践总结Netty 在项目开发中的一些最佳实践.md">30 实践总结Netty 在项目开发中的一些最佳实践.md.html</a>
</li>
<li>
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/31 结束语 技术成长之路:如何打造自己的技术体系.md">31 结束语 技术成长之路:如何打造自己的技术体系.md.html</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>11 另起炉灶Netty 数据传输载体 ByteBuf 详解</h1>
<p>在学习编解码章节的过程中,我们看到 Netty 大量使用了自己实现的 ByteBuf 工具类ByteBuf 是 Netty 的数据容器,所有网络通信中字节流的传输都是通过 ByteBuf 完成的。然而 JDK NIO 包中已经提供了类似的 ByteBuffer 类,为什么 Netty 还要去重复造轮子呢?本节课我会详细地讲解 ByteBuf。</p>
<h3>为什么选择 ByteBuf</h3>
<p>我们首先介绍下 JDK NIO 的 ByteBuffer才能知道 ByteBuffer 有哪些缺陷和痛点。下图展示了 ByteBuffer 的内部结构:</p>
<p><img src="assets/Ciqc1F-3ukmAImo_AAJEEbA2rts301.png" alt="Netty11" /></p>
<p>从图中可知ByteBuffer 包含以下四个基本属性:</p>
<ul>
<li>mark为某个读取过的关键位置做标记方便回退到该位置</li>
<li>position当前读取的位置</li>
<li>limitbuffer 中有效的数据长度大小;</li>
<li>capacity初始化时的空间容量。</li>
</ul>
<p>以上<strong>四个基本属性</strong>的关系是:<strong>mark &lt;= position &lt;= limit &lt;= capacity</strong>。结合 ByteBuffer 的基本属性,不难理解它在使用上的一些缺陷。</p>
<p>第一ByteBuffer 分配的长度是固定的,无法动态扩缩容,所以很难控制需要分配多大的容量。如果分配太大容量,容易造成内存浪费;如果分配太小,存放太大的数据会抛出 BufferOverflowException 异常。在使用 ByteBuffer 时,为了避免容量不足问题,你必须每次在存放数据的时候对容量大小做校验,如果超出 ByteBuffer 最大容量,那么需要重新开辟一个更大容量的 ByteBuffer将已有的数据迁移过去。整个过程相对烦琐对开发者而言是非常不友好的。</p>
<p>第二ByteBuffer 只能通过 position 获取当前可操作的位置,因为读写共用的 position 指针,所以需要频繁调用 flip、rewind 方法切换读写状态,开发者必须很小心处理 ByteBuffer 的数据读写,稍不留意就会出错。</p>
<p>ByteBuffer 作为网络通信中高频使用的数据载体,显然不能够满足 Netty 的需求Netty 重新实现了一个性能更高、易用性更强的 ByteBuf相比于 ByteBuffer 它提供了很多非常酷的特性:</p>
<ul>
<li>容量可以按需动态扩展,类似于 StringBuffer</li>
<li>读写采用了不同的指针,读写模式可以随意切换,不需要调用 flip 方法;</li>
<li>通过内置的复合缓冲类型可以实现零拷贝;</li>
<li>支持引用计数;</li>
<li>支持缓存池。</li>
</ul>
<p>这里我们只是对 ByteBuf 有一个简单的了解,接下来我们就一起看下 ByteBuf 是如何实现的吧。</p>
<h3>ByteBuf 内部结构</h3>
<p>同样我们看下 ByteBuf 的内部结构,与 ByteBuffer 做一个对比。</p>
<p><img src="assets/CgqCHl-3uraAAhvwAASZGuNRMtA960.png" alt="Netty112.png" /></p>
<p>从图中可以看出ByteBuf 包含三个指针:<strong>读指针 readerIndex</strong><strong>写指针 writeIndex</strong><strong>最大容量 maxCapacity</strong>,根据指针的位置又可以将 ByteBuf 内部结构可以分为四个部分:</p>
<p>第一部分是<strong>废弃字节</strong>,表示已经丢弃的无效字节数据。</p>
<p>第二部分是<strong>可读字节</strong>,表示 ByteBuf 中可以被读取的字节内容,可以通过 writeIndex - readerIndex 计算得出。从 ByteBuf 读取 N 个字节readerIndex 就会自增 NreaderIndex 不会大于 writeIndex当 readerIndex == writeIndex 时,表示 ByteBuf 已经不可读。</p>
<p>第三部分是<strong>可写字节</strong>,向 ByteBuf 中写入数据都会存储到可写字节区域。向 ByteBuf 写入 N 字节数据writeIndex 就会自增 N当 writeIndex 超过 capacity表示 ByteBuf 容量不足,需要扩容。</p>
<p>第四部分是<strong>可扩容字节</strong>,表示 ByteBuf 最多还可以扩容多少字节,当 writeIndex 超过 capacity 时,会触发 ByteBuf 扩容,最多扩容到 maxCapacity 为止,超过 maxCapacity 再写入就会出错。</p>
<p>由此可见Netty 重新设计的 ByteBuf 有效地区分了可读、可写以及可扩容数据,解决了 ByteBuffer 无法扩容以及读写模式切换烦琐的缺陷。接下来,我们一起学习下 ByteBuf 的核心 API你可以把它当作 ByteBuffer 的替代品单独使用。</p>
<h3>引用计数</h3>
<p>ByteBuf 是基于引用计数设计的,它实现了 ReferenceCounted 接口ByteBuf 的生命周期是由引用计数所管理。只要引用计数大于 0表示 ByteBuf 还在被使用;当 ByteBuf 不再被其他对象所引用时,引用计数为 0那么代表该对象可以被释放。</p>
<p>当新创建一个 ByteBuf 对象时,它的初始引用计数为 1当 ByteBuf 调用 release() 后,引用计数减 1所以不要误以为调用了 release() 就会保证 ByteBuf 对象一定会被回收。你可以结合以下的代码示例做验证:</p>
<pre><code>ByteBuf buffer = ctx.alloc().directbuffer();
assert buffer.refCnt() == 1;
buffer.release();
assert buffer.refCnt() == 0;
</code></pre>
<p>引用计数对于 Netty 设计缓存池化有非常大的帮助,当引用计数为 0该 ByteBuf 可以被放入到对象池中,避免每次使用 ByteBuf 都重复创建,对于实现高性能的内存管理有着很大的意义。</p>
<p>此外 Netty 可以利用引用计数的特点实现内存泄漏检测工具。JVM 并不知道 Netty 的引用计数是如何实现的,当 ByteBuf 对象不可达时,一样会被 GC 回收掉,但是如果此时 ByteBuf 的引用计数不为 0那么该对象就不会释放或者被放入对象池从而发生了内存泄漏。Netty 会对分配的 ByteBuf 进行抽样分析,检测 ByteBuf 是否已经不可达且引用计数大于 0判定内存泄漏的位置并输出到日志中你需要关注日志中 LEAK 关键字。</p>
<h3>ByteBuf 分类</h3>
<p>ByteBuf 有多种实现类,每种都有不同的特性,下图是 ByteBuf 的家族图谱,可以划分为三个不同的维度:<strong>Heap/Direct</strong><strong>Pooled/Unpooled</strong><strong>Unsafe/非 Unsafe</strong>,我逐一介绍这三个维度的不同特性。</p>
<p><img src="assets/Ciqc1F-3h3WAMF4CAAe4IOav4SA876.png" alt="image" /></p>
<p><strong>Heap/Direct 就是堆内和堆外内存</strong>。Heap 指的是在 JVM 堆内分配底层依赖的是字节数据Direct 则是堆外内存,不受 JVM 限制,分配方式依赖 JDK 底层的 ByteBuffer。</p>
<p><strong>Pooled/Unpooled 表示池化还是非池化内存</strong>。Pooled 是从预先分配好的内存中取出,使用完可以放回 ByteBuf 内存池,等待下一次分配。而 Unpooled 是直接调用系统 API 去申请内存,确保能够被 JVM GC 管理回收。</p>
<p><strong>Unsafe/非 Unsafe 的区别在于操作方式是否安全。</strong> Unsafe 表示每次调用 JDK 的 Unsafe 对象操作物理内存,依赖 offset + index 的方式操作数据。非 Unsafe 则不需要依赖 JDK 的 Unsafe 对象,直接通过数组下标的方式操作数据。</p>
<h3>ByteBuf 核心 API</h3>
<p>我会分为<strong>指针操作</strong><strong>数据读写</strong><strong>内存管理</strong>三个方面介绍 ByteBuf 的核心 API。在开始讲解 API 的使用方法之前,先回顾下之前我们实现的自定义解码器,以便于加深对 ByteBuf API 的理解。</p>
<pre><code>public final void decode(ChannelHandlerContext ctx, ByteBuf in, List&lt;Object&gt; out) {
// 判断 ByteBuf 可读取字节
if (in.readableBytes() &lt; 14) {
return;
}
in.markReaderIndex(); // 标记 ByteBuf 读指针位置
in.skipBytes(2); // 跳过魔数
in.skipBytes(1); // 跳过协议版本号
byte serializeType = in.readByte();
in.skipBytes(1); // 跳过报文类型
in.skipBytes(1); // 跳过状态字段
in.skipBytes(4); // 跳过保留字段
int dataLength = in.readInt();
if (in.readableBytes() &lt; dataLength) {
in.resetReaderIndex(); // 重置 ByteBuf 读指针位置
return;
}
byte[] data = new byte[dataLength];
in.readBytes(data);
SerializeService serializeService = getSerializeServiceByType(serializeType);
Object obj = serializeService.deserialize(data);
if (obj != null) {
out.add(obj);
}
}
</code></pre>
<h4>指针操作 API</h4>
<ul>
<li><strong>readerIndex() &amp; writeIndex()</strong></li>
</ul>
<p>readerIndex() 返回的是当前的读指针的 readerIndex 位置writeIndex() 返回的当前写指针 writeIndex 位置。</p>
<ul>
<li><strong>markReaderIndex() &amp; resetReaderIndex()</strong></li>
</ul>
<p>markReaderIndex() 用于保存 readerIndex 的位置resetReaderIndex() 则将当前 readerIndex 重置为之前保存的位置。</p>
<p>这对 API 在实现协议解码时最为常用,例如在上述自定义解码器的源码中,在读取协议内容长度字段之前,先使用 markReaderIndex() 保存了 readerIndex 的位置,如果 ByteBuf 中可读字节数小于长度字段的值,则表示 ByteBuf 还没有一个完整的数据包,此时直接使用 resetReaderIndex() 重置 readerIndex 的位置。</p>
<p>此外对应的写指针操作还有 markWriterIndex() 和 resetWriterIndex(),与读指针的操作类似,我就不再一一赘述了。</p>
<h4>数据读写 API</h4>
<ul>
<li><strong>isReadable()</strong></li>
</ul>
<p>isReadable() 用于判断 ByteBuf 是否可读,如果 writerIndex 大于 readerIndex那么 ByteBuf 是可读的,否则是不可读状态。</p>
<ul>
<li><strong>readableBytes()</strong></li>
</ul>
<p>readableBytes() 可以获取 ByteBuf 当前可读取的字节数,可以通过 writerIndex - readerIndex 计算得到。</p>
<ul>
<li><strong>readBytes(byte[] dst) &amp; writeBytes(byte[] src)</strong></li>
</ul>
<p>readBytes() 和 writeBytes() 是两个最为常用的方法。readBytes() 是将 ByteBuf 的数据读取相应的字节到字节数组 dst 中readBytes() 经常结合 readableBytes() 一起使用dst 字节数组的大小通常等于 readableBytes() 的大小。</p>
<ul>
<li><strong>readByte() &amp; writeByte(int value)</strong></li>
</ul>
<p>readByte() 是从 ByteBuf 中读取一个字节,相应的 readerIndex + 1同理 writeByte 是向 ByteBuf 写入一个字节,相应的 writerIndex + 1。类似的 Netty 提供了 8 种基础数据类型的读取和写入,例如 readChar()、readShort()、readInt()、readLong()、writeChar()、writeShort()、writeInt()、writeLong() 等,在这里就不详细展开了。</p>
<ul>
<li><strong>getByte(int index) &amp; setByte(int index, int value)</strong></li>
</ul>
<p>与 readByte() 和 writeByte() 相对应的还有 getByte() 和 setByte()get/set 系列方法也提供了 8 种基础类型的读写那么这两个系列的方法有什么区别呢read/write 方法在读写时会改变readerIndex 和 writerIndex 指针,而 get/set 方法则不会改变指针位置。</p>
<h4>内存管理 API</h4>
<ul>
<li><strong>release() &amp; retain()</strong></li>
</ul>
<p>之前已经介绍了引用计数的基本概念,每调用一次 release() 引用计数减 1每调用一次 retain() 引用计数加 1。</p>
<ul>
<li><strong>slice() &amp; duplicate()</strong></li>
</ul>
<p>slice() 等同于 slice(buffer.readerIndex(), buffer.readableBytes()),默认截取 readerIndex 到 writerIndex 之间的数据,最大容量 maxCapacity 为原始 ByteBuf 的可读取字节数,底层分配的内存、引用计数都与原始的 ByteBuf 共享。</p>
<p>duplicate() 与 slice() 不同的是duplicate()截取的是整个原始 ByteBuf 信息,底层分配的内存、引用计数也是共享的。如果向 duplicate() 分配出来的 ByteBuf 写入数据,那么都会影响到原始的 ByteBuf 底层数据。</p>
<ul>
<li><strong>copy()</strong></li>
</ul>
<p>copy() 会从原始的 ByteBuf 中拷贝所有信息,所有数据都是独立的,向 copy() 分配的 ByteBuf 中写数据不会影响原始的 ByteBuf。</p>
<p>到底为止ByteBuf 的核心 API 我们基本已经介绍完了ByteBuf 读写指针分离的小设计,确实带来了很多实用和便利的功能,在开发的过程中不必再去想着 flip、rewind 这种头疼的操作了。</p>
<h3>ByteBuf 实战演练</h3>
<p>学习完 ByteBuf 的内部构造以及核心 API 之后,我们下面通过一个简单的示例演示一下 ByteBuf 应该如何使用,代码如下所示。</p>
<pre><code>public class ByteBufTest {
public static void main(String[] args) {
ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(6, 10);
printByteBufInfo(&quot;ByteBufAllocator.buffer(5, 10)&quot;, buffer);
buffer.writeBytes(new byte[]{1, 2});
printByteBufInfo(&quot;write 2 Bytes&quot;, buffer);
buffer.writeInt(100);
printByteBufInfo(&quot;write Int 100&quot;, buffer);
buffer.writeBytes(new byte[]{3, 4, 5});
printByteBufInfo(&quot;write 3 Bytes&quot;, buffer);
byte[] read = new byte[buffer.readableBytes()];
buffer.readBytes(read);
printByteBufInfo(&quot;readBytes(&quot; + buffer.readableBytes() + &quot;)&quot;, buffer);
printByteBufInfo(&quot;BeforeGetAndSet&quot;, buffer);
System.out.println(&quot;getInt(2): &quot; + buffer.getInt(2));
buffer.setByte(1, 0);
System.out.println(&quot;getByte(1): &quot; + buffer.getByte(1));
printByteBufInfo(&quot;AfterGetAndSet&quot;, buffer);
}
private static void printByteBufInfo(String step, ByteBuf buffer) {
System.out.println(&quot;------&quot; + step + &quot;-----&quot;);
System.out.println(&quot;readerIndex(): &quot; + buffer.readerIndex());
System.out.println(&quot;writerIndex(): &quot; + buffer.writerIndex());
System.out.println(&quot;isReadable(): &quot; + buffer.isReadable());
System.out.println(&quot;isWritable(): &quot; + buffer.isWritable());
System.out.println(&quot;readableBytes(): &quot; + buffer.readableBytes());
System.out.println(&quot;writableBytes(): &quot; + buffer.writableBytes());
System.out.println(&quot;maxWritableBytes(): &quot; + buffer.maxWritableBytes());
System.out.println(&quot;capacity(): &quot; + buffer.capacity());
System.out.println(&quot;maxCapacity(): &quot; + buffer.maxCapacity());
}
}
</code></pre>
<p>程序的输出结果在此我就不贴出了,建议你可以先尝试思考 readerIndex、writerIndex 是如何改变的,然后再动手跑下上述代码,验证结果是否正确。</p>
<p>结合代码示例,我们总结一下 ByteBuf API 使用时的注意点:</p>
<ul>
<li>write 系列方法会改变 writerIndex 位置,当 writerIndex 等于 capacity 的时候Buffer 置为不可写状态;</li>
<li>向不可写 Buffer 写入数据时Buffer 会尝试扩容,但是扩容后 capacity 最大不能超过 maxCapacity如果写入的数据超过 maxCapacity程序会直接抛出异常</li>
<li>read 系列方法会改变 readerIndex 位置get/set 系列方法不会改变 readerIndex/writerIndex 位置。</li>
</ul>
<h3>总结</h3>
<p>本节课我们介绍了 Netty 强大的数据容器 ByteBuf它不仅解决了 JDK NIO 中 ByteBuffer 的缺陷,而且提供了易用性更强的接口。很多开发者已经使用 ByteBuf 代替 ByteBuffer即便他没有在写一个网络应用也会单独使用 ByteBuf。ByteBuf 作为 Netty 中最基础的数据结构,你必须熟练掌握它,这是你精通 Netty 的必经之路,接下来的课程我们会围绕 ByteBuf 介绍关于 Netty 内存管理的相关设计。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/10 双刃剑:合理管理 Netty 堆外内存.md">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/12 他山之石:高性能内存分配器 jemalloc 基本原理.md">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"7099735439593d60","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>