本章介绍

  • ByteBuf
  • ByteBufHolder
  • ByteBufAllocator
  • 使用这些接口分配缓冲和运行操作

每当你须要数据传输时,它必须包括一个缓冲区。Java NIO API自带的缓冲区类是相当有限的,没有经过优化,使用JDK的ByteBuffer操作更复杂。缓冲区是一个重要的组建,它是API的一部分。

Netty提供了一个强大的缓冲区实现用于表示一个字节序列,并帮助你操作原始字节或自己定义的POJO。Netty的ByteBuf相当于JDK的ByteBuffer,ByteBuf的作用是在Netty中通过Channel数据传输。它被又一次设计以解决JDK的ByteBuffer中的一些问题,从而使开发者开发网络应用程序显得更有效率。本章将讲述Netty中的缓冲区,并了解它为什么比JDK自带的缓冲区实现更优秀。还会深入了解在Netty中使用ByteBuf訪问数据以及怎样使用它。

5.1 Buffer API

        Netty的缓冲API有两个接口:
  • ByteBuf
  • ByteBufHolder

Netty使用reference-counting(引用计数)的时候知道安全释放Buf和其它资源,尽管知道Netty有效的使用引用计数。这都是自己主动完毕的。这同意Netty使用池和其它技巧来加高速度和保持内存利用率在正常水平,你不须要做不论什么事情来实现这一点。可是在开发Netty应用程序时,你应该处理数据尽快释放池资源。

        Netty缓冲API提供了几个优势:
  • 能够自己定义缓冲类型
  • 通过一个内置的复合缓冲类型实现零拷贝
  • 扩展性好,比方StringBuffer
  • 不须要调用flip()来切换读/写模式
  • 读取和写入索引分开
  • 方法链
  • 引用计数
  • Pooling(池)

5.2 ByteBuf - 字节数据容器

        当须要与远程进行交互时,须要以字节码发送/接收数据。

因为各种原因,一个高效、方便、易用的数据接口是必须的,而Netty的ByteBuf满足这些需求,ByteBuf是一个非常好的经过优化的数据容器。我们能够将字节数据有效的加入到ByteBuf中或从ByteBuf中获取数据。ByteBuf有2部分:一个用于读,一个用于写。我们能够按顺序的读取数据,而且能够跳到開始又一次读一遍。全部的数据操作,我们仅仅须要做的是调整读取数据索引和再次開始读操作。

5.2.1 ByteBuf怎样在工作?

        写入数据到ByteBuf后,写入索引是添加的字节数量。

開始读字节后,读取索引添加。你能够读取字节,直到写入索引和读取索引处理同样的位置,次数若继续读取。则会抛出IndexOutOfBoundsException。调用ByteBuf的不论什么方法開始读/写都会单独维护读索引和写索引。ByteBuf的默认最大容量限制是Integer.MAX_VALUE,写入时若超出这个值将会导致一个异常。

        ByteBuf类似于一个字节数组。最大的差别是读和写的索引能够用来控制对缓冲区数据的訪问。

下图显示了一个容量为16的ByteBuf:


5.2.2 不同类型的ByteBuf

        使用Netty时会遇到3种不同类型的ByteBuf
Heap Buffer(堆缓冲区)
        最经常使用的类型是ByteBuf将数据存储在JVM的堆空间,这是通过将数据存储在数组的实现。堆缓冲区能够高速分配,当不使用时也能够高速释放。它还提供了直接訪问数组的方法,通过ByteBuf.array()来获取byte[]数据。
        訪问非堆缓冲区ByteBuf的数组会导致UnsupportedOperationException,能够使用ByteBuf.hasArray()来检查是否支持訪问数组。
Direct Buffer(直接缓冲区)
        直接缓冲区,在堆之外直接分配内存。

直接缓冲区不会占用堆空间容量,使用时应该考虑到应用程序要使用的最大内存容量以及怎样限制它。直接缓冲区在使用Socket传递数据时性能非常好,由于若使用间接缓冲区,JVM会先将数据拷贝到直接缓冲区再进行传递;可是直接缓冲区的缺点是在分配内存空间和释放内存时比堆缓冲区更复杂。而Netty使用内存池来解决这种问题。这也是Netty使用内存池的原因之中的一个。直接缓冲区不支持数组訪问数据,可是我们能够间接的訪问数据数组,如以下代码:

		ByteBuf directBuf = Unpooled.directBuffer(16);
if(!directBuf.hasArray()){
int len = directBuf.readableBytes();
byte[] arr = new byte[len];
directBuf.getBytes(0, arr);
}

訪问直接缓冲区的数据数组须要很多其它的编码和更复杂的操作。建议若须要在数组訪问数据使用堆缓冲区会更好。

Composite Buffer(复合缓冲区)
        复合缓冲区,我们能够创建多个不同的ByteBuf。然后提供一个这些ByteBuf组合的视图。复合缓冲区就像一个列表。我们能够动态的加入和删除当中的ByteBuf。JDK的ByteBuffer没有这种功能。Netty提供了CompositeByteBuf类来处理复合缓冲区,CompositeByteBuf仅仅是一个视图。CompositeByteBuf.hasArray()总是返回false。由于它可能包括一些直接或间接的不同类型的ByteBuf。
        比如。一条消息由header和body两部分组成,将header和body组装成一条消息发送出去,可能body同样,仅仅是header不同。使用CompositeByteBuf就不用每次都又一次分配一个新的缓冲区。

下图显示CompositeByteBuf组成header和body:


若使用JDK的ByteBuffer就不能这样简单的实现。仅仅能创建一个数组或创建一个新的ByteBuffer,再将内容拷贝到新的ByteBuffer中。以下是使用CompositeByteBuf的样例:
		CompositeByteBuf compBuf = Unpooled.compositeBuffer();
ByteBuf heapBuf = Unpooled.buffer(8);
ByteBuf directBuf = Unpooled.directBuffer(16);
//加入ByteBuf到CompositeByteBuf
compBuf.addComponents(heapBuf,directBuf);
//删除第一个ByteBuf
compBuf.removeComponent(0);
Iterator<ByteBuf> iter = compBuf.iterator();
while(iter.hasNext()){
System.out.println(iter.next().toString());
}
//使用数组訪问数据
if(!compBuf.hasArray()){
int len = compBuf.readableBytes();
byte[] arr = new byte[len];
compBuf.getBytes(0, arr);
}

CompositeByteBuf是ByteBuf的子类。我们能够像操作BytBuf一样操作CompositeByteBuf。而且Netty优化套接字读写的操作是尽可能的使用CompositeByteBuf来做的,使用CompositeByteBuf不会操作内存泄露问题。

5.3 ByteBuf的字节操作

        ByteBuf提供了很多操作,同意改动当中的数据内容或仅仅是读取数据。ByteBuf和JDK的ByteBuffer非常像,可是ByteBuf提供了更好的性能。

5.3.1 随机訪问索引

ByteBuf使用zero-based-indexing(从0開始的索引),第一个字节的索引是0,最后一个字节的索引是ByteBuf的capacity - 1,以下代码是遍历ByteBuf的全部字节:

		//create a ByteBuf of capacity is 16
ByteBuf buf = Unpooled.buffer(16);
//write data to buf
for(int i=0;i<16;i++){
buf.writeByte(i+1);
}
//read data from buf
for(int i=0;i<buf.capacity();i++){
System.out.println(buf.getByte(i));
}

注意通过索引訪问时不会推进读索引和写索引,我们能够通过ByteBuf的readerIndex()或writerIndex()来分别推进读索引或写索引。

5.3.2 顺序訪问索引

        ByteBuf提供两个指针变量支付读和写操作。读操作是使用readerIndex(),写操作时使用writerIndex()。这和JDK的ByteBuffer不同,ByteBuffer仅仅有一个方法来设置索引。所以须要使用flip()方法来切换读和写模式。
        ByteBuf一定符合:0 <= readerIndex <= writerIndex <= capacity。

5.3.3 Discardable bytes废弃字节

我们能够调用ByteBuf.discardReadBytes()来回收已经读取过的字节。discardReadBytes()将丢弃从索引0到readerIndex之间的字节。调用discardReadBytes()方法后会变成例如以下图:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYWJjX2tleQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">

        ByteBuf.discardReadBytes()能够用来清空ByteBuf中已读取的数据,从而使ByteBuf有多余的空间容纳新的数据。可是discardReadBytes()可能会涉及内存复制,由于它须要移动ByteBuf中可读的字节到開始位置,这种操作会影响性能,一般在须要立即释放内存的时候使用收益会比較大。

5.3.4 可读字节(实际内容)

        不论什么读操作会添加readerIndex,假设读取操作的參数也是一个ByteBuf而没有指定目的索引,指定的目的缓冲区的writerIndex会一起添加。没有足够的内容时会抛出IndexOutOfBoundException。新分配、包装、复制的缓冲区的readerIndex的默认值都是0。以下代码显示了获取全部可读数据:
		ByteBuf buf = Unpooled.buffer(16);
while(buf.isReadable()){
System.out.println(buf.readByte());
}

(代码于原书中有出入。原书可能是基于Netty4之前的版本号解说的,此处基于Netty4)

5.3.5 可写字节Writable bytes

不论什么写的操作会添加writerIndex。若写操作的參数也是一个ByteBuf而且没有指定数据源索引,那么指定缓冲区的readerIndex也会一起添加。若没有足够的可写字节会抛出IndexOutOfBoundException。新分配的缓冲区writerIndex的默认值是0。以下代码显示了随机一个int数字来填充缓冲区,直到缓冲区空间耗尽:

		Random random = new Random();
ByteBuf buf = Unpooled.buffer(16);
while(buf.writableBytes() >= 4){
buf.writeInt(random.nextInt());
}

5.3.6 清除缓冲区索引Clearing the buffer indexs

        调用ByteBuf.clear()能够设置readerIndex和writerIndex为0,clear()不会清除缓冲区的内容,仅仅是将两个索引值设置为0。请注意ByteBuf.clear()与JDK的ByteBuffer.clear()的语义不同。
        下图显示了ByteBuf调用clear()之前:

下图显示了调用clear()之后:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYWJjX2tleQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">

        和discardReadBytes()相比。clear()是廉价的,由于clear()不会复制不论什么内存。

5.3.7 搜索操作Search operations

        各种indexOf()方法帮助你定位一个值的索引是否符合,我们能够用ByteBufProcessor复杂动态顺序搜索实现简单的静态单字节搜索。假设你想解码可变长度的数据,如null结尾的字符串。你会发现bytesBefore(byte value)方法实用。比如我们写一个集成的flash sockets的应用程序。这个应用程序使用NULL结束的内容,使用bytesBefore(byte value)方法能够非常easy的检查数据中的空字节。没有ByteBufProcessor的话。我们须要自己做这些事情,使用ByteBufProcessor效率更好。

5.3.8 标准和重置Mark and reset

        每一个ByteBuf有两个标注索引,一个存储readerIndex,一个存储writerIndex。你能够通过调用一个重置方法又一次定位两个索引之中的一个。它类似于InputStream的标注和重置方法,没有读限制。

我们能够通过调用readerIndex(int readerIndex)和writerIndex(int writerIndex)移动读索引和写索引到指定位置,调用这两个方法设置指定索引位置时可能抛出IndexOutOfBoundException。

5.3.9 衍生的缓冲区Derived buffers

        调用duplicate()、slice()、slice(int index, int length)、order(ByteOrder endianness)会创建一个现有缓冲区的视图。

衍生的缓冲区有独立的readerIndex、writerIndex和标注索引。假设须要现有缓冲区的全新副本。能够使用copy()或copy(int index, int length)获得。看以下代码:

		// get a Charset of UTF-8
Charset utf8 = Charset.forName("UTF-8");
// get a ByteBuf
ByteBuf buf = Unpooled.copiedBuffer("“Netty in Action rocks!“", utf8);
// slice
ByteBuf sliced = buf.slice(0, 14);
// copy
ByteBuf copy = buf.copy(0, 14);
// print "“Netty in Action rocks!“"
System.out.println(buf.toString(utf8));
// print "“Netty in Act"
System.out.println(sliced.toString(utf8));
// print "“Netty in Act"
System.out.println(copy.toString(utf8));

5.3.10 读/写操作以及其它一些操作

        有两种主要类型的读写操作:
  • get/set操作以索引为基础,在给定的索引设置或获取字节
  • 从当前索引開始读写,递增当前的写索引或读索引

ByteBuf的各种读写方法或其它一些检查方法能够看ByteBuf的源代码。这里不赘述了。

5.4 ByteBufHolder

ByteBufHolder是一个辅助类,是一个接口,事实上现类是DefaultByteBufHolder,另一些实现了ByteBufHolder接口的其它接口类。ByteBufHolder的作用就是帮助更方便的訪问ByteBuf中的数据,当缓冲区没用了后,能够使用这个辅助类释放资源。

ByteBufHolder非常easy,提供的可供訪问的方法也非常少。假设你想实现一个“消息对象”有效负载存储在ByteBuf,使用ByteBufHolder是一个好主意。

        虽然Netty提供的各种缓冲区实现类已经非常easy使用,但Netty依旧提供了一些使用的工具类,使得创建和使用各种缓冲区更加方便。

以下会介绍一些Netty中的缓冲区工具类。

5.4.1 ByteBufAllocator

        Netty支持各种ByteBuf的池实现,来使Netty提供一种称为ByteBufAllocator成为可能。ByteBufAllocator负责分配ByteBuf实例,ByteBufAllocator提供了各种分配不同ByteBuf的方法,如须要一个堆缓冲区能够使用ByteBufAllocator.heapBuffer()。须要一个直接缓冲区能够使用ByteBufAllocator.directBuffer(),须要一个复合缓冲区能够使用ByteBufAllocator.compositeBuffer()。

其它方法的使用能够看ByteBufAllocator源代码及凝视。

        获取ByteBufAllocator对象非常easy,能够从Channel的alloc()获取,也能够从ChannelHandlerContext的alloc()获取。看以下代码:
			ServerBootstrap b = new ServerBootstrap();
b.group(group).channel(NioServerSocketChannel.class).localAddress(new InetSocketAddress(port))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// get ByteBufAllocator instance by Channel.alloc()
ByteBufAllocator alloc0 = ch.alloc();
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//get ByteBufAllocator instance by ChannelHandlerContext.alloc()
ByteBufAllocator alloc1 = ctx.alloc();
ctx.writeAndFlush(buf.duplicate()).addListener(ChannelFutureListener.CLOSE);
}
});
}
});

Netty有两种不同的ByteBufAllocator实现。一个实现ByteBuf实例池将分配和回收成本以及内存使用降到最低;还有一种实现是每次使用都创建一个新的ByteBuf实例。

Netty默认使用PooledByteBufAllocator,我们能够通过ChannelConfig或通过引导设置一个不同的实现来改变。很多其它细节在后面讲述。

5.4.2 Unpooled

        Unpooled也是用来创建缓冲区的工具类,Unpooled的使用也非常easy。Unpooled提供了非常多方法,具体方法及使用能够看API文档或Netty源代码。

看以下代码:

		//创建复合缓冲区
CompositeByteBuf compBuf = Unpooled.compositeBuffer();
//创建堆缓冲区
ByteBuf heapBuf = Unpooled.buffer(8);
//创建直接缓冲区
ByteBuf directBuf = Unpooled.directBuffer(16);

5.4.3 ByteBufUtil

        ByteBufUtil提供了一些静态的方法,在操作ByteBuf时很实用。ByteBufUtil提供了Unpooled之外的一些方法。或许最有价值的是hexDump(ByteBuf buffer)方法。这种方法返回指定ByteBuf中可读字节的十六进制字符串,能够用于调试程序时打印ByteBuf的内容。十六进制字符串相比字节而言对用户更友好。

5.5 Summary

        本章主要学习Netty提供的缓冲区类ByteBuf的创建和简单有用以及一些操作ByteBuf的工具类。

Netty In Action中文版 - 第五章:Buffers(缓冲)的更多相关文章

  1. Netty In Action中文版 - 第四章:Transports(传输)

    本章内容 Transports(传输) NIO(non-blocking IO,New IO), OIO(Old IO,blocking IO), Local(本地), Embedded(嵌入式) U ...

  2. Netty In Action中文版 - 第七章:编解码器Codec

    http://blog.csdn.net/abc_key/article/details/38041143 本章介绍 Codec,编解码器 Decoder,解码器 Encoder,编码器 Netty提 ...

  3. Netty In Action中文版 - 第六章:ChannelHandler

    本章介绍 ChannelPipeline ChannelHandlerContext ChannelHandler Inbound vs outbound(入站和出站) 接受连接或创建他们仅仅是你的应 ...

  4. Netty In Action中文版 - 第三章:Netty核心概念

            在这一章我们将讨论Netty的10个核心类.清楚了解他们的结构对使用Netty非常实用.可能有一些不会再工作中用到.可是也有一些非经常常使用也非常核心,你会遇到. Bootstrap ...

  5. Java Persistence with MyBatis 3(中文版) 第五章 与Spring集成

    MyBatis-Spring是MyBatis框架的子模块,用来提供与当前流行的依赖注入框架Spring的无缝集成. Spring框架是一个基于依赖注入(Dependency Injection)和面向 ...

  6. Netty In Action中文版 - 第一章:Netty介绍

    本章介绍 Netty介绍 为什么要使用non-blocking IO(NIO) 堵塞IO(blocking IO)和非堵塞IO(non-blocking IO)对照 Java NIO的问题和在Nett ...

  7. Netty In Action中国版 - 第二章:第一Netty程序

    本章介绍 获得Netty4最新的版本号 设置执行环境,以构建和执行netty程序 创建一个基于Netty的server和client 拦截和处理异常 编制和执行Nettyserver和client 本 ...

  8. Netty In Action中文版 - 第十五章:选择正确的线程模型

    http://blog.csdn.net/abc_key/article/details/38419469 本章介绍 线程模型(thread-model) 事件循环(EventLoop) 并发(Con ...

  9. Learning Spark中文版--第五章--加载保存数据(2)

    SequenceFiles(序列文件)   SequenceFile是Hadoop的一种由键值对小文件组成的流行的格式.SequenceFIle有同步标记,Spark可以寻找标记点,然后与记录边界重新 ...

随机推荐

  1. BZOJ 3907: 网格( 组合数 + 高精度 )

    (0,0)->(n,m)方案数为C(n,n+m), 然后减去不合法的方案. 作(n,m)关于y=x+1的对称点(m-1,n+1), 则(0,0)->(m-1,n+1)的任意一条路径都对应( ...

  2. Android之判断设备网络连接状态,并判断连接方式

    在Android开发过程中,对于一个需要连接网络的Android设备,对设备的网络状态检测是很有必要的!有很多的App都需要连接网络.判断设备是否已经连接网络,并且在连接网络的状态下判断是wifi无线 ...

  3. HTML5 事件

    下面的表格列出了可插入 HTML 5 元素中以定义事件行为的标准事件属性. Window 事件属性 - Window Event Attributes 表单事件 - Form Events 键盘事件 ...

  4. mysqld守护进程

    1.安装方式:安装文件:可执行的二进制文件: 源代码编译. 2.版本选择:常见版本区别:GA(一般应用,尽量使用最新版本)/RC(候选发布版本)/测试版本实版本选择主要是够用.适用.好用!不一定是最新 ...

  5. Java InputStream读取网络响应Response数据的方法

    Java InputStream读取数据问题 原理讲解 1. 关于InputStream.read()     在从数据流里读取数据时,为图简单,经常用InputStream.read()方法.这个方 ...

  6. [置顶] IT屌丝的离职申请

          各位好,很遗憾在如此关键的时刻和大家说告别.我为金三也付出了近一年的心血,几乎没怎么休过令人舒心的周末,熬了多少夜更不用说,如此的离开,请相信我也非常心痛.       尽管直到现在我的水 ...

  7. 依赖于设备的位图(DDB) ,CreateCompatibleBitmap用法

    DDB(Device-dependent bitmap)依赖于具体设备,这主要体现在以下两个方面: DDB的颜色模式必需与输出设备相一致.例如,如果当前的显示设备是256色模式,那么DDB必然也是25 ...

  8. IT第三天 - 数据类型、转换、Scanner使用

    IT第三天 上午 变量类型 1.6种数值类型:byte.short.int.long.float.double:其中byte是8个字节,short是16字节,int是32字节.long是64字节:日常 ...

  9. HDU 5281 Senior&#39;s Gun

    Senior's Gun Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others) Tot ...

  10. 将其它图片格式转为.eps格式

    假设是用origin的话就不存在这个问题. 倘若你是用excel绘图的话. 1.先将excel导出为.pdf文件 2.用Adobe Acrobat Pro 打开pdf,用其它的pdf软件貌似不行 3. ...