Spring 核心技术 - 数据缓冲区和编解码器

Spring 学习指南大全
Spring 核心技术

官方文档版本 Version 5.2.22.RELEASE

数据缓冲区和编解码器

Java NIO 提供 ByteBuffer,但许多库在其上构建自己的字节缓冲区 API,特别是对于重用缓冲区和/或使用直接缓冲区有利于性能的网络操作。 例如,Netty 具有 ByteBuf 层次结构,Undertow 使用 XNIO,Jetty 使用池化字节缓冲区并带有要释放的回调,等等。 spring-core 模块提供了一组抽象来处理各种字节缓冲区 API,如下所示:

  • DataBufferFactory 抽象了数据缓冲区的创建。
  • DataBuffer 表示一个字节缓冲区,它可以被池化。
  • DataBufferUtils 为数据缓冲区提供实用方法。
  • Codecs:编解码器将流数据缓冲区流解码或编码为更高级别的对象。
DataBufferFactory

DataBufferFactory 用于通过以下两种方式之一创建数据缓冲区:

  • 分配一个新的数据缓冲区,如果知道的话,可以选择预先指定容量,即使 DataBuffer 的实现可以按需增长和缩小,这也会更有效。
  • 包装现有的 byte[] 或 java.nio.ByteBuffer,它使用 DataBuffer 实现装饰给定数据并且不涉及分配。

请注意,WebFlux 应用程序不会直接创建 DataBufferFactory,而是通过客户端的 ServerHttpResponse 或 ClientHttpRequest 访问它。 工厂的类型取决于底层客户端或服务器,例如 NettyDataBufferFactory 用于 Reactor Netty, DefaultDataBufferFactory 用于其他。

DataBuffer

DataBuffer 接口提供与 java.nio.ByteBuffer 类似的操作,但也带来了一些额外的好处,其中一些是受 Netty ByteBuf 启发的。 以下是部分福利列表:

  • 以独立位置读取和写入,即不需要调用 flip() 在读取和写入之间交替。
  • 与 java.lang.StringBuilder 一样按需扩展容量。
  • 通过 PooledDataBuffer 池化缓冲区和引用计数。
  • 以 java.nio.ByteBuffer、InputStream 或 OutputStream 的形式查看缓冲区。
  • 确定给定字节的索引或最后一个索引。
PooledDataBuffer

正如 ByteBuffer 的 Javadoc 中所解释的,字节缓冲区可以是直接的或非直接的。 直接缓冲区可以驻留在 Java 堆之外,这消除了对本地 I/O 操作进行复制的需要。 这使得直接缓冲区对于通过套接字接收和发送数据特别有用,但它们的创建和释放成本也更高,这导致了池化缓冲区的想法。

PooledDataBuffer 是 DataBuffer 的扩展,它有助于引用计数,这对于字节缓冲池至关重要。 它是如何工作的? 当分配 PooledDataBuffer 时,引用计数为 1。调用 retain() 增加计数,调用 release() 减少计数。 只要计数大于0,就保证缓冲区不会被释放。 当计数减少到 0 时,可以释放池化缓冲区,这实际上可能意味着为缓冲区保留的内存返回到内存池。

请注意,与其直接对 PooledDataBuffer 进行操作,在大多数情况下,最好使用 DataBufferUtils 中的便捷方法,这些方法仅在 DataBuffer 是 PooledDataBuffer 的实例时才对 DataBuffer 应用释放或保留。

DataBufferUtils

DataBufferUtils 提供了许多实用方法来操作数据缓冲区:

  • 将数据缓冲区流加入可能具有零副本的单个缓冲区,例如 通过复合缓冲区,如果底层字节缓冲区 API 支持的话。
  • 将 InputStream 或 NIO Channel 转换为 Flux<DataBuffer>,反之亦然,将 Publisher<DataBuffer> 转换为 OutputStream 或 NIO Channel。
  • 如果缓冲区是 PooledDataBuffer 的实例,则释放或保留 DataBuffer 的方法。
  • 跳过或从字节流中获取,直到特定的字节数。
编解码器

org.springframework.core.codec 包提供以下策略接口:

  • Encoder(编码器),用于将 Publisher<T> 编码到数据缓冲区流中。
  • Decoder (解码器),用于将 Publisher<DataBuffer> 解码为更高级别的对象流。

spring-core 模块提供了 byte[]、ByteBuffer、DataBuffer、Resource 和 String 编码器和解码器的实现。 spring-web 模块添加了 Jackson JSON、Jackson Smile、JAXB2、Protocol Buffers 和其他编码器和解码器。 请参阅 WebFlux 部分中的编解码器。

使用 DataBuffer(数据缓冲区)

使用数据缓冲区时,必须特别注意确保缓冲区被释放,因为它们可能被池化。 我们将使用编解码器来说明它是如何工作的,但这些概念更普遍适用。 让我们看看编解码器必须在内部做什么来管理数据缓冲区。

Decoder (解码器) 是在创建更高级别对象之前最后读取输入数据缓冲区的,因此它必须按如下方式释放它们:

  • 如果解码器简单地读取每个输入缓冲区并准备立即释放它,它可以通过 DataBufferUtils.release(dataBuffer) 这样做。
  • 如果解码器使用 Flux 或 Mono 运算符(例如 flatMap、reduce 和其他在内部预取和缓存数据项),或者使用诸如 filter、skip 和其他省略项的运算符,则 doOnDiscard(PooledDataBuffer.class, DataBufferUtils ::release) 必须添加到组合链中,以确保此类缓冲区在被丢弃之前被释放,也可能作为错误或取消信号的结果。
  • 如果解码器以任何其他方式保留一个或多个数据缓冲区,它必须确保它们在完全读取时被释放,或者在缓存数据缓冲区被读取和释放之前发生错误或取消信号的情况下。

请注意,DataBufferUtils#join 提供了一种将数据缓冲区流聚合到单个数据缓冲区中的安全有效的方法。 同样,skipUntilByteCount 和 takeUntilByteCount 是解码器使用的附加安全方法。

Encoder (编码器)分配其他人必须读取(和释放)的数据缓冲区。 所以编码器没有太多工作要做。 但是,如果在用数据填充缓冲区时发生序列化错误,编码器必须注意释放数据缓冲区。 例如:

DataBuffer buffer = factory.allocateBuffer();
boolean release = true;
try {
    // 序列化并填充缓冲区..
    release = false;
}
finally {
    if (release) {
        DataBufferUtils.release(buffer);
    }
}
return buffer;

编码器的消费者负责释放它接收到的数据缓冲区。 在 WebFlux 应用程序中,编码器的输出用于写入 HTTP 服务器响应或客户端 HTTP 请求,在这种情况下,释放数据缓冲区是代码写入服务器响应或客户端的责任 要求。

请注意,在 Netty 上运行时,有用于解决缓冲区泄漏的调试选项。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值