Offer to come, dig friends take it! I am participating in the 2022 Spring Recruit Punch card activity. Click here for details.

Get using the get function

Convert ByteArray to byte[], and most people will use get in the first place

public ByteBuffer get(byte[] dst, int offset, int length) {
    checkBounds(offset, length, dst.length);
    if (length > remaining())
        throw new BufferUnderflowException();
    int end = offset + length;
    for (int i = offset; i < end; i++)
        dst[i] = get();
    return this;
}

public ByteBuffer get(byte[] dst) {
    return get(dst, 0, dst.length);
}
Copy the code

Both get functions create and pass in byte[] arrays, and then write data to them. Note that you need to know the length of the created byte[] array

int len = byteBuffer.limit() - byteBuffer.position();
Copy the code

There are several attributes of ByteArray involved here

  • Capacity: Indicates the capacity. The value cannot be changed
  • Limit: the first unreadable position, which is the last bit of the last data position. It’s actually the length of the data, and it can’t exceed the capacity.
  • Position: indicates the current position.

So how do these three change? Look at the picture below

You can see that position moves when we write data. When we finish writing data and prepare to read data, we need to call flip first to move position to the beginning, so as to read the complete data.

But we can also read data from the middle, so the length of the data we read is limit-position, not just limit

Get using the array function

But ByteArray has another function that we haven’t noticed before, and that’s array

public final byte[] array() {
    if (hb == null)
        throw new UnsupportedOperationException();
    if (isReadOnly)
        throw new ReadOnlyBufferException();
    return hb;
}
Copy the code

This returns a byte[], so wouldn’t it be better to use this directly?

We can see that the byte array is hb, a ByteArray property, and hb has null values.

So what is hb? Let’s look at the source code

final byte[] hb;   // Non-null only for heap buffers
Copy the code

As you can see, the only heap buffer that is not empty is the heap buffer.

ByteArray is an abstract class. We actually use its implementation classes HeapByteBuffer and DirectByteBuffer.

HeapByteBuffer and DirectByteBuffer

When we create ByteArray, we use the allocate function, and there are two functions in ByteArray

public static ByteBuffer allocateDirect(int capacity) {
    // Android-changed: Android's DirectByteBuffers carry a MemoryRef.
    // return new DirectByteBuffer(capacity);
    DirectByteBuffer.MemoryRef memoryRef = new DirectByteBuffer.MemoryRef(capacity);
    return new DirectByteBuffer(capacity, memoryRef);
}

public static ByteBuffer allocate(int capacity) {
    if (capacity < 0)
        throw new IllegalArgumentException();
    return new HeapByteBuffer(capacity, capacity);
}
Copy the code

You can see that these two functions create HeapByteBuffer and DirectByteBuffer respectively

So what’s the difference between the two?

DirectByteBuffer is not allocated on the heap and is not managed directly by the GC (the JAVA objects of the Direct Buffer are managed by the GC, and the operating system only frees the space claimed by the Direct Buffer when the GC reclaims its JAVA objects). It seems to feel like a “buffer in kernel.” A HeapByteBuffer is allocated on the Heap, or the Heap Buffer is simply a form of a byte[] array wrapped in a Heap Buffer. Check the JAVA source code and HeapByteBuffer is exactly that. HeapByteBuffer is allocated in the JVM heap memory and is reclaimed by the JVM management, whereas DirectByteBuffer is allocated directly by the system memory and is not managed by the JVM. See from the above differences:

1. Directbytebuffers are more expensive to create and free than HeapByteBuffers because allocating and freeing memory in the JVM heap is more efficient than allocating and creating memory in the system

2, because normal Read /write experiences a “kernel buffer” between the I/O device and the application space. DirectByteBuffer is like a cache on a “kernel buffer” and is not directly managed by GC; The Heap Buffer is simply a wrapper around an array of bytes []. Therefore, it is faster to write a Direct Buffer to a Channel than it is to write a HeapByteBuffer to a Channel.

So each class has its own advantages. Depending on the situation, DirectByteBuffer objects can be used if a ByteBuffer is frequently reused. Use the HeapByteBuffer object if you need to release and allocate frequently.

You can see the difference in how they handle GET or PUT data. Here’s an example of get:

//HeapByteBuffer
public byte get(a) {
    return hb[ix(nextGetIndex())];
}
Copy the code

The GET of HeapByteBuffer is very simple, which is to directly obtain the data of the corresponding position from HB

//DirectByteBuffer
public final byte get(a) {
    if(! memoryRef.isAccessible) {throw new IllegalStateException("buffer is inaccessible");
    }
    return get(ix(nextGetIndex()));
}
private byte get(long a) {
    return Memory.peekByte(a);
}
Copy the code

DirectByteBuffer’s get function is ultimately retrieved using Memory’s peekByte. So DirectByteBuffer operations are handled in Memory.

conclusion

So we know that HeapByteBuffer is allocated on the heap, which is essentially byte[], so its HB is not null, which is this byte[]. So HeapByteBuffer can use array directly to get byte[], but DirectByteBuffer cannot because its HB is null. So DirectByteBuffer can only get bytes using the get function []

Note here that DirectByteBuffer hb is not null on higher versions (at least Android 29, which version is not clear), and there is data, but whether this data is normal data has not been investigated, so it is safer to use get function