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.
rtruszk
  • 3,902
  • 13
  • 36
  • 53
Seth
  • 5,596
  • 8
  • 42
  • 56

11 Answers11

44

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.

brianegge
  • 29,240
  • 13
  • 74
  • 99
  • 2
    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 Nov 22 '09 at 19:01
  • 2
    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. – Kevin Bourrillion Dec 15 '09 at 22:16
  • 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. – Val Mar 19 '15 at 09:37
  • 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. – bvdb Jul 30 '15 at 11:58
  • 1
    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. – Dexter Jul 27 '16 at 08:07
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.

Kevin Bourrillion
  • 40,336
  • 12
  • 74
  • 87
  • Neither `ByteArrayDataOutput` nor `ByteStreams` appears in the JDK, as of Java 8. What are you referring to? – user207421 Dec 14 '14 at 01:02
  • 2
    @EJP Those are classes from [Google Guava](https://code.google.com/p/guava-libraries/) as Kevin mentioned. – Jesper Jan 13 '15 at 16:05
  • 1
    @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 Apr 20 '18 at 03:06
  • @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. – agermano Jan 24 '20 at 23:53
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.

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
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.

Dave L.
  • 43,907
  • 11
  • 63
  • 62
Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • 6
    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." – Suppressingfire Nov 21 '09 at 16:55
  • 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. – Val Mar 19 '15 at 09:42
  • 1
    Link is dead. 404. – luckydonald Oct 13 '16 at 08:42
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
Qix - MONICA WAS MISTREATED
  • 14,451
  • 16
  • 82
  • 145
anonymous
  • 181
  • 3
  • 6
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.

antak
  • 19,481
  • 9
  • 72
  • 80
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); 
    }
Full Stack
  • 91
  • 1
  • 3
1

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.

BullyWiiPlaza
  • 17,329
  • 10
  • 113
  • 185
0

Netty ByteBuf is pretty good on that.

Clebert Suconic
  • 5,353
  • 2
  • 22
  • 35
-5

A Vector allows for continuous growth

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

  • 6
    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 Aug 18 '14 at 06:47
  • @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. – Scott Carey Feb 01 '17 at 18:57
-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.

Pokechu22
  • 4,984
  • 9
  • 37
  • 62