5.2 ByteBuf-字节数据容器

每当您需要与远程对等方(例如数据库)进行交互时,都需要以字节为单位进行通信。由于上述原因和其他原因,需要一种高效,便捷且易于使用的数据结构,Netty的ByteBuf实现满足了这些要求以及更多要求,使其成为理想的数据容器,已针对保留和与字节交互进行了优化。

ByteBuf是一个数据容器,使您可以高效地从中添加/获取字节。为了简化操作,它使用了两个索引:一种用于阅读,一种用于写作。这使您可以按顺序从其中读取数据,然后“跳回”以再次读取它。您需要做的只是调整阅读器索引,然后再次开始读取操作。

5.2.1 原理

将某些内容写入ByteBuf后,其writerIndex将增加写入的字节数。开始读取字节后,其readerIndex会增加。您可以读取字节,直到writerIndex和readerIndex处于相同位置。然后,ByteBuf变得不可读,因此下一个读取请求将触发IndexOutOfBoundsException,类似于尝试读取超出数组容量的内容时看到的内容。

调用任何以“ read”或“ write”开头的buffer的方法会自动使读索引或写索引前进。还有一些相对的操作来设置和获取字节。这些不会移动索引,但会根据给定的相对索引进行操作。

ByteBuf可能具有最大容量,可以为其可容纳的最大数据设置上限,尝试将写入器索引移到该容量以上将导致异常。默认限制为Integer.MAX_VALUE。

Figure 5.1 A 16-byte ByteBuf, initialized with the read and write indices set to 0

如图5.1所示,ByteBuf与字节数组相似,最显着的区别是可用于控制对缓冲区数据访问的读写索引的增加。

您将在后面的部分中详细了解可以对ByteBuf执行的操作。现在,请记住这一点,让我们回顾一下您最有可能使用的不同类型的ByteBuf。

5.2.2 不同类型的ByteBuf

有三种不同类型的ByteBuf您会遇到,使用Netty(有更多,但这些是内部使用)。 你可能最终实现了你自己的,但这超出了范围。 来看看你最可能感兴趣的提供的类型。

Heap Buffers

最常用的类型是ByteBuf,它将其数据存储在JVM的堆空间中。这是通过将其存储在支持数组中来完成的。当您不使用池时,此类型可以快速分配和取消分配。它还提供了一种直接访问支持数组的方法,这可以使与遗留代码的交互更加容易。

从非堆ByteBuf访问数组将导致UnsupportedOperationException。因此,始终如一如一,最好检查ByteBuf是否由具有hasArray()的数组支持,如清单5.1所示。如果您以前使用过JDK的ByteBuffer,则可能会熟悉此模式。

Direct Buffers

另一个ByteBuf实现是直接实现。 Direct表示它直接分配内存,该内存在heap之外。您不会在堆空间中看到其内存使用情况。在计算应用程序将使用的最大内存量以及如何限制它时,必须考虑到这一点,因为最大堆大小将不足。当需要通过套接字传输数据时,另一端的直接缓冲区是最佳的。实际上,如果您使用非直接缓冲区,则JVM会先在内部将缓冲区的副本复制到直接缓冲区,然后再通过套接字发送。

直接缓冲区的缺点是,与堆缓冲区相比,直接缓冲区分配和取消分配的开销更大。这是Netty支持池化的原因之一,这使得此问题消失了。另一个可能的不利方面是您不再能够通过支持数组访问数据,因此,如果数据需要与此操作的遗留代码一起使用,则需要制作该数据的副本。

以下清单显示了即使没有直接访问支持数组的能力,如何获取数组中的数据并调用方法。

如您所见,这需要更多的工作,并且涉及复制操作。如果您希望访问数据并需要将其存储在数组中,则最好使用堆缓冲区。

Composite Buffers

您可能会遇到的最后一个ByteBuf实现是CompositeByteBuf。这确实如其名称所说;它允许您组成不同的ByteBuf实例并提供它们的视图。好消息是您还可以即时添加和删除它们,因此它有点像列表。如果您曾经使用过JDK的ByteBuffer,那么您很可能会错过该功能。由于CompositeByteBuf仅是其他视图,因此hasArray()方法将返回false,因为它可能包含多个直接类型和非直接类型的ByteBuf实例。

例如,一条消息可以由两部分组成:标头和正文。在模块化应用程序中,这两个部分可以由不同的模块生产,并在稍后发送消息时组装。另外,您可以一直使用相同的正文,而只需更改标题即可。因此,在这里不必每次都分配一个新的缓冲区是有意义的。

这将非常适合CompositeByteBuf,因为不需要内存副本,并且可以使用与非复合缓冲区相同的API。

图5.2显示了如何使用CompositeByteBuf组成标题和正文。

Figure 5.2 CompositeBuf that holds a header and body.

相反,如果您使用了JDK的ByteBuffer,那将是不可能的。组成两个ByteBuffer的唯一方法是创建一个保存它们的数组,或者创建一个新的ByteBuffer并将它们的内容复制到新创建的ByteBuffer中。以下清单显示了这些内容。

图5.3中所示的两种方法都有缺点:如果要支持两者,则必须处理数组不能使API保持简单。当然,与此复制相关的性能成本也很高。这根本不是最佳选择。

但是,让我们看一下CompositeByteBuf的运行情况。以下清单提供了概述。

那里有更多的方法,但我想您会明白的。Netty API已明确记录在案,因此当您使用其他未显示的方法时,通过参考API文档将很容易理解它们的作用。

另外,由于CompositeBytebuf的性质,您将无法访问支持数组。它看起来与在本地缓冲区和以下清单中看到的相似。

由于CompositeByteBuf是ByteBuf的子类型,因此您可以照常对缓冲区进行操作,但有可能进行一些额外的操作。

您也可能会欢迎Netty在使用CompositeByteBuf时尽可能地优化套接字上的读取和写入操作。这意味着在对套接字进行读写时,使用收集和分散不会导致性能损失,也不会遭受JDK实现中的内存泄漏问题。 所有这些都是在Netty本身的核心中完成的,因此您不必太担心它,但是知道某些优化是在后台进行的也无济于事。

使用ByteBuffer时不存在诸如CompositeByteBuf之类的类。这仅仅是使缓冲区API的功能比JDK作为java.nio软件包的一部分提供的缓冲区API更加丰富的功能之一。

最后更新于

这有帮助吗?