其他分享
首页 > 其他分享> > 5. ByteBuf

5. ByteBuf

作者:互联网

第5章 ByteBuf

Netty提供的ByteBuf与JDK的ByteBuffer相比,前者具有卓越的功能性和灵活性。

5.1 ByteBuf的API

ByteBuf提供读访问索引(readerIndex)和写访问索引(writerIndex)来控制字节数组。ByteBuf API具有以下优点:

5.2 ByteBuf类 ----- Netty的数据容器

5.2.1 ByteBuf如何工作的

ByteBuf维护两个不同的索引: 读索引(readerIndex)和写索引(writerIndex)。如下图:
图5-1

5.2.2 ByteBuf的使用模式

ByteBuf本质是: 一个由不同的索引分别控制读访问和写访问的字节数组。
请记住这句话。ByteBuf共有三种模式: 堆缓冲区模式(Heap Buffer)、直接缓冲区模式(Direct Buffer)和复合缓冲区模式(Composite Buffer)

1. 堆缓冲区模式(Heap Buffer)

堆缓冲区模式又称为:支撑数组(backing array)。将数据存放在JVM的堆空间,通过将数据存储在数组中实现
能在没有使用池化的情况下提供快速的分配和释放。

代码:
/**
     * 代码清单 5-1 堆缓冲区
     */
    public static void heapBuffer() {
        // 创建Java堆缓冲区
        ByteBuf heapBuf = Unpooled.buffer();
        //检查ByteBuf是否有一个支撑数组
        //当 hasArray()方法返回 false 时,尝试访问支撑数组将触发一个 Unsupported OperationException。这个模式类似于 JDK 的 ByteBuffer 的用法。
        if (heapBuf.hasArray()) {
            byte[] array = heapBuf.array();
            //计算第一个字节的偏移量
            int offset = heapBuf.arrayOffset() + heapBuf.readerIndex();
            int length = heapBuf.readableBytes();
            //使用数组、偏移量和长度作为参数调用你的方法
            handleArray(array, offset, length);
        }
    }
2. 直接缓冲区模式(Direct Buffer)

Direct Buffer属于堆外分配的直接内存,不会占用堆的容量。适用于套接字传输过程,避免了数据从内部缓冲区拷贝到直接缓冲区的过程,性能较好

代码:
public static void directBuffer() {
    ByteBuf directBuf = Unpooled.directBuffer();
    if (!directBuf.hasArray()) {
        int length = directBuf.readableBytes();
        byte[] array = new byte[length];
        directBuf.getBytes(directBuf.readerIndex(), array);
        handleArray(array, 0, length);
    }
}
3. 复合缓冲区模式(Composite Buffer)

Composite Buffer是Netty特有的缓冲区。本质上类似于提供一个或多个ByteBuf的组合视图,可以根据需要添加和删除不同类型的ByteBuf。

警告 CompositeByteBuf 中的 ByteBuf 实例可能同时包含直接内存分配和非直接内存分配。
如果其中只有一个实例,那么对 CompositeByteBuf 上的 hasArray()方法的调用将返回该组
件上的 hasArray()方法的值;否则它将返回 false。

图5-2

下面代码使用 JDK 的 ByteBuffer 来实现这一需求。
创建了一个包含两个 ByteBuffer 的数组用来保存这些消息组件,
同时创建了第三个 ByteBuffer 用来保存所有这些数据的副本。

// Use an array to hold the message parts
ByteBuffer[] message = new ByteBuffer[] { header, body };
// Create a new ByteBuffer and use copy to merge the header and body
ByteBuffer message2 =
ByteBuffer.allocate(header.remaining() + body.remaining());
message2.put(header);
message2.put(body);
message2.flip();

分配和复制操作,以及伴随着对数组管理的需要,使得这个版本的实现效率低下而且笨拙。
下面展示了一个使用了 CompositeByteBuf 的版本。

代码:
public static void byteBufComposite() {
	// 复合缓冲区,只是提供一个视图
    CompositeByteBuf messageBuf = Unpooled.compositeBuffer();
    ByteBuf headerBuf = Unpooled.buffer(); // can be backing or direct
    ByteBuf bodyBuf = Unpooled.directBuffer();   // can be backing or direct
    messageBuf.addComponents(headerBuf, bodyBuf);
    messageBuf.removeComponent(0); // remove the header
    for (ByteBuf buf : messageBuf) {
        System.out.println(buf.toString());
    }
}

CompositeByteBuf 可能不支持访问其支撑数组,因此访问 CompositeByteBuf 中的数据类似于(访问)直接缓冲区的模式,如下

CompositeByteBuf compBuf = Unpooled.compositeBuffer();
int length = compBuf.readableBytes();
byte[] array = new byte[length];
compBuf.getBytes(compBuf.readerIndex(), array);
handleArray(array, 0, array.length);

Netty使用了CompositeByteBuf来优化套接字的I/O操作,尽可能地消除了
由JDK的缓冲区实现所导致的性能以及内存使用率的惩罚

5.3 字节级操作

5.3.1 随机访问索引

ByteBuf的索引与普通的Java字节数组一样。第一个字节的索引是0,最后一个字节索引总是capacity()-1。请记住下列两条,非常有用:

代码:
public static void byteBufRelativeAccess() {
    ByteBuf buffer = Unpooled.buffer(); //get reference form somewhere
    for (int i = 0; i < buffer.capacity(); i++) {
        byte b = buffer.getByte(i);// 不改变readerIndex值
        System.out.println((char) b);
    }
}

5.3.2 顺序访问索引

Netty的ByteBuf同时具有读索引和写索引,但JDK的ByteBuffer只有一个索引,所以JDK需要调用flip()方法在读模式和写模式之间切换。

5.3.3 可丢弃字节区域

可丢弃字节区域是指:[0,readerIndex)之间的区域。可调用discardReadBytes()方法丢弃已经读过的字节。

5.3.4 可读字节区域

可读字节区域是指:[readerIndex, writerIndex)之间的区域。任何名称以read和skip开头的操作方法,都会改变readerIndex索引。

5.3.5 可写字节区域

可写字节区域是指:[writerIndex, capacity)之间的区域。任何名称以write开头的操作方法都将改变writerIndex的值。

5.3.6 索引管理

5.3.7 查找操作(indexOf)

查找ByteBuf指定的值。类似于,String.indexOf("str")操作

代码:
public static void byteProcessor() {
    ByteBuf buffer = Unpooled.buffer(); //get reference form somewhere
    // 使用indexOf()方法来查找
    buffer.indexOf(buffer.readerIndex(), buffer.writerIndex(), (byte)8);
    // 使用ByteProcessor查找给定的值
    int index = buffer.forEachByte(ByteProcessor.FIND_CR);
}

5.3.8 派生缓冲区 ----- 视图

派生缓冲区为ByteBuf提供了一个访问的视图。视图仅仅提供一种访问操作,不做任何拷贝操作。下列方法,都会呈现给使用者一个视图,以供访问:

理解
代码:
public static void byteBufSlice() {
    Charset utf8 = Charset.forName("UTF-8");
    ByteBuf buf = Unpooled.copiedBuffer("Netty in Action rocks!", utf8);
    ByteBuf sliced = buf.slice(0, 15);
    System.out.println(sliced.toString(utf8));
    buf.setByte(0, (byte)'J');
    assert buf.getByte(0) == sliced.getByte(0); // return true
}

public static void byteBufCopy() {
    Charset utf8 = Charset.forName("UTF-8");
    ByteBuf buf = Unpooled.copiedBuffer("Netty in Action rocks!", utf8);
    ByteBuf copy = buf.copy(0, 15);
    System.out.println(copy.toString(utf8));
    buf.setByte(0, (byte)'J');
    assert buf.getByte(0) != copy.getByte(0); // return true
}

5.3.9 读/写操作

如上文所提到的,有两种类别的读/写操作:

5.3.10 更多的操作


下面的两个方法操作字面意思较难理解,给出解释:

5.4 ByteBufHolder接口

ByteBufHolder为Netty的高级特性提供了支持,如缓冲区池化,可以从池中借用ByteBuf,并且在需要时自动释放。

代码
public class CustomByteBufHolder extends DefaultByteBufHolder{

    private String protocolName;

    public CustomByteBufHolder(String protocolName, ByteBuf data) {
        super(data);
        this.protocolName = protocolName;
    }

    @Override
    public CustomByteBufHolder replace(ByteBuf data) {
        return new CustomByteBufHolder(protocolName, data);
    }

    @Override
    public CustomByteBufHolder retain() {
        super.retain();
        return this;
    }

    @Override
    public CustomByteBufHolder touch() {
        super.touch();
        return this;
    }

    @Override
    public CustomByteBufHolder touch(Object hint) {
        super.touch(hint);
        return this;
    }
    ...
}

5.5 ByteBuf分配

创建和管理ByteBuf实例的多种方式:按需分配(ByteBufAllocator)、Unpooled缓冲区和ByteBufUtil类

5.5.1 按序分配: ByteBufAllocator接口

Netty通过接口ByteBufAllocator实现了(ByteBuf的)池化。Netty提供池化和非池化的ButeBufAllocator:

解释:

5.5.2 Unpooled缓冲区 ----- 非池化

Unpooled提供静态的辅助方法来创建未池化的ByteBuf。

注意:
创建ByteBuf代码:
 public void createByteBuf(ChannelHandlerContext ctx) {
    // 1. 通过Channel创建ByteBuf
    ByteBuf buf1 = ctx.channel().alloc().buffer();
    // 2. 通过ByteBufAllocator.DEFAULT创建
    ByteBuf buf2 =  ByteBufAllocator.DEFAULT.buffer();
    // 3. 通过Unpooled创建
    ByteBuf buf3 = Unpooled.buffer();
}

5.5.3 ByteBufUtil类

ByteBufUtil类提供了用于操作ByteBuf的静态的辅助方法: hexdump()和equals

5.6 引用计数

Netty4.0版本中为ButeBuf和ButeBufHolder引入了引用计数技术。请区别引用计数和可达性分析算法(jvm垃圾回收)

代码:
public static void releaseReferenceCountedObject(){
    ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer();
    // 引用计数加1
    buffer.retain();
    // 输出引用计数
    buffer.refCnt();
    // 引用计数减1
    buffer.release();
}

5.7 建议

标签:索引,buffer,readerIndex,-----,ByteBuf,缓冲区
来源: https://www.cnblogs.com/qianingmeng/p/15911325.html