不断增长的ByteBuffer

52

有没有人见过一个可以动态增长的 java.nio.ByteBuffer 实现,当 putX() 调用超出容量时会增长?

我希望这样做有两个原因:

  1. 我不知道需要多少空间。
  2. 我不想每次空间不足时都要执行 new ByteBuffer.allocate() 然后进行批量 put()。
11个回答

44
为了实现异步I/O,必须拥有连续的内存。在C语言中,可以尝试重新分配数组,但在Java中必须分配新的内存。你可以向ByteArrayOutputStream写入数据,然后在准备发送时将其转换为ByteBuffer。缺点是会复制内存,而高效的I/O关键之一是减少内存复制的次数。

2
ByteArrayOutputStream实际上正是我想要的(我实际上并没有进行任何I/O,只是有一些复杂的序列化要做)。谢谢! - Seth
2
Seth,你的问题陈述(“putX”)暗示了你需要像putInt、putDouble等方法,这意味着ByteArrayOutputStream对你来说不够用,因此我回答使用ByteArrayDataOutput。 - Kevin Bourrillion
我甚至不会问你为什么在要求无限大小的缓冲区之后,固定大小的缓冲区正是你想要的。我们生活在一个荒谬成为常态的世界。 - Val
“复制内存的缺点”与使用ArrayList与数组的权衡差不多。就像使用ArrayList一样,您可以使用初始容量(构造函数ByteArrayOutputStream(int capacity))进行实例化。例如,如果您希望有大约800个字节的数据,则可以使用类似于new ByteArrayOutputStream(1024)的内容。这将防止(或��少)调整大小的次数。 - bvdb
1
我们可以通过扩展ByteArrayOutputStream并添加一个方法来避免复制内存,该方法返回ByteBuffer.wrap(buf,0,count),其中'buf'是正在使用的内部字节数组。 - Dexter

13

ByteBuffer不能以这种方式工作,因为它的设计理念是仅作为特定数组的视图,您也可以直接引用该数组。如果尝试将该数组替换为更大的数组,则会发生奇怪的问题。

您需要使用的是。最方便的方法是使用(预发行版)Guava库:

ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.write(someBytes);
out.writeInt(someInt);
// ...
return out.toByteArray();

你还可以手动从ByteArrayOutputStream创建DataOutputStream,然后通过将它们链接为AssertionErrors处理假的IOExceptions。


截至Java 8,JDK中都没有ByteArrayDataOutputByteStreams。你指的是什么? - user207421
2
@EJP,正如Kevin所提到的那样,这些是来自Google Guava的类。 - Jesper
1
@Jesper,我一点也不觉得他的陈述很清晰。首先他说有这样一个类,然后他又说“最方便的方法是使用(预发布的)Guava库”。除非另一个库提供了这样一个类,否则这是唯一的使用方式。最好完全避免使用第三方库,而直接使用new DataOutputStream(new ByteArrayOutputStream()) - user207421
@user207421 他建议使用DataOutput,然后给出了两个例子。一个使用Guava,另一个与您在评论中提到的完全相同。ByteArrayDataOutputDataOutputStream都实现了该接口。 - agermano

7

另一种选择是使用具有大缓冲区的直接内存。这会消耗虚拟内存,但只使用您实际使用的物理内存(按页面计算,通常为4K)

因此,如果您分配了1 MB的缓冲区,则会消耗1 MB的虚拟内存,但操作系统只会将物理页面提供给应用程序实际使用的部分。

效果是您会看到应用程序使用大量虚拟内存,但占用的驻留内存相对较小。


6
请查看 Mina IOBuffer,它是 ByteBuffer 的替代品,链接为 https://mina.apache.org/mina-project/userguide/ch8-iobuffer/ch8-iobuffer.html
然而,我建议您多分配一些内存,不用过于担心。如果您分配一个缓冲区(尤其是直接缓冲区),操作系统会为其提供虚拟内存,但仅在实际使用时才使用物理内存。虚拟内存应该非常便宜。

6
我喜欢页面上的警告:“MINA在nio ByteBuffer之上拥有自己的包装器的主要原因是为了拥有可扩展的缓冲区。这是一个非常糟糕的决定。” - Suppressingfire
事实上,将数据写入内存意味着您最终想要一些限制,而且不是那么大的限制。有趣的是,未使用的ArrayBuffer部分是否保留空闲以供其他应用程序/用途使用。 - Val
1
链接失效了。404。 - luckydonald

5

也许值得看一下Netty的DynamicChannelBuffer。我发现以下内容很方便:

  • slice(int index, int length)
  • 无符号操作
  • 分离的写入者和读取者指数

4

的确,自动扩展缓冲区更符合直觉。如果您能够承担性能上的损失,为什么不这么做呢?

ByteBuf 是 Netty 提供的一种缓冲区实现,它就像是对 java.nioByteBuffer 进行了裁剪,让它更易于使用。

此外,独立的 netty-buffer 包也已经在 Maven 上发布,因此您不需要引入完整的 Netty 套件即可使用。


2

我建议使用输入流从文件中接收数据(如果需要非阻塞,则使用单独的线程),然后将字节读入ByteArrayOutputStream中,这样可以将其作为字节数组获取。下面是一个简单的示例,没有添加太多的解决方法。

    try (InputStream inputStream = Files.newInputStream(
            Paths.get("filepath"), StandardOpenOption.READ)){

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        int byteRead = 0;

        while(byteRead != -1){
            byteRead = inputStream.read();
            baos.write(byteRead);
        }
        ByteBuffer byteBuffer = ByteBuffer.allocate(baos.size())
        byteBuffer.put(baos.toByteArray());

        //. . . . use the buffer however you want

    }catch(InvalidPathException pathException){
        System.out.println("Path exception: " + pathException);
    }
    catch (IOException exception){
        System.out.println("I/O exception: " + exception); 
    }

1
另一种解决方案是分配超过所需的内存,填充 ByteBuffer ,然后仅返回占用的字节数组:
初始化一个大的 ByteBuffer :
ByteBuffer byteBuffer = ByteBuffer.allocate(1000);

完成后,将东西放入其中:
private static byte[] getOccupiedArray(ByteBuffer byteBuffer)
{
    int position = byteBuffer.position();
    return Arrays.copyOfRange(byteBuffer.array(), 0, position);
}

然而,从一开始使用org.apache.commons.io.output.ByteArrayOutputStream可能是最好的解决方案。

0

Netty的ByteBuf在这方面非常好。


-5

Vector 允许不断增长

Vector<Byte> bFOO = new Vector<Byte>(); bFOO.add((byte) 0x00);


6
使用这种方法,对于每个字节,您需要创建一个Byte对象,该对象将具有8字节的头部,+1字节用于存储对象内部的值。现在,所有的Java对象都占用多个8字节,因此每个对象占用16字节。假设我们正在使用32位系统,因此在向量中引用这些对象的引用每个占4个字节。因此,为了存储每个字节,您需要20个字节的内存。这并不是很好。 - Numeron
@Numeron Byte是一个轻量级对象,JVM中最多只有256个实例,除非你使用'new'而不是'valueOf'。自动装箱会执行后者。但无论哪种方式,答案都很糟糕,因为即使没有分配Byte对象,包装的间接性也会显着增加大小和速度变慢。 - Scott Carey

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