Netty学习之ByteBuf

Netty学习之ByteBuf

前言

在网络中传输的基本单元是字节byte,虽然在NIO中提供了一个ByteBuffer作为字节的容器,不过由于ByteBuffer比较难使用,所以Netty自己实现了一个,ByteBuf,并且提供了比较灵活的操作方式以及操作工具,本节我们将详细学习这一部分的知识。

ByteBuf

在Netty中,数据通过ByteBuf以及ByteBufHolder来进行操作,ByteBuf具有众多优秀的特性

  • 易于扩展
  • 通过内置的composite buffer类型可以实现zero-copy
  • 根据需要扩展容量,类似于JDK中的(StringBuilder)
  • 自动在读写模式进行切换,相比ByteBuffer需要调用filp()更加方便
  • 采用读写两个指针
  • 支持链式调用
  • 支持引用计数
  • 支持池化技术

工作原理

ByteBuf可以理解为一个字节数组,并且维护两个不同的指针:读指针以及写指针,当从ByteBuf中读取数据(read开头的函数)的时候,读指针增加,当写入数据(write开头的函数)的时候,写指针增加,当读写指针相同时,表示已经没有数据可以读取,继续读取会抛出IndexOutOfBoundException,set以及get开头的函数不会影响指针,默认的最大容量是Integer.MAX_VALUE,当写入超过容量时,会触发异常。

ByteBuf类型

基于堆的ByteBuf,将数据存储在JVM的堆内存中,并且其内部是一个字节数组,但没有进行缓存的时候,可以快速地申请以及回收内存,比较适合处理常规数据。

基于直接内存的ByteBuf,ByteBuf的内存空间是通过直接内存申请的(本地方法调用分配的内存),好处在于可以避免在发生I/O调用的时候,将数据从堆空间拷贝到直接内存中(节省一次拷贝),比较适合于进行网络数据传输,由于数据没有在堆中,所以操作的时候需要先拷贝到堆空间中(需要手动操作),缺点是空间的申请以及回收比较消耗资源,而且不受gc的管理。

上面两个可以通过hasArray()进行区分,基于堆的返回true,基于直接内存的返回false

组合ByteBuf(CompositeByteBuf),提供了多个ByteBuf的聚合视图,可以往其中添加或者删除ByteBuf实例,可能包含上面两种类型的ByteBuf,所以使用的时候需要注意。

获取ByteBuf

通过ByteBufAllocator

Netty通过ByteBufAllocator接口,来提供获取ByteBuf的操作

buffer();
buffer(initCapacity);
buffer(initCapacity, maxCapacity);

heapBuffer();
heapBuffer(initCapacity);
heapBuffer(initCapacity, maxCapacity);

directBuffer();
directBuffer(..);
directBuffer(.., ..);

compositeBuffer();
compositeBuffer(..);
compositeDirectBuffer();
compositeDirectBuffer(..);
compositeHeapBuffer();
compositeHeapBuffer(..);

ioBuffer(); // for i/o in socket

可以从Channel或者ChannelHanderContext中获取ByteBuffAllocator实例

public void testAllocator() {
    NioServerSocketChannel channel = new NioServerSocketChannel();
    ByteBufAllocator alloc = channel.alloc();
    ByteBuf byteBuf = alloc.heapBuffer();
    System.out.println(byteBuf.hasArray());

    ChannelHandlerContext ctx = ...;
    ByteBufAllocator allocator = ctx.alloc();
}

同时,Netty提供了两种ByteBufAllocator的默认实现:PooledByteBufAllocator以及UnpooledByteBufAllocator

// true表示调用buffer()时,使用直接内存,false表示堆内存
ByteBufAllocator allocator = new UnpooledByteBufAllocator(true);
ByteBufAllocator allocator = new PooledByteBufAllocator(true);

true跟false的区别仅在于buffer(),如果是调用heapBuffer(),那还是堆内存,跟true/false无关

通过Unpooled

在有一些情况,我们没有办法获取ByteBuffAllocator,则可以通过Unpooled工具来创建未缓存的ByteBuf实例

buffer(); // 基于堆的ByteBuff
buffer(..); // 同上

directBuffer(); // 同上

wrappedBuffer(); // 包装给定内容
copiedBuffer(); // 拷贝给定内容

ByteBuf操作

随机访问

ByteBuf支持类似于数组的访问形式,并且其下标从0开始,最后一个Byte为capacity() - 1

ByteBuf buffer = Unpooled.copiedBuffer("hello world".getBytes());
for (int i = 0; i < buffer.capacity(); i++) {
    System.out.print((char)buffer.getByte(i));
}

连续访问

ByteBuf由三个部分组成,如下图所示

+----------+------------+----------------+
|          |            |                |
| 已经读取  |  可以读取   |   可以写入     |
|          |            |                |
+----------+------------+----------------+
0------readerIndex---writeIndex-----capacity

其中已经读取的数据不可能再被读取(指的是当读指针已经移动后,读指针之前的数据),读指针与写指针之间的数据则可以被继续读取,写指针与容量之间的空间可以继续写入。

当调用ByteBuf#discardReadByytes()后,后面的数据会移动到前面,使得读指针归为0,注意该操作会比较消耗资源,一般只在内存资源比较紧张的时候才进行该操作。

移动指针

在ByteBuf中,存在读写指针,所以可以根据需要移动指针,当调用read开头的函数时,读指针会移动,write开头的函数时,write指针会移动,同时,可以调用readIndex(int)将读指针设置到指定位置,超过可读位置会抛出异常,writeIndex(int)同理。

可以调用clear()将读写指针均设置为0,该操作并没有清空内容,也即数据依旧可以读取出来,该操作比discardReadBytes()消耗更低,因为只是重置了指针。

搜索操作

用于查找某个字节的下标,可以使用indexOf(),也可以使用复杂的操作ByteBufProcessor#process(byte value),同时ByteBufProcessor定义了一系列公用操作,如ByteBufProcessor.FIND_CRByteBufProcessor.FIND_LF

衍生操作

衍生操作提供了一些可以从当前ByteBuf中获取ByteBuf的操作,其底层公用一个ByteBuff,但是具有自己的读写指针

public void testDerived() {
    ByteBuf buffer = Unpooled.copiedBuffer("hello world".getBytes());
    // 影子拷贝,底层其实是同一个
    ByteBuf duplicate = buffer.duplicate();
    buffer.setByte(0, 'a');
    // a
    System.out.println((char)duplicate.getByte(0));

    // 影子切片,可以带参数
    ByteBuf slice = buffer.slice();
    buffer.setByte(0, 'a');
    // a
    System.out.println((char) slice.getByte(0));

    ByteBuf byteBuf = buffer.readSlice(buffer.readableBytes());
    for (int i = 0; i < byteBuf.capacity(); i++) {
        System.out.print((char) byteBuf.getByte(i));
    }
}

如果是要拷贝数据,则应该使用copy()或者copy(int, int)操作

ByteBufUtils

在Netty中,同时还提供了ByteBufUtils工具类来操作ByteBuf,如hexDump()可以用于打印ByteBuf的内容,更多关于ByteBufUtils,可以参考API即可。

引用计数

Netty为ByteBuf以及ByteBufHolder引入了引用计数,两者均实现了ReferenceCounted接口,可以用于提高性能。

当引用计数的值大于0的时候,对应的资源不会被释放,当计数值等于0时,资源会被释放掉。

通常来说,最后一个使用资源的对象需要释放掉该资源,即调用其release()方法

总结

本小节主要详细学习了Netty中的数据容器,ByteBuf,在Netty中,所有的数据都是存放在ByteBuf中,所以,对Netty中数据的操作,其实就是对ByteBuf的操作,ByteBuf有三种不同的类型,基于堆的,基于直接内存的,组合类型的,在使用的时候需要根据情况选择合适的容器,同时,为了提高性能,Netty中引入了引用计数的概念,所以,当资源不需要使用的时候,需要显示释放掉对应的资源。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,825评论 4 377
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,887评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,425评论 0 255
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,801评论 0 224
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,252评论 3 299
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,089评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,216评论 2 322
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 31,005评论 0 215
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,747评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,883评论 2 255
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,354评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,694评论 3 265
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,406评论 3 246
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,222评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,996评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,242评论 2 287
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 36,017评论 2 281

推荐阅读更多精彩内容

  • 翻出了之前临摹的画,还记得当时画的时候很带劲,感觉勾线也挺有意思,当时还觉得很满意,现在再看一看发现了很多不足之处...
    世上没有秘密阅读 2,187评论 4 4
  • 昨天Macron上台了,今天二战纪念日,法国放假一天。我继续倒时差,顺便把家里收拾了,洗个澡,给身体和头发做了护理...
    voutetoilee阅读 94评论 0 0
  • 大学里,好多人的英语,都在走下坡路,你坚持学习,就在走上坡路,总有一天,你会跟正常水平汇合,甚至达到更高的水平。
    黑板是吸收知识的地方阅读 226评论 0 0
  • 第一次写空瓶记录,也不知道咋地,想分享一下,希望可以帮助到爱美的你们。 坐标欧洲,皮肤在国内是混合油,但是欧洲太干...
    我是青蛙我怕啥阅读 371评论 0 1
  • 一:统计要从反应全面情况的统计数字着手。 1:统计分析要从分析统计数字着手。 2:统计分析自始至终都要根据统计数字...
    花上阅读 968评论 0 3