每当你需要传输数据时,它必须包含一个缓冲区。Java NIO API 自带的缓冲区类是相当有限的,没有经过优化,使用 JDK 的
ByteBuffer 操作更复杂。缓冲区是一个重要的组建,它是 API
的一部分。Netty提供了一个强大的缓冲区实现用于表示一个字节序列,并帮助你操作原始字节或自定义的 POJO。Netty 的 ByteBuf
相当于 JDK 的ByteBuffer,ByteBuf的作用是在 Netty 中通过 Channel 传输数据。它被重新设计以解决 JDK
的 ByteBuffer 中的一些问题, 从而使开发人员开发网络应用程序显得更有效率。
一: ByteBuf
当需要与远程进行交互时,需要以字节码发送/接收数据。由于各种原因,一个高效、方便、易用的数据接口是必须的,而 Netty 的 ByteBuf 满足这些需求,ByteBuf 是一个很好的经过优化的数据容器, 我们可以将字节数据有效的添加到 ByteBuf 中或从 ByteBuf 中获取数据。ByteBuf 有 2 部分:一个用于读,一个用于写。我们可以按顺序的读取数据,并且可以跳到开始重新读一遍。 所有的数据操作, 我们只需要做的是调整读取数据索引和再次开始读操作。
写入数据到 ByteBuf 后, 写入索引是增加的字节数量。 开始读字节后, 读取索引增加。你可以读取字节,直到写入索引和读取索引处理相同的位置,次数若继续读取,则会抛出IndexOutOfBoundsException。调用 ByteBuf 的任何方法开始读/写都会单独维护读索引和写索引。ByteBuf 的默认最大容量限制是 Integer.MAX_VALUE,写入时若超出这个值将会导致一个异常。
ByteBuf 类似于一个字节数组,最大的区别是读和写的索引可以用来控制对缓冲区数据的访问。
使用 Netty 时会遇到 3 种不同类型的 ByteBuf:
ByteBuf directBuf = Unpooled.directBuffer(16);
if(!directBuf.hasArray()){
int len = directBuf.readableBytes();
byte[] arr = new byte[len];
directBuf.getBytes(0, arr);
}
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 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不会操作内存泄露问题。
ByteBuf 字节操作API:
//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
注意通过索引访问时不会推进读索引和写索引,我们可以通过 ByteBuf 的
readerIndex()或writerIndex()来分别推进读索引或写索引。
顺序访问索引
ByteBuf 提供两个指针变量支付读和写操作,读操作是使用 readerIndex(),写操作时使用 writerIndex()。这和 JDK 的 ByteBuffer 不同,ByteBuffer 只有一个方法来设置索引,所以需要使用 flip()方法来切换读和写模式。 ByteBuf 一定符合:
0 <= readerIndex <= writerIndex <= capacity。
Discardable bytes废弃字节
我们可以调用 ByteBuf.discardReadBytes()来回收已经读取过的字节, discardReadBytes()将丢弃从索引 0 到 readerIndex 之间的字节。
ByteBuf.discardReadBytes()可以用来清空 ByteBuf 中已读取的数据, 从而使ByteBuf有多余的空间容纳新的数据, 但是 discardReadBytes()可能会涉及内存复制, 因为它需要移动 ByteBuf 中可读的字节到开始位置, 这样的操作会影响性能, 一般在需要马上释放内存的时候使用收益会比较大。
可读字节( 实际内容)
任何读操作会增加 readerIndex,如果读取操作的参数也是一个 ByteBuf
而没有指定目的索引,指定的目的缓冲区的 writerIndex 会一起增加,没有足够的内容时会抛出IndexOutOfBoundException。 新分配、 包装、 复制的缓冲区的 readerIndex 的默认值都是 0。下面代码显示了获取所有可读数据:
ByteBuf buf = Unpooled.buffer(16);
while(buf.isReadable()){
System.out.println(buf.readByte());
}
Random random = new Random();
ByteBuf buf = Unpooled.buffer(16);
while(buf.writableBytes() >= 4){
buf.writeInt(random.nextInt());
}
// 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));
读/ 写操作以及其他一些操作 有两种主要类型的读写操作:
二: ByteBufHolder
ByteBufHolder 是一个辅助类,是一个接口,其实现类是DefaultByteBufHolder,还有一些实现了ByteBufHolder 接口的其他接口类。ByteBufHolder 的作用就是帮助更方便的访问 ByteBuf 中的数据, 当缓冲区没用了后, 可以使用这个辅助类释放资源。 ByteBufHolder很简单,提供的可供访问的方法也很少。如果你想实现一个“消息对象”有效负载存储在ByteBuf,使用ByteBufHolder 是一个好主意。
尽管 Netty 提供的各种缓冲区实现类已经很容易使用,但 Netty 依然提供了一些使用的工具类, 使得创建和使用各种缓冲区更加方便。 下面会介绍一些 Netty 中的缓冲区工具类。
ServerBootstrap b = new ServerBootstrap();
new InetSocketAddress(port)).childHandler(
new ChannelInitializer() {
@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()
ctx.writeAndFlush(buf.duplicate()).addListener(
ChannelFutureListener.CLOSE);
}
});
}
});
Netty 有两种不同的 ByteBufAllocator 实现,一个实现 ByteBuf实例池将分配和回收成本以及内存使用降到最低;另一种实现是每次使用都创建一个新的 ByteBuf 实例。Netty默认使用PooledByteBufAllocator, 我们可以通过 ChannelConfig 或通过引导设置一个不同的实现来改变。更多细节在后面讲述。
//创建复合缓冲区
CompositeByteBuf compBuf = Unpooled.compositeBuffer();
//创建堆缓冲区
ByteBuf heapBuf = Unpooled.buffer(8);
//创建直接缓冲区
ByteBuf directBuf = Unpooled.directBuffer(16);
可以自定义缓冲类型
通过一个内置的复合缓冲类型实现零拷贝
扩展性好,比如 StringBuffer
不需要调用 flip()来切换读/写模式
读取和写入索引分开
方法链
引用计数
Pooling(池)
无意中发现了一个巨牛的人工智能教程,忍不住分享一下给大家。教程不仅是零基础,通俗易懂,而且非常风趣幽默,像看小说一样!觉得太牛了,所以分享给大家。点这里可以跳转到教程。