netty UnpooledHeapByteBuf 源码分析
UnpooledHeapByteBuf 是基于堆内存进行内存分配的字节缓冲区,没有基于对象池技术实现,这意味着每次I/O的读写都会创建一个新的UnpooledHeapByteBuf,频繁进行大块内存的分配和回收对性能会造成一定的影响,但是对比与堆外内存的申请和释放,它的成本会低一些。
相对与PooledHeapByteBuf,UnpooledHeapByteBuf 的实现原理更加简单,也不容易出现内存管理方面的问题,在满足性能的情况下,尽量使用UnpooledHeapByteBuf 。
1.成员变量
private final ByteBufAllocator alloc;//聚合一个ByteBufAllocator,用于UnpooledHeapByteBuf的内存分配 private byte[] array;//byte 数组作为缓冲区 private ByteBuffer tmpNioBuf;//用于实现Netty ByteBuf 到 JDK ByteBuffer 的转换
事实上,如果使用JDK 的ByteBuffer替换byte数组也是可行的 ,直接使用byte数组的根本原因是提升性能和更加便捷的进行位操作。JDK 的ByteBuffer底层实现也是byte数组,如下。
public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer>{ // These fields are declared here rather than in Heap-X-Buffer in order to // reduce the number of virtual method invocations needed to access these // values, which is especially costly when coding small buffers. // final byte[] hb; // Non-null only for heap buffers final int offset; boolean isReadOnly; // Valid only for heap buffers
@Override public ByteBuf capacity(int newCapacity) { ensureAccessible(); if (newCapacity < 0 || newCapacity > maxCapacity()) { throw new IllegalArgumentException("newCapacity: " + newCapacity); } int oldCapacity = array.length; if (newCapacity > oldCapacity) { byte[] newArray = new byte[newCapacity]; System.arraycopy(array, 0, newArray, 0, array.length); setArray(newArray); } else if (newCapacity < oldCapacity) { byte[] newArray = new byte[newCapacity]; int readerIndex = readerIndex(); if (readerIndex < newCapacity) { int writerIndex = writerIndex(); if (writerIndex > newCapacity) { writerIndex(writerIndex = newCapacity); } System.arraycopy(array, readerIndex, newArray, readerIndex, writerIndex - readerIndex); } else { setIndex(newCapacity, newCapacity); } setArray(newArray); } return this; }
/** * Should be called by every method that tries to access the buffers content to check * if the buffer was released before. */ protected final void ensureAccessible() { if (refCnt() == 0) { throw new IllegalReferenceCountException(0); } }
方法 的入口首先对新容量进行合法性校验,如果大于容量上限或者小于0,则抛出IllegalArgumentException异常。
private void setArray(byte[] initialArray) { array = initialArray; tmpNioBuf = null; }
动态扩容完成后,需要将原来的视图tmpNioBuf设置为空。
如果新的容量小于当前缓冲区容量不需要动态扩展,但是需要截取当前缓冲区创建一个新的子缓冲区。先判断下读索引是否小于新的容量值,如果小于进一步判断写索引是否大于新的容量值,如果大于则将写索引设置为新的容量值(防止越界)。更新完写索引之后,通过 System.arraycopy将当前可读的字节数组复制到新创建的子缓冲区中, System.arraycopy(array, readerIndex, newArray, readerIndex, writerIndex - readerIndex);
如果新的容量值小于读索引,说明没有可读的字节数组需要复制到新创建的缓冲区中,将读写索引为新的容量值即可。最后调用setArray方法替换原来的字节数组。
3.字节数组复制
@Override public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) { checkSrcIndex(index, length, srcIndex, src.length); System.arraycopy(src, srcIndex, array, index, length); return this; }
首先做合法性校验。
protected final void checkSrcIndex(int index, int length, int srcIndex, int srcCapacity) { checkIndex(index, length); if (srcIndex < 0 || srcIndex > srcCapacity - length) { throw new IndexOutOfBoundsException(String.format( "srcIndex: %d, length: %d (expected: range(0, %d))", srcIndex, length, srcCapacity)); } }
校验index,length的值,如果小于0,抛出IllegalArgumentException异常,然后对两者之和进行判断,如果大于缓冲区的容量,则抛出IndexOutOfBoundsException异常。srcIndex和srcCapacity,与之类似。校验通过之后,调用 System.arraycopy(src, srcIndex, array, index, length)方法进行字节数组的复制。
protected final void checkIndex(int index, int fieldLength) { ensureAccessible(); if (fieldLength < 0) { throw new IllegalArgumentException("length: " + fieldLength + " (expected: >= 0)"); } if (index < 0 || index > capacity() - fieldLength) { throw new IndexOutOfBoundsException(String.format( "index: %d, length: %d (expected: range(0, %d))", index, fieldLength, capacity())); } }
注意: ButeBuf以set和get开头读写缓冲区的方法不会修改读写索引。
4.转换为JDK ByteBuffer
ByteBuffer 是基于byte数组实现,NIO的ByteBuffer提供wrap方法,可以将byte数组转换成ByteBuffer对象。
/** * Wraps a byte array into a buffer. * * <p> The new buffer will be backed by the given byte array; * that is, modifications to the buffer will cause the array to be modified * and vice versa. The new buffer's capacity will be * <tt>array.length</tt>, its position will be <tt>offset</tt>, its limit * will be <tt>offset + length</tt>, and its mark will be undefined. Its * {@link #array backing array} will be the given array, and * its {@link #arrayOffset array offset} will be zero. </p> * * @param array * The array that will back the new buffer * * @param offset * The offset of the subarray to be used; must be non-negative and * no larger than <tt>array.length</tt>. The new buffer's position * will be set to this value. * * @param length * The length of the subarray to be used; * must be non-negative and no larger than * <tt>array.length - offset</tt>. * The new buffer's limit will be set to <tt>offset + length</tt>. * * @return The new byte buffer * * @throws IndexOutOfBoundsException * If the preconditions on the <tt>offset</tt> and <tt>length</tt> * parameters do not hold */ public static ByteBuffer wrap(byte[] array, int offset, int length) { try { return new HeapByteBuffer(array, offset, length); } catch (IllegalArgumentException x) { throw new IndexOutOfBoundsException(); } }
UnpooledHeapByteBuf.java
@Override public ByteBuffer nioBuffer(int index, int length) { ensureAccessible(); return ByteBuffer.wrap(array, index, length).slice(); }
调用了ByteBuffer的slice方法,由于每次调用nioBuffer都会创建一个新的ByteBuffer,因此此处的slice方法起不到重用缓冲区的效果,只能保证读写索引的独立性。
5.与子类相关的方法
isDirect方法:如果基础堆内存实现的ByteBuf,返回false,
@Override public boolean isDirect() { return false; }
hasArray方法:因为UnpooledHeapByteBuf是基于字节数组实现,返回true
@Override public boolean hasArray() { return true; }
array方法:因为UnpooledHeapByteBuf是基于字节数组实现,所以返回值是内部的字节数组成员变量。调用array方法之前,可以先使用hasArray方法进行判断,如果返回false说明当前ByteBuf不支持array方法。
@Override public byte[] array() { ensureAccessible(); return array; }