Netty源码分析——ByteBuf

目录

1、ByteBuffer介绍

2、ByteBuffer缺点

3、ByteBuf介绍

4、ByteBuf常用API

5、ByteBuf转换成标准的ByteBuffer

6、ByteBuf源码分析

7、最简单的将接收到的消息打印出来



Netty 里面数据读写是以 ByteBuf 为单位进行交互的,ByteBuf是Byte数组的缓冲区,像java里的ByteBuffer

1、ByteBuffer介绍

ByteBuffer只有一个指针position,读写的时候需要手动调用flip()和rewind()等,否则可能导致程序读写错误。

flip是把limit设为当前position,把position设为0,这样就可以从buffer开头,对该buffer进行读取了。一般在从Buffer读出数据前调用。

clear是把position设为0,把limit设为capacity,一般在把数据写入Buffer前调用。

rewind是把position设为0,limit不变,一般在把数据重写入Buffer前调用。

典型用法如下:

public class ByteBufferTest {
    public static void main(String[] args) {
        ByteBuffer buffer = ByteBuffer.allocate(88);
        String value = "ByteBuffer学习";
        buffer.put(value.getBytes());
        System.out.println("position = " + buffer.position());
        System.out.println("limit = " + buffer.limit());
        System.out.println("remaining = " + buffer.remaining());
        buffer.flip();
        System.out.println("position = " + buffer.position());
        System.out.println("limit = " + buffer.limit());
        System.out.println("remaining = " + buffer.remaining());
        byte[] dst = new byte[buffer.remaining()];
        buffer.get(dst);
        System.out.println(new String(dst));
    }
}

输出:

position = 16
limit = 88
remaining = 72
position = 0
limit = 16
remaining = 16
ByteBuffer学习

可以看出调用flip操作前后的对比:

2、ByteBuffer缺点

  1. 长度固定,一旦分配完成,容量不能动态扩展收缩,当需要编码的对象大于ByteBuffer的容量时,就会发生索引越界异常
  2. 只有一个标识位置的指针position,读写的时候需要手动调用flip和rewind等
  3. api功能有限,不支持高级特性

3、ByteBuf介绍

ByteBuf有两个指针,读指针(readerIndex)、写指针(writerIndex),然后还有一个变量 capacity,表示 ByteBuf 底层内存的总容量。同时还有个maxCapacity,当bytebuf的capcity不足时的最大扩容容量。

刚开始readerIndex和writerIndex的位置都是0,随着数据的读取,readerIndex会增加,但是不会超过writerIndex,ByteBuf 里面总共有 writerIndex-readerIndex 个字节可读,由此可以推论出当 readerIndex 与 writerIndex 相等的时候,ByteBuf 不可读。

在读取之后,0~readerIndex之间的数据就是discard已丢弃的,调用discardReadBytes可以释放这部分空间。

写数据是从 writerIndex 指向的部分开始写,每写一个字节,writerIndex 自增1,直到增到 capacity,这个时候,表示 ByteBuf 已经不可写了,ByteBuf 里面其实还有一个参数 maxCapacity,当向 ByteBuf 写数据的时候,如果容量不足,那么这个时候可以进行扩容,直到 capacity 扩容到 maxCapacity,超过 maxCapacity 就会报错

两个指针把bytebuf分成了三部分,

  • 第一个部分是已经丢弃的字节,这部分数据是无效的;
  • 第二部分是可读字节,这部分数据是 ByteBuf 的主体数据, 从 ByteBuf 里面读取的数据都来自这一部分;
  • 最后一部分的数据是可写字节,所有写到 ByteBuf 的数据都会写到这一段。最后一部分虚线表示的是该 ByteBuf 最多还能扩容多少容量

初始分配的bytebuf如①

写入N个字节后如②

读取M(< N)个字节后如③

调用discardReadBytes后如④

调用clear后恢复如①

4、ByteBuf常用API

 

  • readableBytes() 与 isReadable()

readableBytes() 表示 ByteBuf 当前可读的字节数,它的值等于 writerIndex-readerIndex,如果两者相等,则不可读,isReadable() 方法返回 false

  • writableBytes()、 isWritable() 与 maxWritableBytes()

writableBytes() 表示 ByteBuf 当前可写的字节数,它的值等于 capacity-writerIndex,如果两者相等,则表示不可写,isWritable() 返回 false,但是这个时候,并不代表不能往 ByteBuf 中写数据了, 如果发现往 ByteBuf 中写数据写不进去的话,Netty 会自动扩容 ByteBuf,直到扩容到底层的内存大小为 maxCapacity,而 maxWritableBytes() 就表示可写的最大字节数,它的值等于 maxCapacity-writerIndex

  • writeBytes(byte[] src) 与 buffer.readBytes(byte[] dst)

writeBytes() 表示把字节数组 src 里面的数据全部写到 ByteBuf,而 readBytes() 指的是把 ByteBuf 里面的数据全部读取到 dst

  • writeByte(byte b) 与 buffer.readByte()

writeByte() 表示往 ByteBuf 中写一个字节,而 buffer.readByte() 表示从 ByteBuf 中读取一个字节

  • slice和duplicate

slice() 方法从原始 ByteBuf 中截取一段,这段数据是从 readerIndex 到 writeIndex,同时,返回的新的 ByteBuf 的最大容量 maxCapacity 为原始 ByteBuf 的 readableBytes();duplicate() 方法把整个 ByteBuf 都截取出来,包括所有的数据,指针信息

slice() 方法与 duplicate() 方法的相同点是:底层内存以及引用计数与原始的 ByteBuf 共享,也就是说经过 slice() 或者 duplicate() 返回的 ByteBuf 调用 write 系列方法都会影响到 原始的 ByteBuf,但是它们都维持着与原始 ByteBuf 相同的内存引用计数和不同的读写指针.

 

5、ByteBuf转换成标准的ByteBuffer

由于通过NIO的SocketChannel进行网络读写时,操作的对象是jdk标准的ByteBuffer,所以看下ByteBuf如何转换成ByteBuffer

(1)nioBuffer():将当前的ByteBuf可读的缓冲区转换成ByteBuffer,对ByteBuffer的读写操作不会影响原ByteBuf的读写索引。

(2)  nioBuffer(int index, int length):将当前的ByteBuf从index开始长度为length的缓冲区转换成ByteBuffer,对ByteBuffer的读写操作不会影响原ByteBuf的读写索引。

6、ByteBuf源码分析

堆内存字节缓冲区(HeapByteBuf):UnPoolHeapByteBuf、PooledHeapByteBuf
它的特点是内存的分配和回收都在堆,所以速度很快;缺点就是进行Socket的IO读写,需要把堆内存对应的缓冲区复制到内核Channel中,这内存复制会影响性能
直接内存缓冲区(DirectByteBuf):UnPoolDirectByteBuf、UnPoolUnsafeDirectByteBuf、PoolDirectByteBuf、PoolUnsafeDirectByteBuf它的特点是由于内存的分配在非堆(方法区),不需要内存复制,所以IO读取的速度较快,但是内存的分配较慢

当使用DirectByteBuf可以实现零拷贝
总结:
根据两种内存的特点,我们可以知道,IO读写时最好使用DirectByteBuf,而在后端业务消息的解编码最好使用HeapByteBuf

7、最简单的将接收到的消息打印出来

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg){
        //没用指定decoder和encoder,就用byteBuf来解析
        ByteBuf in = (ByteBuf) msg;
        try {
            while (in.isReadable()) {
                System.out.print((char) in.readByte());
            }
        } finally {
            ((ByteBuf) msg).release();
        }
    }

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值