【问题标题】:Growing ByteBuffer增长的字节缓冲区
【发布时间】:2010-12-18 23:32:08
【问题描述】:

有没有人见过 java.nio.ByteBuffer 的实现,如果 putX() 调用超出容量,它会动态增长?

我想这样做的原因有两个:

  1. 我不知道我提前需要多少空间。
  2. 我宁愿在每次空间用完时都执行新的 ByteBuffer.allocate(),然后执行批量 put()。

【问题讨论】:

    标签: java bytebuffer


    【解决方案1】:

    为了使异步 I/O 工作,您必须拥有连续的内存。在 C 中,您可以尝试重新分配数组,但在 Java 中,您必须分配新内存。您可以写信给ByteArrayOutputStream,然后在准备发送时将其转换为ByteBuffer。缺点是你在复制内存,而高效 IO 的关键之一是减少内存复制的次数。

    【讨论】:

    • ByteArrayOutputStream 实际上正是我想要的(我实际上并没有做任何 I/O,我只是有一些复杂的序列化要做)。谢谢!
    • Seth,您对问题的陈述(“putX”)暗示您需要 putInt、putDouble 等方法,这意味着 ByteArrayOutputStream 对您来说还不够,因此我的回答ByteArrayDataOutput.
    • 在你要求无限大小的 buf 之后,我什至不会问你想要的固定大小的 buf 到底是什么。我们生活在一个以荒谬为常态的世界。
    • “复制内存的缺点”几乎与使用 ArrayList 与数组的权衡相同。就像ArrayList 一样,您可以使用初始容量(构造函数ByteArrayOutputStream(int capacity))对其进行实例化。例如如果您期望大约 800 字节的数据,请使用 new ByteArrayOutputStream(1024) 之类的内容。这将防止(或减少)调整大小。
    • 我们可以通过扩展 ByteArrayOutputStream 并添加一个返回 ByteBuffer.wrap(buf, 0, count) 的方法来避免复制内存,'buf' 是正在使用的内部字节数组。
    【解决方案2】:

    看看 Mina IOBuffer https://mina.apache.org/mina-project/userguide/ch8-iobuffer/ch8-iobuffer.html 这是一个替代品(它包装了 ByteBuffer)

    不过,我建议你分配的比你需要的多,不要太担心。如果您分配一个缓冲区(尤其是直接缓冲区),操作系统会为其提供虚拟内存,但它仅在实际使用时才使用物理内存。虚拟内存应该很便宜。

    【讨论】:

    • 我喜欢页面上的警告:“MINA 在 nio ByteBuffer 之上拥有自己的包装器的主要原因是拥有可扩展的缓冲区。这是一个非常糟糕的决定。”
    • 确实,写入内存意味着您最终需要一些限制,而且,不是那么大的限制。目前还很想知道 ArrayBuffer 的未使用部分是否可以免费用于其他应用程序/用途。
    • 链接已失效。 404.跨度>
    【解决方案3】:

    ByteBuffer 不能真正以这种方式工作,因为它的设计理念是只是一个特定数组的 view,您也可以直接引用它。它无法尝试将该数组换成更大的数组而不会发生奇怪的事情。

    您要使用的是DataOutput。最方便的方法是使用(预发布)Guava 库:

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

    但您也可以手动从 ByteArrayOutputStream 创建 DataOutputStream,并通过将虚假 IOException 链接到 AssertionErrors 中来处理它们。

    【讨论】:

    • 从 Java 8 开始,JDK 中既没有出现 ByteArrayDataOutput 也没有出现 ByteStreams。你指的是什么?
    • @EJP 这些是凯文提到的Google Guava 的课程。
    • @Jesper 我觉得他的声明一点都不清楚。首先他说这样一个类,然后他接着说“最方便的方法是使用(预发布的)Guava 库”。除非这样的类是由另一个库提供的,否则这是使用它的唯一方式。完全避免使用第三方库并使用new DataOutputStream(new ByteArrayOutputStream()) 会更容易。
    • @user207421 他说要用DataOutput,然后举了两个例子。一个使用番石榴,另一个正是您在评论中所说的。 ByteArrayDataOutputDataOutputStream 都实现了该接口。
    【解决方案4】:

    看看 Netty 的 DynamicChannelBuffer 可能也值得一看。我觉得方便的是:

    • slice(int index, int length)
    • 无符号操作
    • 独立的写入器和读取器索引

    【讨论】:

      【解决方案5】:

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

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

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

      【讨论】:

        【解决方案6】:

        向量允许持续增长

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

        【讨论】:

        • 使用此方法,您需要为每个字节创建一个 Byte 对象,该对象将有一个 8 字节的标头,+1 字节来存储对象内部的值。现在,所有 java 对象占用多个 8 个字节,因此每个对象占用 16 个字节。假设我们使用的是 32 位系统,因此对向量中这些对象的引用每个都是 4 个字节。因此,要存储每个字节,您需要 20 个字节的内存。那不是很好。
        • @Numeron Byte 是一个享元,除非您调用 'new' 而不是 'valueOf',否则 JVM 中正好有 256 个实例。自动装箱是后者。但无论哪种方式,答案都很糟糕,因为即使没有分配的 Byte 对象,盒装间接也会变得更大且更慢。
        【解决方案7】:

        要序列化某些东西,您需要在条目中输入对象。您可以做的是将您的对象放入对象集合中,然后进行循环以获取迭代器并将它们放入字节数组中。然后,拨打ByteBuffer.allocate(byte[].length)。这就是我所做的,它对我有用。

        【讨论】:

          【解决方案8】:

          确实,使用自动扩展缓冲区更加直观。如果你能负担得起重新分配的性能奢侈,你为什么不呢!?

          Netty 的ByteBuf 正是为您提供了这一点。就好像他们把java.nioByteBuffer 刮掉了边缘,让它更容易使用。

          此外,它是 on Maven in an independent netty-buffer 包,因此您无需包含完整的 Netty 套件即可使用。

          【讨论】:

            【解决方案9】:

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

                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); 
                }
            

            【讨论】:

              【解决方案10】:

              另一种解决方案是分配足够多的内存,填充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 可能是最好的解决方案。

              【讨论】:

                【解决方案11】:

                Netty ByteBuf 在这方面做得很好。

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2017-06-14
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多