# 8.数据缓存和解码器

Java NIO提供ByteBuffer，但许多库在顶部构建自己的字节缓冲区API，尤其是对于重用缓冲区和/或使用直接缓冲区有利于性能的网络操作。 例如，Netty具有ByteBuf层次结构，Undertow使用XNIO，Jetty使用pooled字节缓冲区并释放回调，依此类推。 spring-core模块提供了一组抽象来处理各种字节缓冲区API，如下所示：

* DataBufferFactory抽象创建数据缓冲区。
* DataBuffer表示可以合并的字节缓冲区。
* DataBufferUtils为数据缓冲区提供实用程序方法。
* Codecs将流数据缓冲流解码或编码为更高级别的对象。

## 8.1 DataBufferFactory

DataBufferFactory用以下两种方式之一创建数据缓冲区：

1. 分配一个新的数据缓冲区，可选择预先指定capacity（如果已知），即使DataBuffer的实现可以按需增长和缩小，这也更有效。
2. 包装现有的byte \[]或java.nio.ByteBuffer，它使用DataBuffer实现来修饰给定的数据，并且不涉及分配。

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

## 8.2 DataBuffer

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

* 使用独立位置进行读写，即不需要调用flip（）来在读写之间交替。
* 与java.lang.StringBuilder一样，按需扩展容量。
* 通过PooledDataBuffer汇集缓冲区和引用计数。
* 以java.nio.ByteBuffer，InputStream或OutputStream的形式查看缓冲区。
* 确定给定字节的索引或最后一个索引。

## 8.3 PooledDataBuffer

正如Javadoc for ByteBuffer中所解释的，字节缓冲区可以是直接缓冲区，也可以是非直接缓冲区。直接缓冲区可以驻留在Java堆之外，这样就无需复制本机I / O操作。这使得直接缓冲区对于通过套接字接收和发送数据特别有用，但是创建和释放它们也更加昂贵，这导致了pooling缓冲区的想法。

PooledDataBuffer是DataBuffer的扩展，它有助于引用计数，这对于字节缓冲池是必不可少的。它是如何工作的？当分配PooledDataBuffer时，引用计数为1.调用retain（）递增计数，而对release（）的调用则递减计数。只要计数大于0，就保证缓冲区不被释放。当计数减少到0时，可以释放pooled缓冲区，这实际上可能意味着缓冲区的保留内存返回到内存池。

请注意，不是直接对PooledDataBuffer进行操作，在大多数情况下，最好使用DataBufferUtils中的方法，只有当它是PooledDataBuffer的实例时才应用release或retain到DataBuffer。

## 8.4 DataBufferUtils

DataBufferUtils提供了许多用于操作数据缓冲区的实用方法：

* 将数据缓冲区流加入单个缓冲区中，可能只有零拷贝，例如：如果底层字节缓冲区API支持可以复合缓冲区。
* 将InputStream或NIO Channel转换为Flux，反之亦然，将Publisher转换为OutputStream或NIO Channel。
* 如果缓冲区是PooledDataBuffer的实例，则释放或保留DataBuffer的方法。
* 从字节流中跳过或取出，直到特定的字节数。

## 8.5 Codecs

org.springframework.core.codec 包提供了下面的策略接口：

* Encoder 用来将Publisher 编码为数据缓存的stream。
* Decoder 将Publisher解码为更高级别的对象流。

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

## 8.6 使用DataBuffer

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

Decoder是在创建更高级别对象之前，最后一个读取输入数据缓冲区的，因此必须按如下方式释放它们：

1. 如果Decoder只是读取每个输入缓冲区并准备立即释放它，它可以通过DataBufferUtils.release（dataBuffer）来实现。
2. 如果Decoder正在使用Flux或Mono运算符（如flatMap，reduce等）在内部预取和缓存数据项，或者正在使用filter，skip和其他省略项的运算符，那么doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release)必须被添加到组合链中以确保在丢弃之前（错误或取消信号）释放这些缓冲区，。
3. 如果Decoder以任何其他方式保持一个或多个数据缓冲区，则必须确保在完全读取时（或者在读取和释放高速缓存数据缓冲区之前发生错误或取消信号）释放它们。

请注意，DataBufferUtils#join提供了一种安全有效的方法，可将数据缓冲区流聚合到单个数据缓冲区中。同样，skipUntilByteCount和takeUntilByteCount是decoders使用的其他安全方法。

Encoder分配其他人必须读取（和释放）的数据缓冲区。所以Encoder没什么可做的。但是，如果在使用数据填充缓冲区时发生序列化错误，则Encoder必须注意释放数据缓冲区。例如：

```java
DataBuffer buffer = factory.allocateBuffer();
boolean release = true;
try {
    // serialize and populate buffer..
    release = false;
}
finally {
    if (release) {
        DataBufferUtils.release(buffer);
    }
}
return buffer;
```

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

请注意，在Netty上运行时，可以使用调试选项来排除缓冲区泄漏。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.flydean.com/spring-framework-documentation5/core-technologies/8data-buffers-and-codecs.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
