Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Missing some absolute methods on ByteBuffer

Tags:

java

Perhaps I'm going about this the wrong way but I'm missing some absolute put methods on ByteBuffer.

If you look at ByteBuffer you'll see that most of the put methods have both an absolute and a relative variant.

Except for:

  • Writing parts of a byte array into the ByteBuffer.
  • Writing a ByteBuffer into the ByteBuffer.

..and I need exactly those.

To be clear ByteBuffer has methods:

 put(byte[] src, int offset, int length)
 put(ByteBuffer src)

but is lacking:

 put(int index, byte[] src, int offset, int length)
 put(int index, ByteBuffer src)

I have reasons why I do not want to move the buffer's position pointer, hence I only want to use absolute put methods.

Any idea why those methods have been left out?

I can of course mimic the missing methods without moving the buffer's position pointer but that will involve looping over the source bytes. The Javadoc clearly states that theses methods are (potentially) much more efficient than moving than looping and moving the bytes one-by-one. I believe the Javadoc because my tests indicate the same. I need to squeeze out as much speed I can from my implementation so therefore I'm of course inclined to take advantage of whatever bulk methods I can get my hands on .... if only they existed.

Incedentially ByteBuffer is also missing an absolute get method for partial byte array move. But I don't actually need such a method currently. But again strange it doesn't exist.

like image 262
peterh Avatar asked Mar 14 '13 12:03

peterh


People also ask

What is limit on ByteBuffer?

The limit() method of java. nio. ByteBuffer Class is used to set this buffer's limit. If the position is larger than the new limit then it is set to the new limit. If the mark is defined and larger than the new limit then it is discarded.

What is the difference between ByteBuffer and byte array?

There are several differences between a byte array and ByteBuffer class in Java, but the most important of them is that bytes from byte array always reside in Java heap space, but bytes in a ByteBuffer may potentially reside outside of the Java heap in case of direct byte buffer and memory mapped files.

What does ByteBuffer flip do?

ByteBuffer flip() methods in Java with Examples After a sequence of channel-read or put operations, invoke this method to prepare for a sequence of channel-write or relative get operations. This method is often used in conjunction with the compact method when transferring data from one place to another.


3 Answers

One way to get the methods you need is to have a second ByteBuffer sharing the same memory, so you can change its position without changing the position of the original.

Unfortunately, the slice method does not take a position parameter either; instead it uses the current position of the original buffer. So you cannot do:

dstBuffer.slice(100).put(srcBuffer);

Here are some ideas, in no particular order except it's the order I thought of them:

  • If it fits with the way you're using the buffer, you can prepare a copy of the buffer with slice() and keep that around for when you need to put data at a position independent of the original's position.

  • If the position you want to absolute put at is always greater or equal to the original buffer's position pointer, you can do:

    dstBuffer.slice().position(desiredPosition - dstBuffer.position()).put(srcBuffer);
    

That won't work to put at an earlier position, unfortunately, since the position on the slice is not allowed to be negative. EDIT: Never mind, I forgot about the duplicate method. See @BorisBrodski's great answer.

  • If you are not using direct byte buffers, System.arraycopy is easy and fast:

    System.arraycopy(
        srcBuffer.array(), srcBuffer.arrayOffset() + srcBuffer.position(),
        dstBuffer.array(), dstBuffer.arrayOffset() + desiredPosition,
        srcBuffer.remaining()
    );
    
  • If concurrent access is not necessary, then you can temporarily change the buffer's position when you need to do an absolute put and put it back afterwards. If you need concurrent access but the thread contention is low, you can synchronize all access to the buffer (maybe obvious, but included for completeness):

    synchronize (lock) {
        int originalPosition = dstBuffer.position();
        dstBuffer.position(desiredPosition);
        dstBuffer.put(srcBuffer);
        dstBuffer.position(originalPosition);
    }
    
  • If none of the other ideas work for you, you can hack the buffer. This is messy, but here's an example:

    private static final sun.misc.Unsafe UNSAFE;
    static {
        Object result = null;
        try {
            Class<?> klass = Class.forName("sun.misc.Unsafe");
            for (Field field : klass.getDeclaredFields()) {
                if (field.getType() == klass &&
                    (field.getModifiers() & (Modifier.FINAL | Modifier.STATIC)) ==
                        (Modifier.FINAL | Modifier.STATIC)) {
                    field.setAccessible(true);
                    result = field.get(null);
                    break;
                }
            }
        } catch (Throwable t) {}
        UNSAFE = result == null ? null : (sun.misc.Unsafe)result;
    }
    
    private static final Field ADDRESS_FIELD;
    static {
        Field f;
        try {
            f = Buffer.class.getDeclaredField("address");
            f.setAccessible(true);
        } catch (NoSuchFieldException | SecurityException e) {
            f = null;
        }
        ADDRESS_FIELD = f;
    }
    
    
    public static void absolutePut(ByteBuffer dstBuffer, int dstPosition, ByteBuffer srcBuffer) {
        if (!srcBuffer.isDirect()) {
            absolutePut(dstBuffer, dstPosition,
                srcBuffer.array(), srcBuffer.arrayOffset() + srcBuffer.position(),
                srcBuffer.remaining());
            return;
        }
    
        if (UNSAFE != null && ADDRESS_FIELD != null && dstBuffer.isDirect()) {
            try {
                long dstAddress = (long)ADDRESS_FIELD.get(dstBuffer) + dstPosition;
                long srcAddress = (long)ADDRESS_FIELD.get(srcBuffer) + srcBuffer.position();
                UNSAFE.copyMemory(srcAddress, dstAddress, srcBuffer.remaining());
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        } else {
            // fallback to basic loop
            for (int i = srcBuffer.position(); i < srcBuffer.limit(); i++) {
                dstBuffer.put(dstPosition + i, srcBuffer.get(i));
            }
        }
    }
    
    public static void absolutePut(ByteBuffer dstBuffer, int dstPosition, byte[] src, int srcOffset, int length) {
        if (UNSAFE != null && ADDRESS_FIELD != null && dstBuffer.isDirect()) {
            try {
                long dstAddress = (long)ADDRESS_FIELD.get(dstBuffer) + dstPosition;
                UNSAFE.copyMemory(
                    src, UNSAFE.arrayBaseOffset(byte[].class) + srcOffset,
                    null, dstAddress,
                    length);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        } else {
            // fallback to System.arraycopy
            System.arraycopy(
                src, srcOffset,
                dstBuffer.array(), dstBuffer.arrayOffset() + dstPosition,
                length);
        }
    }
    

I've given this code some minimal testing with mixes of direct and non-direct buffers and it seems okay. If the reflection techniques fail (e.g., because you are within an applet security sandbox or the Java implementation is not compatible) it can fall back to plainer methods.

like image 59
Boann Avatar answered Oct 14 '22 14:10

Boann


In my opinion, those methods where left out, since no other such methods exists. There are bunch of absolute methods taking primitives including "byte", but no absolute methods taking array of primitives.

The solution could be:

((ByteBuffer)buffer.duplicate().position(index)).put(src, offset, length);
((ByteBuffer)buffer.duplicate().position(index)).put(otherByteBuffer);
like image 40
Boris Brodski Avatar answered Oct 14 '22 12:10

Boris Brodski


static void put(ByteBuffer buffer, int index, ByteBuffer src)
{
    int p0 = buffer.position();
    buffer.position(index);
    buffer.put(src);
    buffer.position(p0);
}
like image 22
ZhongYu Avatar answered Oct 14 '22 13:10

ZhongYu