netty 系列之: 不用怀疑,netty 中的 ByteBuf 就是比 JAVA 中的好用
简介
netty 作为一个优秀的的 NIO 框架,被广泛应用于各种服务器和框架中。同样是 NIO,netty 所依赖的 JDK 在 1.4 版本中早就提供 nio 的包,既然 JDK 已经有了 nio 的包,为什么 netty 还要再写一个呢?
不是因为 JDK 不优秀,而是因为 netty 的要求有点高。
ByteBuf 和 ByteBuffer 的可扩展性
在讲解 netty 中的 ByteBuf 如何优秀之前,我们先来看一下 netty 中的 ByteBuf 和 jdk 中的 ByteBuffer 有什么关系。
事实上,没啥关系,只是名字长的有点像而已。
jdk 中的 ByteBuffer,全称是 java.nio.ByteBuffer,属于 JAVA nio 包中的一个基础类。它的定义如下:
而 netty 中的 ByteBuf,全称是 io.netty.buffer,属于 netty nio 包中的一个基础类。它的定义如下:
两者的定义都很类似,两者都是抽象类,都需要具体的类来实现他们。
但是,当你尝试去创建一个类来继承 JDK 的 ByteBuffer,则会发现继承不了,为什么命名一个 abstract 的类会继承不了呢?
仔细研究会发现,在 ByteBuffer 中,定义了下面两个没有显示标记其作用域访问的方法:
根据 JDK 的定义,没有显示标记作用域的方法,默认其访问访问是 package,当这两个方法又都是 abstract 的,所以只有同一个 package 的类才能继承 JDK 的 ByteBuffer。
当然,JDK 本身有 5 个 ByteBuffer 的实现,他们分别是 DirectByteBuffer,DirectByteBufferR,HeapByteBuffer,HeapByteBufferR 和 MappedByteBuffer。
但是 JDK 限制了用户自定义类对 ByteBuffer 的扩展。虽然这样可以保证 ByteBuffer 类在使用上的安全性,但是同时也现在了用户需求的多样性。
既然 JDK 的 ByteBuffer 不能扩展,那么很自然的 netty 中的 ByteBuf 跟它就没有任何关系了。
netty 中的 ByteBuff 是参考了 JDK 的 ByteBuffer,并且做了很多有意义的提升,让 ByteBuff 更加好用。
和 JDK 的 ByteBuffer 相比,netty 中的 ByteBuf 并没有扩展的限制,你可以自由的对其进行扩展和修改。
不同的使用方法
JDK 中的 ByteBuffer 和 netty 中的 ByteBuff 都提供了对各种类型数据的读写功能。
但是相对于 netty 中的 ByteBuff, JDK 中的 ByteBuffer 使用其来比较复杂,因为它定义了 4 个值来描述 ByteBuffer 中的数据和使用情况,这四个值分别是:mark,position,limit 和 capacity。
capacity 是它包含的元素数。 capacity 永远不会为负且永远不会改变。
limit 是不应读取或写入的第一个元素的索引。 limit 永远不会为负,也永远不会大于其容量。
position 是要读取或写入的下一个元素的索引。 position 永远不会为负,也永远不会大于其限制。
mark 是调用 reset 方法时其位置将重置到的索引。 mark 并不一定有值,但当它有值的时候,它永远不会是负的,也永远不会大于 position。
上面 4 个值的关系是:
然后 JDK 还提供了 3 个处理上面 4 个标记的方法:
clear : 将 limit 设置为 capacity,并将 position 设置为 0,表示可以写入。
flip : 将 limit 设置为当前位置,并将 position 设置为 0.表示可以读取。
rewind : limit 不变,将 position 设置为 0,表示重新读取。
是不是头很大?
太多的变量,太多的方法,虽然现在你可能记得,但是过一段时间就会忘记到底该怎么正确使用 JDK 的 ByteBuffer 了。
和 JDK 不同的是,netty 中的 ByteBuff,只有两个 index,分别是 readerIndex 和 writerIndex 。
除了 index 之外,ByteBuff 还提供了更加丰富的读写 API,方便我们使用。
性能上的不同
对于 JDK 的 java.nio.ByteBuffer 来说,当我们为其分配空间的时候,buffer 中会被使用 0 来填充。虽然这些 0 可能会马上被真正有意义的值来进行替换。但是不可否认,填充的过程消耗了 CPU 和内存。
另外 JDK 的 java.nio.ByteBuffer 是依赖于垃圾回收器来进行回收的,但是我们之前讲过了,ByteBuffer 有两种内型,一种是 HeapBuffer,这种类型是由 JVM 进行管理的,用垃圾回收器来进行回收是没有问题的。
但是问题在于还有一类 ByteBuffer 是 DirectByteBuffer,这种 Buffer 是直接分配在外部内存上的,并不是由 JVM 来进行管理.通常来说 DirectBuffer 可能会存在较长的时间,如果短时间分配大量的短生命周期的 DirectBuffer,会导致这些 Buffer 来不及回收,从而导致 OutOfMemoryError.
另外使用 API 来回收 DirectBuffer 的速度也不是那么快。
相对而言,netty 中的 ByteBuf 使用的是自己管理的引用计数。当 ByteBuf 的引用计数归零的时候,底层的内存空间就会被释放,或者返回到内存池中。
我们看一下 netty 中 direct ByteBuff 的使用:
当然,netty 这种自己管理引用计数也有一些缺点,可能会在 pooled buffer 被垃圾回收之后,pool 中的 buffer 才返回,从而导致内存泄露。
还好,netty 提供了 4 种检测引用计数内存泄露的方法,分别是:
DISABLED---禁用泄露检测
SIMPLE --默认的检测方式,占用 1% 的 buff。
ADVANCED - 也是 1%的 buff 进行检测,不过这个选项会展示更多的泄露信息。
PARANOID - 检测所有的 buff。
具体的检测选项如下:
总结
以上就是 netty 中优秀的 ByteBuff 和 JDK 中的对比。还不赶紧用起来。
本文已收录于 http://www.flydean.com/45-netty-bytebuf-bytebuffer/
最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!
欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!
版权声明: 本文为 InfoQ 作者【程序那些事】的原创文章。
原文链接:【http://xie.infoq.cn/article/886306dfbe47cc3d795ea8afb】。文章转载请联系作者。
评论