52

Has anyone has ever seen an implementation of java.nio.ByteBuffer that will grow dynamically if a putX() call overruns the capacity?

The reason I want to do it this way is twofold:

  1. I don't know how much space I need ahead of time.
  2. I'd rather not do a new ByteBuffer.allocate() then a bulk put() every time I run out of space.

11 Answers 11

46

In order for asynchronous I/O to work, you must have continuous memory. In C you can attempt to re-alloc an array, but in Java you must allocate new memory. You could write to a ByteArrayOutputStream, and then convert it to a ByteBuffer at the time you are ready to send it. The downside is you are copying memory, and one of the keys to efficient IO is reducing the number of times memory is copied.

Sign up to request clarification or add additional context in comments.

5 Comments

ByteArrayOutputStream is actually exactly what I want (I'm not actually doing any I/O, I just have some complex serialization to do). Thanks!
Seth, your statement of the question ("putX") implied that you would need methods such as putInt, putDouble, etc., which implied that ByteArrayOutputStream wouldn't be enough for you, hence my answer of ByteArrayDataOutput.
I won't even ask how fixed sized buf is exactly what you want after you ask for unlimited sized buf. We are living in the world where absurd is a norm.
The "downside of copying memory" is pretty much the same tradeoff of using an ArrayList vs an array. And just like with ArrayList you can instantiate it using an initial capacity (constructor ByteArrayOutputStream(int capacity)). e.g. if you expect about 800 bytes of data, then use something like new ByteArrayOutputStream(1024). This will prevent (or reduce the number of) resizes.
well we can avoid copying memory by extending ByteArrayOutputStream and adding a method which returns ByteBuffer.wrap(buf, 0, count), 'buf' being the internal byte array being used.
13

A ByteBuffer cannot really work this way, as its design concept is to be just a view of a specific array, which you may also have a direct reference to. It could not try to swap that array for a larger array without weirdness happening.

What you want to use is a DataOutput. The most convenient way is to use the (pre-release) Guava library:

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

But you could also create a DataOutputStream from a ByteArrayOutputStream manually, and just deal with the spurious IOExceptions by chaining them into AssertionErrors.

4 Comments

Neither ByteArrayDataOutput nor ByteStreams appears in the JDK, as of Java 8. What are you referring to?
@EJP Those are classes from Google Guava as Kevin mentioned.
@Jesper I don't find his statement at all clear. First he states that there is such a class and then he goes on to state that the 'most convenient way is to use the (pre-release) Guava library'. Unless such a class is provided by another library this is the only way to use it. It would be easier to avoid third-party libraries altogether and just use new DataOutputStream(new ByteArrayOutputStream()).
@user207421 He said to use a DataOutput, then gave two examples. One using Guava, and the other exactly what you said in your comment. ByteArrayDataOutput and DataOutputStream both implement that interface.
7

Another option is to use direct memory with a large buffer. This consumes virtual memory but only uses as much physical memory as you use (by page which is typically 4K)

So if you allocate a buffer of 1 MB, it comsumes 1 MB of virtual memory, but the only OS gives physical pages to the application which is actually uses.

The effect is you see your application using alot of virtual memory but a relatively small amount of resident memory.

Comments

6

Have a look at Mina IOBuffer https://mina.apache.org/mina-project/userguide/ch8-iobuffer/ch8-iobuffer.html which is a drop in replacement (it wraps the ByteBuffer)

However , I suggest you allocate more than you need and don't worry about it too much. If you allocate a buffer (esp a direct buffer) the OS gives it virtual memory but it only uses physical memory when its actually used. Virtual memory should be very cheap.

3 Comments

I love the warning on the page: "The main reason why MINA has its own wrapper on top of nio ByteBuffer is to have extensible buffers. This was a very bad decision."
Indeed, writing into memory implies that you ultimately want some limit and, moreover, not that large limit. It is yet curious to know whether unused part of ArrayBuffer stays free for other applications/uses or not.
Link is dead. 404.
5

It may be also worth to have a look at Netty's DynamicChannelBuffer. Things that I find handy are:

  • slice(int index, int length)
  • unsigned operations
  • separated writer and reader indexes

Comments

4

Indeed, auto-extending buffers are so much more intuitive to work with. If you can afford the performance luxury of reallocation, why wouldn't you!?

Netty's ByteBuf gives you exactly this. It's like they've taken java.nio's ByteBuffer and scraped away the edges, making it much easier to use.

Furthermore, it's on Maven in an independent netty-buffer package so you don't need to include the full Netty suite to use.

Comments

2

I'd suggest using an input stream to receive data from a file (with a sperate thread if you need non-blocking) then read bytes into a ByteArrayOutstream which gives you the ability to get it as a byte array. Heres a simple example without adding too many workarounds.

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

Comments

2

Another solution for this would be to allocate more than enough memory, fill the ByteBuffer and then only return the occupied byte array:

Initialize a big ByteBuffer:

ByteBuffer byteBuffer = ByteBuffer.allocate(1000);

After you're done putting things into it:

private static byte[] getOccupiedArray(ByteBuffer byteBuffer)
{
    int position = byteBuffer.position();
    return Arrays.copyOfRange(byteBuffer.array(), 0, position);
}

However, using a org.apache.commons.io.output.ByteArrayOutputStream from the start would probably be the best solution.

Comments

0

Netty ByteBuf is pretty good on that.

Comments

-5

A Vector allows for continuous growth

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

2 Comments

With this method, for each byte you need to create a Byte object which will have an 8 byte header, +1 byte to store the value inside the object. Now, all java objects take up a multiple 8 bytes, so that makes 16 bytes per object. Lets say we're using a 32 bit system so references to these objects in the vector are 4 bytes each. So to store each byte you need 20 bytes of memory. Thats just not very good.
@Numeron Byte is a flyweight, there are exactly 256 instances in the JVM unless you call 'new' instead of 'valueOf'. Auto-boxing does the latter. But the answer is poor either way, as the boxed indirection is going to be significantly larger and slower even if there are no allocated Byte objects.
-6

To serialize somethiing you will need object in entry. What you can do is put your object in collection of objects, and after that make loop to get iterator and put them in byte array. Then, call ByteBuffer.allocate(byte[].length). That is what I did and it worked for me.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.