5.3 ByteBuf的字节操作
ByteBuf提供了许多允许修改内容或仅读取内容的操作。您将很快了解到它非常类似于JDK的ByteBuffer,它提供了更好的用户体验和性能。
5.3.1 随机访问索引
像普通的原始字节数组一样,ByteBuf使用基于零的索引。这意味着第一个字节的索引始终为0,最后一个字节的索引始终为-1。例如,我可以迭代缓冲区的所有字节(请参见下面的清单),而不管其内部实现如何。
/**
* Listing 5.6 Access data
*/
public static void byteBufRelativeAccess() {
ByteBuf buffer = BYTE_BUF_FROM_SOMEWHERE; //get reference form somewhere
for (int i = 0; i < buffer.capacity(); i++) {
byte b = buffer.getByte(i);
System.out.println((char) b);
}
}
请注意,通过索引进行的访问不会提升readerIndex或writerIndex。如果需要,您可以通过调用readerIndex(index)和writerIndex(index)来手动推进。
5.3.2 顺序访问索引
ByteBuf提供两个指针变量,分别支持顺序的读写操作readerIndex用于读操作,而writerIndex用于写操作。同样,这与JDK的ByteBuffer只有一个不同,因此您需要flip()在读和写模式之间切换。图5.3显示了如何通过两个指针将缓冲区划分为三个区域。

5.3.3 可丢弃的字节 Discardable bytes
可丢弃字节段包含已由读取操作读取的字节,因此可以将其丢弃。最初,此段的大小为0,但随着执行读取操作,其大小增加到writerIndex。这仅包括读取操作;获取操作不会移动readerIndex。可以通过调用discardReadBytes()回收未使用的空间来丢弃读取的字节。

正如您所看到的,可丢弃的字节段包含一些可重用的空间。 这可以通过调用discardReadBytes()来实现。
图5.5显示了discardReadBytes()的调用将如何影响分段。

注意,在调用discardReadBytes()之后,无法保证可写字节的内容。在大多数情况下,可写字节不会移动,甚至可能会根据基础缓冲区的实现而用完全不同的数据填充。
另外,您可能会经常调用discardReadBytes()来再次为ByteBuf提供更多可写空间。请注意,discardReadBytes()最有可能涉及内存副本,因为它需要将可读字节(内容)移至ByteBuf的开头。这样的操作不是免费的,并且可能会影响性能,因此请仅在需要时使用它并从中受益。因此,例如,如果您需要尽快释放内存。
5.3.4 可读字节
该段是实际数据的存储位置。名称以“ read”或“ skip”开头的任何操作都将获取或跳过当前readerIndex处的数据,并将其增加读取字节数。如果读取操作的参数也是ByteBuf并且未指定目标索引,则指定的目标缓冲区的writerIndex会一起增加。
如果没有足够的内容,则会引发IndexOutOfBoundException。新分配,包装或复制的缓冲区的readerIndex的默认值为0。
以下清单显示了如何读取所有可读数据。
5.3.5 可写字节
该段是未定义的空间,需要填充。名称以write开头的任何操作都将在当前writerIndex处写入数据,并将其增加写入字节数。如果写操作的参数也是ByteBuf并且未指定源索引,则指定缓冲区的readerIndex会一起增加。
如果剩余的可写字节不足,则会引发IndexOutOfBoundException。新分配的缓冲区的writerIndex的默认值为0。
下面的清单显示了一个示例,该示例用随机的int值填充缓冲区,直到空间用尽。
This segment is an undefined space which needs to be filled. Any operation whose name starts with write will write the data at the current writerIndex and increase it by the number of written bytes. If the argument of the write operation is also a ByteBuf and no source index is specified, the specified buffer's readerIndex is increased together. If there's not enough writable bytes left, IndexOutOfBoundException is raised. The default value of newly allocated buffer's writerIndex is 0. The following listing shows an example that fills the buffer with random int values until it runs out of space.
5.3.6 clear
您可以通过调用clear()将readerIndex和writerIndex都设置为0。它不会清除缓冲区的内容(例如,填充0),但会清除两个指针。请注意,此操作的语义与JDK的ByteBuffer.clear()不同。
让我们看看它的功能。 图5.6显示了具有三个不同段的ByteBuf。

和以前一样,它包含三个部分。 一旦调用了明确的(),您会看到此更改。 图5.7显示了使用clear()后的ByteBuf。

与discardReadBytes()相比,clear()操作便宜,因为它可以调整指针并且不需要复制任何内存。
5.3.7 搜索操作
各种indexOf()方法可帮助您找到满足特定条件的值的索引。可以使用ByteBufProcessor实现以及简单的静态单字节搜索来完成复杂的动态顺序搜索。
如果您要解码可变长度的数据(例如以NULL结尾的字符串),您会发现bytesBefore(byte)方法很有用。假设您编写了一个必须与Flash套接字集成的应用程序,该应用程序使用NULL终止的内容。使用bytesBefore()方法,您可以轻松地从Flash消耗数据,而无需手动准备数据中的每个字节来检查NULL字节。如果没有ByteBufProcessor(已废弃),您将需要自己完成所有这些工作。另外,由于在处理过程中需要较少的边界检查,因此效率更高。
5.3.8标记并重置
如前所述,每个缓冲区中都有两个标记索引。一种用于存储readerIndex,另一种用于存储writerIndex。您始终可以通过调用reset方法来重新定位两个索引之一。除了没有读取限制外,它的工作方式与InputStream中的mark和reset方法类似。
另外,您可以通过调用readerIndex(int)或writerIndex(int)将它们移动到精确的索引。请注意,尝试将readerIndex或writerIndex设置为无效位置会导致IndexOutOfBoundException。
5.3.9 派生缓冲区 (Derived buffers)
要创建现有缓冲区的视图,请调用plicate(),slice(),slice(int,int),readOnly()或order(ByteOrder)。派生的缓冲区具有独立的readerIndex,writerIndex和Marker索引,但是它与NIO ByteBuffer一样共享其他内部数据表示。因为它共享内部数据表示,所以创建起来便宜,例如,如果您在操作中需要一片ByteBuf,则它是首选方法。
如果需要现有缓冲区的新副本,请改用copy()或copy(int,int)方法。以下清单显示了如何使用ByteBuf的一部分。
现在让我们看一下如何创建ByteBuf的副本以及它与切片之间的区别。以下清单显示了如何使用ByteBuf的副本。
API相同,但是修改如何影响派生的ByteBuf。
尽可能使用切片,并仅在需要时使用副本。创建ByteBuf的副本会更加昂贵,因为它需要进行内存副本。
5.3.10 Read/write operations
读/写操作有两种主要类型:
基于索引的get/set操作,在给定索引上设置或获取字节。 (以get/set开头)
读/写操作,从当前索引中读取字节并增加它们,或者写入当前索引并增加它。(以read/write开头)
让先回顾一下相关的操作;我只提到目前最受欢迎的操作。 有关完整的概述,请参阅API文档。
5.3.11 Other useful operations
最后更新于
这有帮助吗?