13-ByteBuf

ByteBuf

  • ByteBuf 是 Netty 中数据容器,它高效的实现了底层数据通信过程中所需要的字节序列的相关操作。

  • ByteBuf 自身的源码就非常多,而且还有很多子类,因此不可能面面俱到,我们分析一些主要的点,对比BIO中的ByteBuffer来对比会更好,ByteBuffer是Java Nio中的字节容器,但是使用上较为复杂,Netty使用ByteBuf来实现了更加强大的功能,我们看看Netty 自己实现的ByteBuf具体有什么优势。

  • 考虑到 ByteBuf 的内容非常多,会将其分为几个部分慢慢研究,本文主要关于ByteBuf的API;

一、ByteBuf

  • ByteBuf 是一个抽象类,是Netty中具体的ByteBuf的抽象父类,实现了ReferenceCounted 和 Comparable 接口,ReferenceCounted 接口在后面文章分析
public abstract class ByteBuf implements ReferenceCounted, Comparable<ByteBuf> 

1.1 类注释

  • 先从类注释获取一些基本的信息,对其有一个大致的了解
/**
 * 一个零个或者多个字节序列,可以随机并且有序访问;此接口提供原始字节的抽象视图
 *
 * A random and sequential accessible sequence of zero or more bytes (octets).
 * This interface provides an abstract view for one or more primitive byte
 * arrays ({@code byte[]}) and {@linkplain ByteBuffer NIO buffers}.
 *
 * <h3>Creation of a buffer</h3>
 *
 * 推荐使用帮助方法Unpooled 来创建一个实例,而不是使用构造方法
 * It is recommended to create a new buffer using the helper methods in
 * {@link Unpooled} rather than calling an individual implementation's
 * constructor.
 *
 * <h3>Random Access Indexing</h3>
 * 可以像数组一样支持随机访问,下标 是: 0 ->  buffer.capacity()-1
 * Just like an ordinary primitive byte array, {@link ByteBuf} uses
 * <a href="http://en.wikipedia.org/wiki/Zero-based_numbering">zero-based indexing</a>.
 * It means the index of the first byte is always {@code 0} and the index of the last byte is
 * always {@link #capacity() capacity - 1}.  For example, to iterate all bytes of a buffer, you
 * can do the following, regardless of its internal implementation:
 *
 * <pre>
 * {@link ByteBuf} buffer = ...;
 * for (int i = 0; i &lt; buffer.capacity(); i ++) {
 *     byte b = buffer.getByte(i);
 *     System.out.println((char) b);
 * }
 * </pre>
 *
 * <h3>Sequential Access Indexing</h3>
 * 提供两个指针变量来支持顺序读写操作,readIndex()用于读操作,writeIndex()用于写操作,
 * 下图展示了buffer如何被两个指针分成三个部分:
 *  0  <=  readIndex <= writeIndex <= capacity
 *  0   丢弃部分 readIndex     可读部分 writeIndex   可写部分 capacity
 *
 * {@link ByteBuf} provides two pointer variables to support sequential
 * read and write operations - {@link #readerIndex() readerIndex} for a read
 * operation and {@link #writerIndex() writerIndex} for a write operation
 * respectively.  The following diagram shows how a buffer is segmented into
 * three areas by the two pointers:
 *
 * <pre>
 *      +-------------------+------------------+------------------+
 *      | discardable bytes |  readable bytes  |  writable bytes  |
 *      |                   |     (CONTENT)    |                  |
 *      +-------------------+------------------+------------------+
 *      |                   |                  |                  |
 *      0      <=      readerIndex   <=   writerIndex    <=    capacity
 * </pre>
 *
 * <h4>Readable bytes (the actual content)</h4>
 *
 * read开头的操作和skip开头的操作是读取数据,读取数据后readerIndex会自动增加读取的
 * 部分长度,skip会跳过一部分读取;
 *
 * 如果读取操作的参数也是一个ByteBuf对象并且没有指定目标index,那么参数对象的writerIndex也
 * 会增长,如果空间不足,则会抛出:IndexOutOfBoundsException
 *
 * This segment is where the actual data is stored.  Any operation whose name
 * starts with {@code read} or {@code skip} will get or skip the data at the
 * current {@link #readerIndex() readerIndex} and increase it by the number of
 * read bytes.  If the argument of the read operation is also a
 * {@link ByteBuf} and no destination index is specified, the specified
 * buffer's {@link #writerIndex() writerIndex} is increased together.
 * <p>
 * If there's not enough content left, {@link IndexOutOfBoundsException} is
 * raised.  The default value of newly allocated, wrapped or copied buffer's
 * {@link #readerIndex() readerIndex} is {@code 0}.
 *
 * <pre>
 * // Iterates the readable bytes of a buffer.
 * {@link ByteBuf} buffer = ...;
 * while (buffer.isReadable()) {
 *     System.out.println(buffer.readByte());
 * }
 * </pre>
 *
 * <h4>Writable bytes</h4>
 *
 * write开头的操作是写数据,写数据之后writerIndex会增加所写部分的长度
 * 如果写操作的参数也是ByteBuf,并且没有指定index,那么参数对应的ByteBuf的readerIndex也
 * 会一起自增,如果空间不足,则会抛出:IndexOutOfBoundsException
 *
 * 新创建的ByteBuf的writerIndex是0,wrapped的或者copied的Buffer的writerIndex等于capacity
 *
 * This segment is a undefined space which needs to be filled.  Any operation
 * whose name starts with {@code write} will write the data at the current
 * {@link #writerIndex() writerIndex} and increase it by the number of written
 * bytes.  If the argument of the write operation is also a {@link ByteBuf},
 * and no source index is specified, the specified buffer's
 * {@link #readerIndex() readerIndex} is increased together.
 * <p>
 *
 * If there's not enough writable bytes left, {@link IndexOutOfBoundsException}
 * is raised.  The default value of newly allocated buffer's
 * {@link #writerIndex() writerIndex} is {@code 0}.  The default value of
 * wrapped or copied buffer's {@link #writerIndex() writerIndex} is the
 * {@link #capacity() capacity} of the buffer.
 *
 * <pre>
 * // Fills the writable bytes of a buffer with random integers.
 * {@link ByteBuf} buffer = ...;
 * while (buffer.maxWritableBytes() >= 4) {
 *     buffer.writeInt(random.nextInt());
 * }
 * </pre>
 *
 * <h4>Discardable bytes</h4>
 * 已经读取的部分称为丢弃部分,最初是0,随着读取操作的进行,这部分区域越来越大,可以通过
 * discardReadBytes方法来回收该部分的内存区域,调用之后这部分区域会变成可写区域,如下图所示
 *
 * This segment contains the bytes which were read already by a read operation.
 * Initially, the size of this segment is {@code 0}, but its size increases up
 * to the {@link #writerIndex() writerIndex} as read operations are executed.
 * The read bytes can be discarded by calling {@link #discardReadBytes()} to
 * reclaim unused area as depicted by the following diagram:
 *
 * <pre>
 *  BEFORE discardReadBytes()
 *
 *      +-------------------+------------------+------------------+
 *      | discardable bytes |  readable bytes  |  writable bytes  |
 *      +-------------------+------------------+------------------+
 *      |                   |                  |                  |
 *      0      <=      readerIndex   <=   writerIndex    <=    capacity
 *
 *
 *  AFTER discardReadBytes()
 *
 *      +------------------+--------------------------------------+
 *      |  readable bytes  |    writable bytes (got more space)   |
 *      +------------------+--------------------------------------+
 *      |                  |                                      |
 * readerIndex (0) <= writerIndex (decreased)        <=        capacity
 * </pre>
 *
 * 注意writable bytes是不能保证的,多数情况下不会被移动,这取决的底层实现
 * Please note that there is no guarantee about the content of writable bytes
 * after calling {@link #discardReadBytes()}.  The writable bytes will not be
 * moved in most cases and could even be filled with completely different data
 * depending on the underlying buffer implementation.
 *
 * <h4>Clearing the buffer indexes</h4>
 *
 * clear方法可以将读写索引置零,但是并不会清楚buffer的内容,注意clear操作的语义
 * 和ByteBuffer#clear()是不一样的
 *
 * You can set both {@link #readerIndex() readerIndex} and
 * {@link #writerIndex() writerIndex} to {@code 0} by calling {@link #clear()}.
 * It does not clear the buffer content (e.g. filling with {@code 0}) but just
 * clears the two pointers.  Please also note that the semantic of this
 * operation is different from {@link ByteBuffer#clear()}.
 *
 * <pre>
 *  BEFORE clear()
 *
 *      +-------------------+------------------+------------------+
 *      | discardable bytes |  readable bytes  |  writable bytes  |
 *      +-------------------+------------------+------------------+
 *      |                   |                  |                  |
 *      0      <=      readerIndex   <=   writerIndex    <=    capacity
 *
 *
 *  AFTER clear()
 *
 *      +---------------------------------------------------------+
 *      |             writable bytes (got more space)             |
 *      +---------------------------------------------------------+
 *      |                                                         |
 *      0 = readerIndex = writerIndex            <=            capacity
 * </pre>
 *
 * <h3>Search operations</h3>
 * 使用indexOf,bytesBefore,forEachByte来搜索
 * For simple single-byte searches, use {@link #indexOf(int, int, byte)} and {@link #bytesBefore(int, int, byte)}.
 * {@link #bytesBefore(byte)} is especially useful when you deal with a {@code NUL}-terminated string.
 * For complicated searches, use {@link #forEachByte(int, int, ByteProcessor)} with a {@link ByteProcessor}
 * implementation.
 *
 * <h3>Mark and reset</h3>
 *  markReaderIndex 和 markWriterIndex分表标记读写索引的位置
 * There are two marker indexes in every buffer. One is for storing
 * {@link #readerIndex() readerIndex} and the other is for storing
 * {@link #writerIndex() writerIndex}.  You can always reposition one of the
 * two indexes by calling a reset method.  It works in a similar fashion to
 * the mark and reset methods in {@link InputStream} except that there's no
 * {@code readlimit}.
 *
 * <h3>Derived buffers</h3>
 * 派生缓冲区,下面方法会给一个已存在的buffer创建一个缓冲区视图,(内部存储的是一份数据,改动是关联
 * 的,使用一个引用计数,但是读写索引和索引标记是相互独立的)
 *
 * 如果需要一份独立的拷贝,使用copy方法
 * You can create a view of an existing buffer by calling one of the following methods:
 * <ul>
 *   <li>{@link #duplicate()}</li>
 *   <li>{@link #slice()}</li>
 *   <li>{@link #slice(int, int)}</li>
 *   <li>{@link #readSlice(int)}</li>
 *   <li>{@link #retainedDuplicate()}</li>
 *   <li>{@link #retainedSlice()}</li>
 *   <li>{@link #retainedSlice(int, int)}</li>
 *   <li>{@link #readRetainedSlice(int)}</li>
 * </ul>
 * A derived buffer will have an independent {@link #readerIndex() readerIndex},
 * {@link #writerIndex() writerIndex} and marker indexes, while it shares
 * other internal data representation, just like a NIO buffer does.
 * <p>
 * In case a completely fresh copy of an existing buffer is required, please
 * call {@link #copy()} method instead.
 *
 * <h4>Non-retained and retained derived buffers</h4>
 *
 * 注意duplicate、slice、slice、readSlice这些方法返回派生缓冲区是不会调用retain增加引用计数器的,
 *
 * 如果需要创建一个buffer并且增加引用计数器,那么使用retainedDuplicate、retainedSlice、retainedSlice、readRetainedSlice,
 * 它们返回的buffer会产生更少的垃圾
 *
 * Note that the {@link #duplicate()}, {@link #slice()}, {@link #slice(int, int)} and {@link #readSlice(int)} does NOT
 * call {@link #retain()} on the returned derived buffer, and thus its reference count will NOT be increased. If you
 * need to create a derived buffer with increased reference count, consider using {@link #retainedDuplicate()},
 * {@link #retainedSlice()}, {@link #retainedSlice(int, int)} and {@link #readRetainedSlice(int)} which may return
 * a buffer implementation that produces less garbage.
 *
 * <h3>Conversion to existing JDK types</h3>
 *
 * <h4>Byte array</h4>
 *
 * 如果ByteBuf内部使用byte array来保存数据,那么可以通过array方法来获取这个字节数组,
 * 使用hasArray可以判断其是不是由byte array来保存数据的
 * If a {@link ByteBuf} is backed by a byte array (i.e. {@code byte[]}),
 * you can access it directly via the {@link #array()} method.  To determine
 * if a buffer is backed by a byte array, {@link #hasArray()} should be used.
 *
 * <h4>NIO Buffers</h4>
 * 如果一个ByteBuf能够转换为一个NIO的ByteBuffer,并且内容共享,可以调用nioBuffer方法来做转换,
 * 使用nioBufferCount判断一个ByteBuf 是否可以转换为一个 NIO的ByteBuffer
 * If a {@link ByteBuf} can be converted into an NIO {@link ByteBuffer} which shares its
 * content (i.e. view buffer), you can get it via the {@link #nioBuffer()} method.  To determine
 * if a buffer can be converted into an NIO buffer, use {@link #nioBufferCount()}.
 *
 * <h4>Strings</h4>
 *
 * Various {@link #toString(Charset)} methods convert a {@link ByteBuf}
 * into a {@link String}.  Please note that {@link #toString()} is not a
 * conversion method.
 *
 * <h4>I/O Streams</h4>
 *
 * Please refer to {@link ByteBufInputStream} and
 * {@link ByteBufOutputStream}.
 */
  • 小结如下:
1.ByteBuf内部是字节序列,支持随机访问和顺序访问,可以通过下标访问,使用Unpooled创建
2.内部通过读写两个索引来控制读写,两个索引都要对应的标记方法,读写需要注意越界异常
3.已经读取过的内容相当于废弃内容,可以通过discardReadBytes来腾出该部分空间用于写入,不过这取决于不同子类实现
4.派生缓冲区会共享数据,但是读写索引是独立的,引用计数是共享的,对应的也有一些方法提供buffer并且增加引用计数
5.可以通过方法获取内部的字节数组

1.2 方法

  • 方法非常多,只能归类熟悉
1.2.1 属性相关
  • ByteBuf 基本属性相关的方法
public abstract int capacity(); // 容量
public abstract ByteBuf capacity(int newCapacity);
public abstract int maxCapacity(); // 最大容量

public abstract ByteBufAllocator alloc(); // 分配器,用于创建 ByteBuf 对象。

public abstract ByteBuf unwrap(); // 获得被包装( wrap )的 ByteBuf 对象。

public abstract boolean isDirect(); // 是否 NIO Direct Buffer

public abstract boolean isReadOnly(); // 是否为只读 Buffer

public abstract int readerIndex(); //获取读取位置
public abstract ByteBuf readerIndex(int readerIndex); //设置读取位置
public abstract int writerIndex(); //获取写入位置
public abstract ByteBuf writerIndex(int writerIndex); //设置写入位置
public abstract ByteBuf setIndex(int readerIndex, int writerIndex); // 设置读取和写入位置
public abstract int readableBytes(); // 剩余可读字节数
public abstract int writableBytes(); // 剩余可写字节数
public abstract ByteBuf markReaderIndex(); // 标记读取位置
public abstract ByteBuf markWriterIndex(); // 标记写入位置
1.2.2 读写数据
  • 数据读取方法,可以按照不同的类型读取,读取的字节数也不一样,下面只给出Int类型的读写,其他类型的读写类似
// Int 4 字节
//读取
//绝对读,不改变索引
public abstract int   getInt(int index); //指定位置读取int,不改变读索引
public abstract int   getIntLE(int index); //指定位置读取int,不改变读索引,以小端的方式读取

//普通读,改变索引
public abstract int   readInt();//读取int,readerIndex增加4
public abstract int   readIntLE();//读取int,readerIndex增加4,以小端的方式读取


//写入
//绝对写,不改变索引
public abstract ByteBuf setInt(int index, int value);//在指定位置写入int,不改变读写索引
public abstract ByteBuf setIntLE(int index, int value);//在指定位置写入int,不改变读写索引,以小端的方式写入

//普通写,改变索引
public abstract ByteBuf writeInt(int value);//写入int,并且增加写索引
public abstract ByteBuf writeIntLE(int value);//写入int,并且增加写索引
  • 这里还有很多其他类型的方法没有给出,比如读取无符号数等,但是讨论都一样,我们看到读和写都有两类方法;直接通过get和set的读写方法是不改变索引的,因为是随机访问直接通过index操作,而read/write对应的读写API的会改变索引的,相当于在前面索引的基础之上再继续操作;
  • 其他还有很多类型,各种基本类型、数组、Medium 类型等操作都有类似的API
1.2.3 查找方法
// 指定值( value ) 在 ByteBuf 中的位置,包左不包右
public abstract int indexOf(int fromIndex, int toIndex, byte value); 


//查找第一次出现value的位置
public abstract int bytesBefore(byte value); 


//从读索引开始查找length长度,查找value出现的位置,不改变读写索引
public abstract int bytesBefore(int length, byte value); 


//从index开始查找length的长度,查找value出现的位置,不改变读写索引
public abstract int bytesBefore(int index, int length, byte value);


// 遍历 ByteBuf ,进行自定义处理
public abstract int forEachByte(ByteProcessor processor); 
public abstract int forEachByte(int index, int length, ByteProcessor processor);
//倒序遍历处理
public abstract int forEachByteDesc(ByteProcessor processor);
public abstract int forEachByteDesc(int index, int length, ByteProcessor processor);
1.2.4 释放操作
public abstract ByteBuf discardReadBytes(); // 释放已读的字节空间,参考前面的注释里面
public abstract ByteBuf discardSomeReadBytes(); // 释放部分已读的字节空间,可能会释放也可能不释放
public abstract ByteBuf clear(); // 清空字节空间。实际是修改 readerIndex=writerIndex=0,标记清空。
  • discardReadBytes 方法会将剩余的可读部分移动到字节数组的头部,由此腾出空间放在最后面写入,这是一个时间换空间的操作,在AbstractByteBuf的实现如下:
@Override
    public ByteBuf discardReadBytes() {
        //1.校验可访问
        ensureAccessible();
        //2.无废弃段,则直接返回
        if (readerIndex == 0) {
            return this;
        }

        //3.未读取完
        if (readerIndex != writerIndex) {
            //4.将可读段复制到ByteBuf头部空间,由子类实现
            setBytes(0, this, readerIndex, writerIndex - readerIndex);
            //5.写索引减小
            writerIndex -= readerIndex;
            //6.调整标记位
            adjustMarkers(readerIndex);
            //7.读索引重置为 0
            readerIndex = 0;
        } else {
            //8.全部读取完,则调整标记位
            adjustMarkers(readerIndex);
            //9.索引重置
            writerIndex = readerIndex = 0;
        }
        //10.返回当前对象
        return this;
    }
  • 在AbstractByteBuf 中discardSomeReadBytes方法会判断废弃的内容是否超过总空间的一半,超过则回收,反之不会回收
1.2.5 拷贝操作
  • 按照前面类注释的说明,拷贝方法有两类,一类是拷贝后的内容是使用同一份的,相互关联的,但是读写索引是独立的,这种也叫派生buffer,注意这里面有些方法不会自增引用计数,带retained的方法,会自增引用计数
public abstract ByteBuf duplicate(); // 拷贝整个的字节数组。共享,相互影响。
public abstract ByteBuf slice(); // 拷贝可读部分的字节数组。共享,相互影响。
public abstract ByteBuf slice(int index, int length);

public abstract ByteBuf readSlice(int length);
public abstract ByteBuf readRetainedSlice(int length);

public abstract ByteBuf retainedSlice();
public abstract ByteBuf retainedSlice(int index, int length);
public abstract ByteBuf retainedDuplicate();
  • 另一类是内容一致,但是相互独立,主要是copy 方法
public abstract ByteBuf copy(); // 拷贝可读部分的字节数组。独立,互相不影响。
public abstract ByteBuf copy(int index, int length);
1.2.6 其他
  • 另外包括NIO转换方法,在类注释中说明了,主要是:nioBuffer() 和 nioBufferCount() 方法
  • 还有获取内部的字节数组,主要是:array() 方法和 hasArray() 方法,适用于Heap类型的 ByteBuf

二、ByteBuf和ByteBuffer对比

  • 主要对比一下ByteBuf 和NIO 的byteBuffer 之间的区别
1.读和写使用了不同的索引,API更友好,因此也不需要flip 切换读写模式
2.容量可以按需增长,ByteBuffer 不能
3.支持引用计数,因此回收更及时,更灵活
4.支持池化,资源复用,支持零拷贝,提高性能
5.支持方法的链式调用,API更友好
  • ByteBuffer 为什么不能扩容:如下所示其内部的hb数组是final类型的,因此在创建之后不能修改,因此无法扩容
public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer>{

    final byte[] hb;                  // Non-null only for heap buffers
    final int offset;
    boolean isReadOnly;                 // Valid only for heap buffers
  • 后续文章继续分析ByteBuf 的一些特性,比如子类实现、扩容机制、分配方式等;
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值