一、概述和原理

网络数据传输的基本单位总是字节,Netty 提供了 ByteBuf 作为它的字节容器,既解决了 JDK API 的局限性,又为网络应用程序提供了更好的 API,ByteBuf 的优点:

1、可以被用户自定义的缓冲区类型扩展
2、通过内置的复合缓冲区类型实现了透明的零拷贝
3、容量可以按需增长
4、在读和写这两种模式之间切换不需要调用 ByteBuffer 的 flip()方法
5、读和写使用了不同的索引
6、支持方法的链式调用
7、支持引用计数
8、支持池化

ByteBuf通过两个索引(readerIndex、writerIndex)划分为三个区域:

1、任何名称以 read 或者 skip 开头的操作都将检索或者跳过位于当前readerIndex 的数据,并且将它增加已读字节数;任何名称以 write 开头的操作都将从当前的 writerIndex 处开始写数据,并将它增加已经写入的字节数。readerIndex 不能超过 writerIndex。

2、如果被调用的方法需要一个 ByteBuf 参数作为读取的目标,并且没有指定目标索引参数,那么该目标缓冲区的 writerIndex 也将被增加;如果写操作的目标也是 ByteBuf,并且没有指定源索引的值,则源缓冲区的 readerIndex 也同样会被增加相同的大小。

3、如果尝试在缓冲区的可读字节数已经耗尽时从中读取数据, 那么将会引发一个IndexOutOfBoundsException;如果尝试往目标写入超过目标容量的数据,将检查当前的写索引以及最大容量是否可以在扩展后容纳该数据,可以的话就会分配并调整容量,否则将会引发一个IndexOutOfBoundException。

4、通过调用 discardReadBytes()方法, 可以丢弃已读字节并回收空间。但不建议频繁的调用discardReadBytes(),因为将可能导致内存复制:

5、通过调用 clear()方法来将 readerIndex 和 writerIndex 都设置为 0。 注意,调用 clear()比调用 discardReadBytes()轻量得多, 因为它将只是重置索引而不会复制任何的内存。

二、ByteBuf 分配

ByteBuf 是一个抽象类,在程序中直接new ByteBuf() 是不现实的啦!毕竟还要实现一堆的方法,并且缺乏池化的管理。那么,在 Netty 中要怎么得到 ByteBuf 呢?

有两种方法可以得到 ByteBuf 实例,一种是 ByteBufAllocator (实现了池化,有效的降低了分配和释放内存的开销),另一种是 Unpooled (Netty 提供的工具类来创建未池化的ByteBuf 实例)。
    ByteBufAllocator:Netty 提供了两种 ByteBufAllocator 的实现,PooledByteBufAllocator(默认使用)和UnpooledByteBufAllocator。前者池化了ByteBuf的实例以提高性能并最大限度地减少内存碎片;后者的实现不池化ByteBuf实例, 并且在每次它被调用时都会返回一个新的实例。

创建 ButeBuf 实例的代码如下:

  1. public void createBuf() {
  2. //获得ByteBufAllocator 的两种方式
  3. //1、
  4. Channel channel = null;
  5. ByteBufAllocator alloc = channel.alloc();
  6. //2、
  7. ChannelHandlerContext channelHandlerContext = null;
  8. ByteBufAllocator alloc1 = channelHandlerContext.alloc();

  9. //ByteBufAllocator 创建 ByteBuf 实例
  10. //1、返回一个基于堆或者直接内存存储的ByteBuf
  11. ByteBuf byteBuf = alloc.buffer(256, Integer.MAX_VALUE);
  12. //2、返回一个基于堆内存存储的 ByteBuf 实例
  13. ByteBuf byteBuf1 = alloc.heapBuffer(256);
  14. byteBuf1.refCnt();//检查该ByteBuf的引用计数
  15. byteBuf1.release();//将ByteBuf的引用计数设为0并释放
  16. //3、返回一个基于直接内存存储的 ByteBuf
  17. ByteBuf byteBuf2 = alloc.directBuffer();
  18. //4、返回一个可以通过添加最大到指定数目的基于堆的或者直接内存存储的缓冲区来扩展的 CompositeByteBuf
  19. CompositeByteBuf compositeByteBuf = alloc.compositeBuffer();
  20. CompositeByteBuf compositeByteBuf1 = alloc.compositeHeapBuffer(16);
  21. CompositeByteBuf compositeByteBuf2 = alloc.compositeDirectBuffer(16);
  22. //5、返回一个用于套接字的 I/O 操作的ByteBuf
  23. ByteBuf byteBuf3 = alloc.ioBuffer();
  24.  
  25. //Unpooled 创建 ByteBuf 实例
  26. //1、创建一个未池化的基于堆内存存储的 ByteBuf 实例
  27. ByteBuf buf = Unpooled.buffer();
  28. //2、创建一个未池化的基于内存存储的ByteBuf
  29. ByteBuf buf1 = Unpooled.directBuffer(256, Integer.MAX_VALUE);
  30. //3、返回一个包装了给定数据的 ByteBuf
  31. Unpooled.wrappedBuffer("Hello Netty".getBytes());
  32. //4、返回一个复制了给定数据的 ByteBuf
  33. Unpooled.copiedBuffer("Hello Netty",CharsetUtil.UTF_8);
  34. }

三、ByteBuf 操作

涉及到 ByteBuf 的操作主要是两个方面,一个是 ByteBuf 的读/写,另一个是 ByteBuf 的复制分片操作。

ByteBuf 的读/写操作操作有两种类别:get() 和 set() 操作,从给定的索引开始,并且保持索引不变,也就是说get() 和 set() 操作并不会改变 readerIndex 和 writerIndex 的值;read()和 write()操作, 从给定的索引开始,并且会根据已经访问过的字节数对索引进行调整,比如 read() 操作 readerIndex 会根据读取的数据类型(byte 1个字节,short 2个字节,int 4个字节,long 8个字节)增加对应的索引数。

ByteBuf 提供了专门的方式来实现复制分片操作:

duplicate();
slice();
slice(int, int);
Unpooled.unmodifiableBuffer(…);
order(ByteOrder);
readSlice(int)。

这些方法都将返回一个新的 ByteBuf 实例,它具有自己的读索引、写索引和标记索引。其内部存储和 JDK 的 ByteBuffer 一样也是共享的。这意味着,如果你修改了它的内容,也同时修改了其对应ByteBuf的源实例。

如果需要一个现有缓冲区的真实副本,请使用 copy()或者 copy(int, int)方法。不同于派生缓冲区,由这个调用所返回的 ByteBuf 拥有独立的数据副本。

现在我们具体来看看 ByteBuf 的操作:

1、数据遍历

  1. for (int i = 0; i < byteBuf.capacity(); i++) {
  2. byte aByte = byteBuf.getByte(i);
  3. System.out.print((char) aByte);
  4. }
  5.  
  6. while (byteBuf.isReadable()){
  7. System.out.print((char) byteBuf.readByte());
  8. }

2、写入数据

  1. while (byteBuf.writableBytes() >= 4){
  2. byteBuf.writeByte(65);
  3. }

3、索引标记管理

  1. ByteBuf buf = byteBuf.readerIndex(0);//将 readerIndex 移动到指定的位置
  2. buf.markReaderIndex();//标记当前的 readerIndex
  3. while (buf.isReadable()){
  4. System.out.print((char) buf.readByte());
  5. }
  6. buf.resetReaderIndex();//回退到之前标记的 readerIndex
  7. while (buf.isReadable()){
  8. System.out.print((char) buf.readByte());
  9. }
  10.  
  11. int index = byteBuf.indexOf(0, byteBuf.capacity() - 1, (byte) 65);//在某个范围内查找某个字节的索引

4、分片操作

  1. ByteBuf slice = byteBuf.slice(0, 15);
  2. System.out.print(slice.toString(CharsetUtil.UTF_8));
  3. //更新索引0处的字节
  4. slice.setByte(0, (byte) 'J');
  5. byte aByte = byteBuf.getByte(0);
  6. System.out.print("\r\n" + (char)aByte);

5、复制操作

  1. ByteBuf copy = buf.copy(0, 15);
  2. System.out.println(copy.toString(CharsetUtil.UTF_8));
  3. copy.setByte(0, (byte) 'A');
  4. System.out.println((char) byteBuf.getByte(0));

6、其他操作

  1. System.out.println("如果至少有一个字节可读取:" + byteBuf.isReadable());
  2. System.out.println("如果至少有一个字节可写入:" + byteBuf.isWritable());
  3. System.out.println("返回可被读取的字节数:" + byteBuf.readableBytes());
  4. System.out.println("返回可被写入的字节数:" + byteBuf.writableBytes());
  5. System.out.println("可容纳的字节数:" + byteBuf.capacity() + ",可扩展最大的字节数:" + byteBuf.maxCapacity());
  6. System.out.println("是否由一个字节数组支撑:" + byteBuf.hasArray());
  7. System.out.println("由一个字节数组支撑则返回该数组:" + byteBuf.array().length);
  8. System.out.println("计算第一个字节的偏移量:" + byteBuf.arrayOffset());
  9. System.out.println("返回Bytebuf的十六进制:" + ByteBufUtil.hexDump(byteBuf.array()));

四、补充

ByteBuf 的使用模式 :

1、堆缓冲区:最常用的 ByteBuf 模式是将数据存储在 JVM 的堆空间中。 这种模式被称为支撑数组(backing array), 它能在没有使用池化的情况下提供快速的分配和释放。

2、直接缓冲区:将数据驻留在会被垃圾回收的堆之外,直接缓冲区对于网络数据传输是最理想的选择,不过,相对于基于堆的缓冲区,它们的分配和释放都较为昂贵。另外,如果你的数据包含在一个在堆上分配的缓冲区中, 那么事实上,在通过套接字发送它之前, JVM将会在内部把你的缓冲区复制到一个直接缓冲区中。经验表明,Bytebuf的最佳实践是在IO通信线程的读写缓冲区使用DirectByteBuf,后端业务使用HeapByteBuf。

3、复合缓冲区:为多个 ByteBuf 提供一个聚合视图。 在这里你可以根据需要添加或者删除 ByteBuf 实例。

Netty 通过一个 ByteBuf 子类——CompositeByteBuf——实现了这个模式, 它提供了一个将多个缓冲区表示为单个合并缓冲区的虚拟表示。

使用 CompositeByteBuf 的复合缓冲区模式:

  1. public void CompositeBuffer() {
  2. CompositeByteBuf messageBuf = Unpooled.compositeBuffer();
  3. ByteBuf headBuf = Unpooled.copiedBuffer("Hello,", CharsetUtil.UTF_8);
  4. ByteBuf bodyBuf = Unpooled.copiedBuffer("Netty!", CharsetUtil.UTF_8);
  5. //将 ByteBuf 实例追加到 CompositeByteBuf
  6. messageBuf.addComponents(headBuf, bodyBuf);
  7. Iterator<ByteBuf> it = messageBuf.iterator();
  8. //访问CompositeByteBuf数据
  9. while(it.hasNext()){
  10. ByteBuf buf = it.next();
  11. while (buf.isReadable()){
  12. System.out.print((char) buf.readByte());
  13. }
  14. }
  15. //使用数组访问数据
  16. if(!messageBuf.hasArray()){
  17. int len = messageBuf.readableBytes();
  18. byte[] arr = new byte[len];
  19. messageBuf.getBytes(0, arr);
  20. for (byte b : arr){
  21. System.out.print((char)b);
  22. }
  23. }
  24. messageBuf.removeComponent(0); //删除位于索引位置为 0(第一个组件)的 ByteBuf
  25. }

参考资料:《Netty IN ACTION》

演示源代码:https://github.com/JMCuixy/NettyDemo/blob/master/src/main/java/org/netty/demo/bytebuf/ByteBufOperation.java

Netty 系列三(ByteBuf).的更多相关文章

  1. Netty 系列(三)Netty 入门

    Netty 系列(三)Netty 入门 Netty 是一个提供异步事件驱动的网络应用框架,用以快速开发高性能.高可靠性的网络服务器和客户端程序.更多请参考:Netty Github 和 Netty中文 ...

  2. Netty 系列之 Netty 高性能之道 高性能的三个主题 Netty使得开发者能够轻松地接受大量打开的套接字 Java 序列化

    Netty系列之Netty高性能之道 https://www.infoq.cn/article/netty-high-performance 李林锋 2014 年 5 月 29 日 话题:性能调优语言 ...

  3. JAVA通信系列三:Netty入门总结

    一.Netty学习资料 书籍<Netty In Action中文版> 对于Netty的十一个疑问http://news.cnblogs.com/n/205413/ 深入浅出Nettyhtt ...

  4. netty系列之:netty中的ByteBuf详解

    目录 简介 ByteBuf详解 创建一个Buff 随机访问Buff 序列读写 搜索 其他衍生buffer方法 和现有JDK类型的转换 总结 简介 netty中用于进行信息承载和交流的类叫做ByteBu ...

  5. netty系列之:不用怀疑,netty中的ByteBuf就是比JAVA中的好用

    目录 简介 ByteBuf和ByteBuffer的可扩展性 不同的使用方法 性能上的不同 总结 简介 netty作为一个优秀的的NIO框架,被广泛应用于各种服务器和框架中.同样是NIO,netty所依 ...

  6. Netty 系列目录

    Netty 系列目录 二 Netty 源码分析(4.1.20) 1.1 Netty 源码(一)Netty 组件简介 2.1 Netty 源码(一)服务端启动 2.2 Netty 源码(二)客户端启动 ...

  7. Netty系列(四)TCP拆包和粘包

    Netty系列(四)TCP拆包和粘包 一.拆包和粘包问题 (1) 一个小的Socket Buffer问题 在基于流的传输里比如 TCP/IP,接收到的数据会先被存储到一个 socket 接收缓冲里.不 ...

  8. 【读后感】Netty 系列之 Netty 高性能之道 - 相比 Mina 怎样 ?

    [读后感]Netty 系列之 Netty 高性能之道 - 相比 Mina 怎样 ? 太阳火神的漂亮人生 (http://blog.csdn.net/opengl_es) 本文遵循"署名-非商 ...

  9. netty系列之:netty架构概述

    目录 简介 netty架构图 丰富的Buffer数据机构 零拷贝 统一的API 事件驱动 其他优秀的特性 总结 简介 Netty为什么这么优秀,它在JDK本身的NIO基础上又做了什么改进呢?它的架构和 ...

随机推荐

  1. yield(),wait(),sleep(),join()

    yield(),wait(),sleep(),join()yield()虽然可以让线程由“运行状态”进入到“就绪状态”:但是,它不一定会让其它线程获取CPU执行权(即,其它线程进入到“运行状态”),即 ...

  2. BI项目记(二):给我接套数据

    这次故事的主角还是小D,小D工作在一家传统公司的信息部门,负责数据仓库系统的运维和开发. 话说有一天,小D被教导老板的office,老板给布置了一个任务,让小D在现有数据仓库里接入刚上线的两个系统的数 ...

  3. 福州首届.NET开源社区技术交流会圆满成功

    活动总结 2018年11月10日周六的下午,在福州蒲公英创新工场举办了福州首届.NET开源社区技术交流会,来自福建省各大科技公司的技术小伙伴齐聚一堂,为了就是能在现场学习到微软跨平台技术.NET Co ...

  4. Hadoop 多表关联

    一.实例描述 多表关联和单表关联类似,它也是通过对原始数据进行一定的处理,从其中挖掘出关心的信息.下面进入这个实例. 输入是两个文件,一个代表工厂表,包含工厂名列和地址编号列:另一个代表地址列,包含地 ...

  5. 第43节:Java学前要点

    Java学前要点 01 学习Java,有人推荐去培训,有人说没用,其实有钱的,不知道如何学,或者逼不得已去的就可以,也有人自己为了不花这些钱,而选择自学,我觉得也行. 现在大部分人学东西要学的好,都是 ...

  6. C#遍历文件夹下面所有指定格式文件

    C#遍历指定文件夹中的所有文件 DirectoryInfo TheFolder=new DirectoryInfo(folderFullName);//遍历文件夹foreach(DirectoryIn ...

  7. python zeros用法实例

    编程就是踩坑的过程.今天又踩了一个坑,做个积累吧. 在给数组赋初始值的时候,经常会用到0数组,而Python中,我们使用zero()函数来实现.在默认的情况下,zeros创建的数组元素类型是浮点型的, ...

  8. base64之js压缩图片

    在日常的一些项目中会有上传图片之类的接口,如果图片过大了再上传的时候是非常的耗时以及占用资源,在这里就给大家分享一下如何在js中把大的图片压缩成小的图片,我这里的功能是用户点击按钮调用相机或者选择文件 ...

  9. (转)[Python 网络编程] makefile (三)

    socket.makefile(mode ='r',buffering = None,*,encoding = None,errors = None,newline = None )返回一个与套接字相 ...

  10. 【翻译】JavaScript中5个值得被广泛使用的数组方法

    原文地址:http://colintoh.com/blog/5-array-methods-that-you-should-use-today?utm_source=javascriptweekly& ...