本章介绍java
- 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有两个接口:
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:dom
5.2.2 不一样类型的ByteBuf
使用Netty时会遇到3种不一样类型的ByteBuf
Heap Buffer(堆缓冲区)
最常用的类型是ByteBuf将数据存储在JVM的堆空间,这是经过将数据存储在数组的实现。堆缓冲区可以高速分配,当不使用时也可以高速释放。它还提供了直接訪问数组的方法,经过ByteBuf.array()来获取byte[]数据。
訪问非堆缓冲区ByteBuf的数组会致使UnsupportedOperationException,可以使用ByteBuf.hasArray()来检查是否支持訪问数组。
Direct Buffer(直接缓冲区)
直接缓冲区,在堆以外直接分配内存。
直接缓冲区不会占用堆空间容量,使用时应该考虑到应用程序要使用的最大内存容量以及怎样限制它。直接缓冲区在使用Socket传递数据时性能很是好,因为若使用间接缓冲区,JVM会先将数据拷贝到直接缓冲区再进行传递;但是直接缓冲区的缺点是在分配内存空间和释放内存时比堆缓冲区更复杂。而Netty使用内存池来解决这种问题。这也是Netty使用内存池的缘由之中的一个。直接缓冲区不支持数组訪问数据,但是咱们可以间接的訪问数据数组,如如下代码:socket
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:

ide
若使用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()方法后会变成例如如下图:
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()以后:
和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的工具类。