Java循环字节缓冲区,扩展java.nio.ByteBuffer

9
我看到的所有Java循环字节缓冲区实现都没有扩展java.nio.ByteBuffer,这对我来说是必要的,因为我需要与SocketChannel一起使用。有人知道是否有扩展ByteBuffer的开源实现吗?我尝试编写自己的实现,但当我意识到position和remaining函数是final时,我就陷入了困境,我将覆盖它们以调整head并防止缓冲区溢出异常。在通过套接字通道发送5000条消息时,每个消息都需要将内容复制到线性缓冲区的头部,这会增加约450毫秒或90微秒每个消息(每个消息包含10个数据包,因此每个数据包需要9微秒)。目前,我能想到的唯一方法是覆盖每个方法并重新编写所有内容。有任何想法吗?
4个回答

7

不必创建循环缓冲区,您可以将缓冲区的大小设置为大于一个消息的大小。假设最大消息大小为N字节。创建一个缓冲区,其大小为100 * N字节,仅在剩余少于N字节时 compact() ByteBuffer。这将减少复制量100倍。

另一个优化是每当没有剩余数据时就compact() ByteBuffer,因为这非常快速。


1
大部分时间我都会避免使用compact()。只有当缓冲区无法清空并且其容量的约1/8可用于进一步写入时,我才会使用compact()。这通常是在Buffer没有完全传输的情况下发生。在读取缓冲区后,我会使用以下代码:buffer().mark(). buffer.position(buffer.limit()).limit(buffer.capacity());。标记显示了读取可以开始的位置。此外,我的缓冲区容量指南与socket.writebuffer完全相同。 - bestsss
1
我同意,当我开始修改我的帧同步方法以允许在缓冲区中有多个帧时,这很可能会成为我的实现方式,但缺点是它使用了更多的内存。 我当前的实现是长度为N的缓冲区,并且在每个消息之后进行压缩。 我犹豫使用压缩的原因是ByteBuffer的文档中说它是可选的。 循环缓冲仍然是理想的解决方案,但似乎不太可行。 - LINEMAN78
compact()在它所做的事情上是最优化的,但这并不意味着它很便宜。另一方面,内存相对便宜。1MB的内存成本约为10美分,相当于以最低工资计算的一分钟时间。 ;) - Peter Lawrey
1
@PeterLawrey,除非您将代码放在1000万个单位中,否则您的10美分将变成100万美元。这可能值得花费一些时间。只是说... - evading
@refuser 我同意。并不是每个人都在处理那么多的单元,尤其是服务器,如果他们在处理的话,可能是移动设备,其中内存更为重要。 ;) - Peter Lawrey
除非你正在进行NIO并且每个客户端都有一个缓冲区,并且你正在扩展到大量的客户端(即不是移动设备,但仍需要更多的内存,并且是大数据系统的常见情况)......我讨厌ByteBuffer不是接口!!!那么我们就可以有一个循环缓冲区。 - Dean Hiller

7
您不能扩展java.nio.ByteBuffer,就这么简单。构造函数是包私有的。它不起作用,因为主要思想是将地址传递给某些C代码。此外,您不能覆盖任何内容,因为许多方法都是final的。ByteBuffers已经被设计用于速度,并且一些决策可能看起来很奇怪,但它们是可以接受的。
尝试使用java.nio.channels.GatheringByteChannel和java.nio.channels.ScatteringByteChannel,虽然它们在实现上相当依赖于(本地C),以使它们有用。

我并不完全同意该包应该是私有的,因为它涉及到本地代码。通过扩展 ByteBuffer 你无法做到外部无法实现的事情。至少应该公开抽象 Buffer 类。 - LINEMAN78

2

这个实现似乎不支持 ByteBuffer 的大多数实际方法,我不敢想象要实际使其有用需要多少工作量。我的应用程序需要按索引、位置、标记和限制获取数据。 - LINEMAN78
那并不是扩展java.nio.ByteBuffer,而是另一个同名的类。 - Leif Gruenwoldt

0

我会利用ByteBuffer.wrap()方法。快速查看Dean Hiller先生之前发布的思科实现,您可以将put()替换为getWriteBuffer(),它将返回一个ByteBuffer,该ByteBuffer包装了它可以写入的缓冲区部分。

同样的逻辑也适用于读取部分。

这样做的好处是不需要压缩,这取决于ByteBuffer中有多少字节,但会使解析逻辑复杂化:您可能会从包装底层循环缓冲区的ByteBuffer的最后一个区域获取消息的第一部分。要获取消息的第二部分,需要进行另一次读取,以获取包装在另一个ByteBuffer中的循环缓冲区开头的字节数组。


这在某种程度上已经可以通过切片方法实现,但仍然无法解决问题。如果剩下的数据量为零并且我还没有收到完整的消息,我仍然需要进行压缩。整个重点是不需要将数据复制到任何地方,所以必须执行两次读取的工作流意味着你正在将数据复制到某个地方。 - LINEMAN78
你没有说明你正在进行零拷贝解析... 在这种情况下,你可以通过在读写缓冲区上设置动态大小限制来使实现变得更加复杂。你需要一种特殊的逻辑来确定在消息大小不同的情况下,可以在接近末尾或开头写入多少字节。如果放在末尾,则将其放在开头,并且将读指针的动态大小限制更改为缓冲区开头的取模值。 - Pierre-Luc Bertrand

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接