ByteBuf Netty source code analysis of the

ByteBuf Netty source code analysis of the


ByteBuf basis

Java Nio's Buffer

Process during data transmission, we often use the buffer.
In the Java NIO provides us with seven native buffer to achieve, corresponding to seven basic types of Java. Generally use more ByteBuffer. Although native Buffer to meet our daily use, but to carry out complex applications, when really a bit powerless, native Buffer there are the following disadvantages. Therefore Netty its packaging, providing a more friendly interface for our use.

  • When we call the corresponding method allocate Buffer class to create a buffer instance, it will allocate the specified space, while the length of the buffer will be fixed and can not be dynamically grow or shrink. If we write data is greater than the capacity of the buffer when it will happen array bounds errors.
  • Buffer only one position flag attribute Position, we can flip or rewind way to modify the position to handle data access position, believe it may lead to errors.
  • Buffer only provides access, flip, release, sign, compare, bulk movement and other basic operations of the buffer, we want to use advanced features, you have to own and maintain the package manually, use very inconvenient.

ByteBuf works

ByteBuf also as a buffer to access data through byte array, by the appearance pattern polymerized ByteBuffer JDK NIO element encapsulation.
ByteBuf is to assist with the read and write buffers by readerIndex writerIndex two position pointer operation.
After the initialization time of the object, and writerIndex readerIndex value of 0, as the read and write operations, and readerIndex writerIndex will increase, but not exceed writerIndex readerIndex, during the read operation, the space between 0 readerIndex will be treated as discard, call ByteBuf of discardReadBytes method, it can be released to reuse this part of space, similar to the compact operation ByteBuffer, the compression of the buffer. readerIndex writerIndex to the space, the space corresponding to the ByteBuffer limit position can be read, WriterIndex capacity of the space, the space equivalent to the capacity limit of the ByteBuffer, can continue to be written.
readerIndex with writerIndex position pointer read and write operations so that separation does not need to be adjusted with a position pointer, to simplify the read and write operations of the buffer.
Similarly, ByteBuf read and write operations on the package, provides the ability to dynamically extended, when we write operation to the buffer, the remaining free space needs to be verified, if the available space is insufficient, while the word to be written the number of sections is less than the maximum number of bytes writable, the buffer will be dynamically expand, it will re-create a buffer, and then copy the data into the buffer before newly created,

ByteBuf basic functions

  • Sequential read
    prior to the read operation, first buffer space available for verification. If the byte length to be read is smaller than 0, IllegalArgumentException exception is thrown if the byte length of the byte to be read is greater than the length of the written, throws an IndexOutOfBoundsException. After passing through check, call getBytes method, starting from the current readerIndex, reads the byte data length to the target length of dst, due to different subclasses to achieve is not the same, getBytes is an abstract method to be implemented by the corresponding subclass. If data is read successfully, readerIndex will be a corresponding increase in length.
public ByteBuf readBytes(ByteBuf dst, int dstIndex, int length) {
    checkReadableBytes(length);
    getBytes(readerIndex, dst, dstIndex, length);
    readerIndex += length;
    return this;
}
protected final void checkReadableBytes(int minimumReadableBytes) {
    ensureAccessible();
    if (minimumReadableBytes < 0) {
        throw new IllegalArgumentException("minimumReadableBytes: " + minimumReadableBytes + " (expected: >= 0)");
    }
    if (readerIndex > writerIndex - minimumReadableBytes) {
        throw new IndexOutOfBoundsException(String.format(
                "readerIndex(%d) + length(%d) exceeds writerIndex(%d): %s",
                readerIndex, minimumReadableBytes, writerIndex, this));
    }
}
  • Sequential write
    read operation is the source from srcIndex start byte array, the data is written to the current length of the length of ByteBuf.
    A start number of bytes written to the array need be verified, if the writing length is less than 0, IllegalArgumentException exception is thrown, if the number of bytes written may be less than the current number of bytes written ByteBuf, through testing. If the number of bytes written to the buffer is greater than the maximum dynamic expansion of capacity maxCapacity, it will throw
    an IndexOutOfBoundsException, otherwise, they would need to meet the number of bytes written by the dynamic expansion. CalculateNewCapacity calculated by first capacity after the re-expansion, then the call to the capacity expansion method, different subclasses have different implementations, it is also an abstract method.
    • Computing capacity expansion, the first gate valve is provided 4m, if you want to extend the capacity to use a threshold equal to the threshold capacity as a new buffer, if it is larger than the threshold as a step to 4M, 4M increments, if the extension period, to expand capacity greater than the maximum expansion capacity, then it can be extended to maximum capacity maxCapacity new capacity. Otherwise, it doubled from 64 starts, until after the results of the multiplier is greater than the capacity to expand, and then as a result of new capacity buffer.
    • To expand capacity by doubling the first step and then, if we just writerIndex + value of the length of the new capacity as a buffer, then again when the write operation performed after each time the need for capacity expansion, capacity expansion process requires memory replication too much can cause memory replication performance degradation system, the reason is doubled again minister, in the first space is relatively small, doubled and operating waste will not bring too much memory, but the memory growth to a certain time, and then doubling time, will result in a waste of memory, therefore, we need to set a threshold on the smoothed growth through long-step approach after reaching the threshold.
public ByteBuf writeBytes(byte[] src, int srcIndex, int length) {
    ensureWritable(length);
    setBytes(writerIndex, src, srcIndex, length);
    writerIndex += length;
    return this;
}
public ByteBuf ensureWritable(int minWritableBytes) {
    if (minWritableBytes < 0) {
        throw new IllegalArgumentException(String.format(
                "minWritableBytes: %d (expected: >= 0)", minWritableBytes));
    }

    if (minWritableBytes <= writableBytes()) {
        return this;
    }

    if (minWritableBytes > maxCapacity - writerIndex) {
        throw new IndexOutOfBoundsException(String.format(
                "writerIndex(%d) + minWritableBytes(%d) exceeds maxCapacity(%d): %s",
                writerIndex, minWritableBytes, maxCapacity, this));
    }

    // Normalize the current capacity to the power of 2.
    int newCapacity = calculateNewCapacity(writerIndex + minWritableBytes);

    // Adjust to the new capacity.
    capacity(newCapacity);
    return this;
}
private int calculateNewCapacity(int minNewCapacity) {
    final int maxCapacity = this.maxCapacity;
    final int threshold = 1048576 * 4; // 4 MiB page

    if (minNewCapacity == threshold) {
        return threshold;
    }

    // If over threshold, do not double but just increase by threshold.
    if (minNewCapacity > threshold) {
        int newCapacity = minNewCapacity / threshold * threshold;
        if (newCapacity > maxCapacity - threshold) {
            newCapacity = maxCapacity;
        } else {
            newCapacity += threshold;
        }
        return newCapacity;
    }

    // Not over threshold. Double up to 4 MiB, starting from 64.
    int newCapacity = 64;
    while (newCapacity < minNewCapacity) {
        newCapacity <<= 1;
    }

    return Math.min(newCapacity, maxCapacity);
}
//UnpooledHeapByteBuf的capacity实现
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;
}
  • Clear operation
    clear operations and writerIndex readerIndex just set to 0, is not modified stored data.
public ByteBuf clear() {
    readerIndex = writerIndex = 0;
    return this;
}
  • Index Operations

    • Write position index provided: mainly for checking the boundary condition, when set readerIndex, newReaderIndex with not less than 0 is greater than writerIndex; writerIndex when provided, newWriterIndex readerIndex must be greater and smaller than the current capacity. If you can not check, it would throw an IndexOutOfBoundsException.
    • mark and reset operations: Because readerIndex and writerIndex, thus for mark or reset need to specify the operating position index, mark operation will current readerIndex or writerIndex set markedReaderIndex or markedWriterIndex; reset operation, then, it is the incorporation mark corresponding to a value call the corresponding readerIndex () or writerIndex ();
  • Buffer reuse
    can go to reuse already been read by discardReadByte buffer method.
    First readerIndex judge:
    • If readerIndex equal to 0, it means there is no read data, there is no spatial reuse may be used to directly return;
    • If equal to 0 and not greater than readerIndex writerIndex, it indicates that there is read data buffer is discarded, the buffer has not yet been read. The method calls setBytes copy byte array, the data will not be read is moved to the beginning of the buffer, and again to set readerIndex writerIndex, readerIndex is 0, writerIndex original writerIndex-readerIndex; also a need to mark reset it.
      • First, make a backup of markedReaderIndex then compared with the decrement, if markedReaderIndex smaller than the decrement words, markedReaderIndex set to 0, then markedWriterIndex comparison with decrement, if less than then, markedWriterIndex is also set to 0, otherwise markedWriterIndex less decrement;
      • If markedReaderIndex larger than the decrement of words, markedReaderIndex and markedReaderIndex subtracted decrement it.
    • If readerIndex equal writerIndex, it indicates that there is no buffer can be reused, re-set directly on the mark on it, no memory copy.
public ByteBuf discardReadBytes() {
    ensureAccessible();
    if (readerIndex == 0) {
        return this;
    }

    if (readerIndex != writerIndex) {
        setBytes(0, this, readerIndex, writerIndex - readerIndex);
        writerIndex -= readerIndex;
        adjustMarkers(readerIndex);
        readerIndex = 0;
    } else {
        adjustMarkers(readerIndex);
        writerIndex = readerIndex = 0;
    }
    return this;
}
protected final void adjustMarkers(int decrement) {
    int markedReaderIndex = this.markedReaderIndex;
    if (markedReaderIndex <= decrement) {
        this.markedReaderIndex = 0;
        int markedWriterIndex = this.markedWriterIndex;
        if (markedWriterIndex <= decrement) {
            this.markedWriterIndex = 0;
        } else {
            this.markedWriterIndex = markedWriterIndex - decrement;
        }
    } else {
        this.markedReaderIndex = markedReaderIndex - decrement;
        markedWriterIndex -= decrement;
    }
}
  • skipBytes

When we need to skip some of the bytes, you can skip the method call skipBytes specified length of bytes to read back the data.
First jump length is determined, if the skip length is less than 0, then an IllegalArgumentException is thrown, and skipping the current buffer length is greater than the length of the read can throw an IndexOutOfBoundsException. If the check is passed, the new readerindex original readerIndex + length, if the new readerIndex greater than writerIndex, it will throw an IndexOutOfBoundsException, or to update readerIndex.

public ByteBuf skipBytes(int length) {
    checkReadableBytes(length);
    int newReaderIndex = readerIndex + length;
    if (newReaderIndex > writerIndex) {
        throw new IndexOutOfBoundsException(String.format(
                "length: %d (expected: readerIndex(%d) + length <= writerIndex(%d))",
                length, readerIndex, writerIndex));
    }
    readerIndex = newReaderIndex;
    return this;
}

ByteBuf source code analysis

ByteBuf

AbstractReferenceCountedByteBuf

AbstractReferenceCountedByteBuf is achieved ByteBuf reference counting base class used to track allocation and destroying objects, automatic memory reclamation.

  • Member variables
    • refCntUpdater refCntUpdater AtomicIntegerFieldUpdater is a member variable of type, it can be atomic update operations on member variables to achieve thread safety.
    • REFCNT_FIELD_OFFSET REFCNT_FIELD_OFFSET is identified refCnt AbstractReferenceCountedByteBuf memory address field, this will be used to offset the UnpooledDirectByteBuf PooledDirectByteBuf and two subclasses.
    • refCnt volatile variable is modified to ensure the visibility of the thread used to track objects citations
  • Object reference counter
    every method call to retain a reference counter will be incremented by one. retain a method of adding the reference counter by the spin operation, the initial value of a reference counter, as long as the program is executed correctly, then it should be a minimum value, when the number of times equal to the application and release time will be corresponding ByteBuf Be recycled. When the number is 0, indicating that the object was mistakenly quoted, IllegalReferenceCountException throws an exception, if the number of times equal to the maximum value of type Integer, will throw
    IllegalReferenceCountException exception. The method is performed by compareAndSet refCntUpdater retain atomic update operation, and uses the value compareAndSet compares the acquired expected value, if the comparison device, there are other threads to modify the variable, then the comparison fails, spin again, and again acquires the value of the reference counter Compare, otherwise, it would be a plus, exit the spin.
    Similar release method, then the retain method, but also to judge and updated through the spin cycle, but when the value of refCnt is equal to 1, indicating that the application references the counter with the number of release as an object reference has been unreachable, the object should be garbage recycling collection out, call deallocate method releases the object ByteBuf
public ByteBuf retain() {
    for (;;) {
        int refCnt = this.refCnt;
        if (refCnt == 0) {
            throw new IllegalReferenceCountException(0, 1);
        }
        if (refCnt == Integer.MAX_VALUE) {
            throw new IllegalReferenceCountException(Integer.MAX_VALUE, 1);
        }
        if (refCntUpdater.compareAndSet(this, refCnt, refCnt + 1)) {
            break;
        }
    }
    return this;
}
    
public final boolean release() {
    for (;;) {
        int refCnt = this.refCnt;
        if (refCnt == 0) {
            throw new IllegalReferenceCountException(0, -1);
        }

        if (refCntUpdater.compareAndSet(this, refCnt, refCnt - 1)) {
            if (refCnt == 1) {
                deallocate();
                return true;
            }
            return false;
        }
    }
}

UnpooledHeapByteBuf

UnpooledHeapByteBuf is a non-thread pool to achieve the heap memory allocation byte buffer, to create a UnpooledHeapByteBuf objects will each IO operation, if the memory is allocated or released frequently impact on performance.

  • Member variables
    • ByteBufAllocator for memory allocation
    • array as a byte array buffer for storing bytes of data
    • ByteBuffer to implement Netty ByteBuf to transform the Nio ByteBuffer
  • Dynamically expanding buffers
    call to the capacity expansion method of dynamic buffer must first verify expand capacity, if the size of the new capacity is less than 0 or greater than the maximum scalable capacity maxCapacity, then an IllegalArgumentException exception.
    After passing through check, if a new expansion capacity than the original, then create a new byte array buffer capacity of the newly expanded capacity, then call System.arraycopy for memory copy, copy the old data to the new array to go then replaced with setArray array. Original view tmpNioBuffer need to dynamically control after spreading.
    If the new current buffer capacity is less than the capacity, then dynamic expansion is not required, but the need to intercept the data as part of the sub-buffer.
    • First, the current is less than newCapacity readerIndex, if it is less than compared with continued writerIndex newCapacity, if greater than newCapacity writerIndex words, it will be set to newCapacity writerIndex, it System.arrayCopy by copying the current-readable memory after completion of updating the index data to the new byte array buffer.
    • If newCapacity less than readerIndex, it indicates that there is no new data to be copied to the new readable byte array buffer, just put writerIndex with readerIndex are updated newCapacity can finally call setArray replacement byte array.
 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;
}
  • setBytes
    array of bytes to copy, the data is first validity test, or if the value of the index is less than 0 srcIndex, an IllegalArgumentException is thrown if index + capacity value is greater than the value of length or length is greater than the value of + srcIndex words src.length , will throw an IndexOutOfBoundsException. After passing through check, call System.arraycopy byte array replication.
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));
    }
}
  • Netty ByteBuf and Nio ByteBuffer conversion
    ByteBuf To Netty converted into Nio ByteBuffer, ByteBuffer have wrap a static method, only need to pass the corresponding byte array to create into a ByteBuffer, in nioBuffer method also calls the slice method, it You can create a shared data elements from the same period of the original ByteBuffer buffer start position of the original buffer. nioBuffer method will not reuse the buffer can only guarantee the independence of writerIndex with readerIndex.
public ByteBuffer nioBuffer(int index, int length) {
    ensureAccessible();
    return ByteBuffer.wrap(array, index, length).slice();
}

PooledByteBuf

After adding memory pool management Netty4, through the memory pool management has been greatly improved than before to create a performance of ByteBuf.

  • PoolChunk
    • The minimum unit of the memory block can be used to allocate the Page
    • Chunk page collection of

PoolChunk responsible for allocating and freeing memory blocks of the chunk will be constructed as a binary page, default page size is 8K, the chunk size is 2 ^ 11 page, i.e. 16M, constitute the binary layer 11, the bottom layer of the leaf node 8192, as the number of the page, each memory allocation must guarantee the continuity of memory operations easy. Each node in the Memory Area will record their offset addresses, when a node represents a memory area is assigned, then the node will be marked as allocated, all child nodes of the node memory requests will be ignored. Each memory allocation is 8k (2 ^ n) the size of the memory block when the memory size needs to be allocated to an end chunkSize / (2 ^ k), in order to find an available memory segments, will start looking from the left of the first layer K available node.

  • PoolArena

In the memory allocation, in order to centrally manage the memory allocation and release, while providing memory allocation and release performance, will generally pre-assigned to a contiguous chunk of memory, the memory operation does not need to be repeated frequently, that large consecutive memory block is called memory Arena, and is PoolArena Netty memory pool implementation class.
In the Netty, PoolArena Chunk is composed of a plurality, and each plurality Page composed by Chunk. PoolArena is jointly organized by Chunk and Page and management.

  • PoolSubpage

When less than a Page for memory allocation when each Page is divided into equal size blocks of memory, which is the size of memory block size in accordance with the first memory allocation request is determined. Equal to the size of the memory block can only be assigned a Page with the first memory of the memory block of memory, if you do not want to want to want to apply the size of the memory block, etc., can only apply the memory allocation in the new Page.
The use of storage area Page is through a long array to maintain bitmap, each bit represents one area of occupancy.

PooledDirectByteBuf

  • Create a byte buffer
    because the memory pool implementation, creation each time a byte buffer, not direct new, but to get from memory pool, then set a reference counter with read and write Index, returned with the maximum capacity of the buffer.
static PooledHeapByteBuf newInstance(int maxCapacity) {
    PooledHeapByteBuf buf = RECYCLER.get();
    buf.reuse(maxCapacity);
    return buf;
}
final void reuse(int maxCapacity) {
    maxCapacity(maxCapacity);
    setRefCnt(1);
    setIndex0(0, 0);
    discardMarks();
}
  • Examples copy byte buffer
    copy method may copy a byte buffer instance, independently of the original buffer.
    It must first determine the legality of the index and length, and then call directBuffer PooledByteBufAllocator method of allocating a new buffer. newDirectBuffer method is an abstract method, for different subclasses have different implementations. If unpooled then, it will directly create a new buffer, if it is pooled, then it will get the next available buffer from the memory pool.
public ByteBuf copy(int index, int length) {
    checkIndex(index, length);
    ByteBuf copy = alloc().directBuffer(length, maxCapacity());
    copy.writeBytes(this, index, length);
    return copy;
}
public ByteBuf directBuffer(int initialCapacity, int maxCapacity) {
    if (initialCapacity == 0 && maxCapacity == 0) {
        return emptyBuf;
    }
    validate(initialCapacity, maxCapacity);
    return newDirectBuffer(initialCapacity, maxCapacity);
}
// PooledByteBufAllocator 
protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) {
    PoolThreadCache cache = threadCache.get();
    PoolArena<ByteBuffer> directArena = cache.directArena;

    ByteBuf buf;
    if (directArena != null) {
        buf = directArena.allocate(cache, initialCapacity, maxCapacity);
    } else {
        if (PlatformDependent.hasUnsafe()) {
            buf = new UnpooledUnsafeDirectByteBuf(this, initialCapacity, maxCapacity);
        } else {
            buf = new UnpooledDirectByteBuf(this, initialCapacity, maxCapacity);
        }
    }

    return toLeakAwareBuffer(buf);
}
//UnpooledByteBufAllocator
protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) {
    ByteBuf buf;
    if (PlatformDependent.hasUnsafe()) {
        buf = new UnpooledUnsafeDirectByteBuf(this, initialCapacity, maxCapacity);
    } else {
        buf = new UnpooledDirectByteBuf(this, initialCapacity, maxCapacity);
    }

    return toLeakAwareBuffer(buf);
}

ByteBuf helper class analysis

ByteBufHolder

ByteBufHolder is a container ByteBuf, it can more easily access the data ByteBuf is, when data transmission in using different protocols, data formats and field different protocol message comprises not the same, so abstract a ByteBufHolder to ByteBuf be packaging, different subclasses have different implementations, users can implement according to their needs. Netty provides a default implementation DefaultByteBufHolder.

ByteBufAllocator

ByteBufAllocator byte buffer dispenser according to different implementations Netty byte buffer is divided into two different dispenser PooledByteBufAllocator and UnpooledByteBufAllocator. They offer a different allocation method of ByteBuf.

CompositeByteBuf

CompositeByteBuf is a virtual Buffer, it can be assembled into a multiple ByteBuf ByteBuf view.
In Java NIO, we have two ways to achieve

  • Copy additional data to a ByteBuffer ByteBuffer in, or re-create a new ByteBuffer, ByteBuffer other copy to the newly created ByteBuffer in.
  • Container storage by multiple ByteBuffer together, unified management and maintenance.

In Netty in, CompositeByByteBuf maintained a collection of Component type. Component is ByteBuf wrapper class, it ByteBuf polymerized. Maintenance positional displacement amount in the collection information. Under normal circumstances, we should use ByteBufAllocator.compositeBuffer () and Unpooled.wrappedBuffer (ByteBuf ...) method to create CompositeByteBuf, rather than directly by the constructor to instantiate a CompositeByteBuf object.

private int addComponent0(int cIndex, ByteBuf buffer) {
    checkComponentIndex(cIndex);
    if (buffer == null) {
        throw new NullPointerException("buffer");
    }

    int readableBytes = buffer.readableBytes();

    // No need to consolidate - just add a component to the list.
    Component c = new Component(buffer.order(ByteOrder.BIG_ENDIAN).slice());
    if (cIndex == components.size()) {
        components.add(c);
        if (cIndex == 0) {
            c.endOffset = readableBytes;
        } else {
            Component prev = components.get(cIndex - 1);
            c.offset = prev.endOffset;
            c.endOffset = c.offset + readableBytes;
        }
    } else {
        components.add(cIndex, c);
        if (readableBytes != 0) {
            updateComponentOffsets(cIndex);
        }
    }
    return cIndex;
}
private void consolidateIfNeeded() {
    final int numComponents = components.size();
    if (numComponents > maxNumComponents) {
        final int capacity = components.get(numComponents - 1).endOffset;
    
        ByteBuf consolidated = allocBuffer(capacity);
    
        for (int i = 0; i < numComponents; i ++) {
            Component c = components.get(i);
            ByteBuf b = c.buf;
            consolidated.writeBytes(b);
            c.freeIfNecessary();
        }
        Component c = new Component(consolidated);
        c.endOffset = c.length;
        components.clear();
        components.add(c);
    }
}

public CompositeByteBuf removeComponent(int cIndex) {
    checkComponentIndex(cIndex);
    Component comp = components.remove(cIndex);
    comp.freeIfNecessary();
    if (comp.length > 0) {
        updateComponentOffsets(cIndex);
    }
    return this;
}

private static final class Component {
    final ByteBuf buf;
    final int length;
    int offset;
    int endOffset;

    Component(ByteBuf buf) {
        this.buf = buf;
        length = buf.readableBytes();
    }

    void freeIfNecessary() {
        buf.release(); // We should not get a NPE here. If so, it must be a bug.
    }
}

ByteBufUtil

ByteBufUtil is ByteBuf utility class, which provides a series of static methods to manipulate ByteBuf.

Guess you like

Origin blog.csdn.net/qq_34730511/article/details/80568062