I have some Java networking code that uses the Java NIO (
java.nio) package and the Java Concurrency utilities (
java.util.concurrent) package. My code is structured to enqueue pending output on a write buffer queue. The write buffer queue is a sequence of
ByteBuffers implemented as a
Now, I want to perform a gathering write operation of pending
ByteBuffers in the write buffer queue. The
BlockingDeque has a
drainTo(Collection<? super E>) method that removes elements from the
Deque and places them in a specified
Collection of the appropriate type. Here's the snag: the
write() method requires a
ByteBuffer, not a
Converting from a
Collection<ByteBuffer> to a
ByteBuffer seems as simple as calling the
java.util.Arrays.asList(T...) method. Just wrap the target array as a
List then call
write() with the filled array:
ByteBuffer writeBufferArray = new ByteBuffer[BUFFER_COUNT]; // write() needs this List<ByteBuffer> writeBufferList = Arrays.asList(writeBufferArray); // drainTo() needs this int writeBufferCount = writeBufferDeque.drainTo(writeBufferList); // fills writeBufferArray? socketChannel.write(writeBufferArray, 0, writeBufferCount);
Unfortunately this does not work because an
UnsupportedOperationExcepion is raised on the call to
drainTo(). This happens because the
List produced by
Arrays.asList(T...) does not permit append operations. It enforces this restriction by not implementing the
java.util.AbstractList.add(int, Object) method.
It would be easy enough to copy the
ByteBuffers to a "temporary"
Collection<ByteBuffer>, then call the
toArray() method, but that results in a "double copy." Each
ByteBuffer reference would be copied from the
BlockingDeque to the temporary
Collection<ByteBuffer>, then copied from the temporary
Collection<ByteBuffer> to the array produced by
Avoiding the double copy means that some way must be found to write directly to a
drainTo() demands a
Collection, some alternative array wrapper must be used.
One potential candidate is the venerable
ArrayList properly encapsulates an array by creating an internal copy of any array given to it and producing a defensive copy when
toArray() is invoked on it.
ArrayList does its job well, but it's not suited to this task because it performs copy operations to and from its own array, not a client provided array.
My solution for a
List that writes-through to an externally accessible array is called
ArrayWrapperList. It works like this:
ByteBuffer writeBufferArray = new ByteBuffer[BUFFER_COUNT]; List<ByteBuffer> writeBufferList = new ArrayWrapperList<ByteBuffer>(); writeBufferDeque.drainTo(writeBufferList); // writes-through directly to writeBufferArray socketChannel.write(writeBufferArray, 0, writeBufferCount);
Conceptually, the code behaves as though
writeBufferList.toArray(writeBufferArray) is invoked immediately after the call to
drainTo(). It should be noted that in my production version I invoke
drainTo(Collection<? super E>, int) not
drainTo(Collection<? super E>). The upper bound on the drained elements ensures that the size of
writeBufferArray is not exceeded.
ArrayWrapperList does not encapsulate the array it manages like
ArrayWrapperList trades-off encapsulation for the ability to write directly to a client accessible array.