背景:我正在编写一些通过网络序列化和反序列化二进制协议的代码,并且我正在编写 C#、Swift 和 Java 客户端代码。
我们从网络上获取一个数据包(字节数组),将其反序列化为结构树,然后遍历树查找内容。
在 C# 中,我使用了Span<T>结构体,它为您提供了现有内存缓冲区的“视图”。同样在 Swift 中,我使用了也可以充当“视图”的Data结构。在 C++ 中我可能会使用一些类似的概念。
本质上是这样的:
原始缓冲区:R_T1aaa_T2bbb_T3ccc.
解析为Root -> [Tag1[aaa],Tag2[bbb], Tag3[ccc]]
在上面的模型中,Root、Tag1、Tag2、Tag3 对象是在堆栈上分配的结构体。标记内容(aaa、bbb、ccc)只是指向底层原始缓冲区的指针,我们根本没有为它们分配或复制任何堆内存。
在 Java 中,我首先创建一个这样的类(@MarkRotteveel 善意地指出我可以使用 ByteBuffer 而不是创建自己的包装类):
class ByteArrayView {
@NonNull final byte[] owner; // shared
final int offset;
final int length;
}
Run Code Online (Sandbox Code Playgroud)
我打算继续使用与 C#、Swift 中使用的相同模式,但是我想到了——我使用数组视图来避免堆分配,但是Java 中的所有内容都是堆分配。我分配对象,而不是byte[]在该位置复制一些值ByteArrayView,但它仍然是堆分配。
我知道,如果我有大的“切片”,那么像我的ByteArrayView或这样的东西java.nio.ByteBuffer会减少我的程序分配的内存总量,但在大多数情况下,我的标签很小 - 例如 4 字节、12 字节、32 字节。
byte[]如果小对象仍然在堆上,那么用包装对象交换小对象是否有任何有意义的结果?它真的会让事情变得更糟吗,因为在我们到达我们想要读取的实际字节之前有一个额外的间接寻址?额外的代码复杂性值得吗?
经验丰富的 Java 开发人员会在这里做什么?
| 归档时间: |
|
| 查看次数: |
1086 次 |
| 最近记录: |