+1
0

关于java.nio.ByteBuffer的一些杂七杂八。

Changming 发表于 2011年10月21日 17:43 | Hits: 1054
Tag: java

任何网络程序框架都会面临一个问题:如何提供一个高效的buffer?比如我们想写一个http server,那么就需要不断的从文件中读入数据,然后写入到socket中,如:
byte[] buf=new byte[4096];
while(file.read(buf)){
mysocket.write(buf);
}
java.nio中引入了一个重要的类:ByteBuffer,来做这件事情。(我的直觉是它应该和ACE的MessageBlock作用很像,但是后来发现接口迥异。)
ByteBuffer是一个抽象类,它有两种实现:HeapByteBuffer 和 DirectByteBuffer。java.nio.ByteBuffer.allocate(int)返回的是HeapByteBuffer,java.nio.ByteBuffer.allocateDirect(int)返回的是DirectByteBuffer。
HeapByteBuffer分配在jvm的堆(如新生代)上,和其它对象一样,由gc来扫描、回收。DirectByteBuffer则是通过底层的JNI向C Runtime Time通过malloc分配,在JVM的GC所管理的堆之外。

下面讨论HeapByteBuffer。
每个HeapByteBuffer内部有一个byte[]存储数据。这个byte[]在构造HeapByteBuffer的时候分配好,长度不会自动增长。

HeapByteBuffer内部有四个指针(offset):
capacity:内部这个byte数组的大小(byte[]的length)。
mark:相当于书签,初始值为-1。需要设置的时候mark()一下,需要跳回去的时候用reset()方法。
position:指向下一个读取/写入位置。初始值为0,读/写 数据的时候自动往后挪这个指针。
limit:初始值等于 capacity。
它们始终满足这样的关系:mark

flip操作:用在读写操作转换的时候。
limit = position;
position = 0;
mark = -1; //清理掉书签
示例用法:
buf.put(magic); // 先往buffer里面写入一个包头(packet header)
in.read(buf); // 然后从另外一个input stream中读入包体,并写入到buffer中
buf.flip(); // Flip buffer。刚才是往ByteBuffer里写数据,下面要转换成读数据。
out.write(buf); // 把整个buffer里的有效数据(包头+包体)读出来,写入output stream中。

但是调用这个方法之前一定要注意,不要多调用了一次。比如,把上面的第三行代码复制一遍,那么
buf.put(magic); // 先往buffer里面写入一个包头(packet header)
in.read(buf); // 然后从另外一个input stream中读入包体,并写入到buffer中
buf.flip(); // Flip buffer。position=0。
buf.flip(); // Flip buffer。limit=0!
out.write(buf); // 什么也不会写入。

假如让你实现一个readfile这样的函数,你会在函数的末尾调用buf.flip吗?
void readfile(File f,ByteBuffer bb){
f.read(bb);
bb.flip(); //Do it or not do it ? That’s a question。
}
你会在这个函数的接口注释那里说“我没调用flip!!!”吗?

ByteBuffer的toArray()?
有时候,想把ByteBuffer转成一个byte[],把它里面的有效数据拿出来。但是可惜它并没有一个toArray()这样的方法。于是就需要手动copy一下。
ByteBuffer bb;
byte[] contentsOnly = Arrays.copyOf( bb.array(), bb.position() );

DirectByteBuffer的接口和HeapByteBuffer完全一样,最大的区别就是内存位置不一样。如果有大量的文件需要以memory mapping的方式映射到内存中,那么DirectByteBuffer明显优于HeapByteBuffer。因为这部分内存不用被gc,所以降低了gc消耗。另外,HeapByteBuffer的缓存区无法作为操作系统direct io api的缓存区,因为它未必是按page size对齐的。

关于ByteBuffer的性能测试:
往ByteBuffer里面写Float:比较数组(new float[10000])、HeapByteBuffer、DirectByteBuffer的性能差距。

数组: 2.06 微秒
DirectByteBuffer:3.94 微秒
普通buffer: 16.88 微秒
测试环境:Java HotSpot(TM) 64-Bit Server VM (build 21.0-b17, mixed mode), windows 7 64bit。
测试代码:
private final FloatBuffer byteBuffer2 = ByteBuffer.allocate(40000).order(ByteOrder.nativeOrder())
.asFloatBuffer();
private final FloatBuffer byteBuffer = ByteBuffer.allocateDirect(40000).order(ByteOrder.nativeOrder())
.asFloatBuffer();

protected void testWrite() {
// Allow VM to compile
testWriteArray(1000);
testWriteByteBuffer(1000);
// Test for real
System.out.println(“Array buffer: ” + testWriteArray(50000) + ” us per iteration”);
System.out.println(“Direct buffer: ” + testWriteByteBuffer(50000) + ” us per iteration”);
}

protected double testWriteByteBuffer(int iterations) {
long timeNow = System.currentTimeMillis();
for (int j = 0; j for (int i = 0; i byteBuffer.put(i, 1234.5678f);
}
}
return 1000.0 * (System.currentTimeMillis() – timeNow) / iterations;
}

protected double testWriteArray(int iterations) {
long timeNow = System.currentTimeMillis();
for (int j = 0; j for (int i = 0; i byteBuffer2.put(i, 1234.5678f);
}
}
return 1000.0 * (System.currentTimeMillis() – timeNow) / iterations;
}

public BufferMark() {
}

public static void main(String[] args) {
for (int n = 0; n System.out.println(“=== round ” + n);
BufferMark app = new BufferMark();
app.testWrite();
}
}

原文链接: http://www.udpwork.com/redirect/6095

0     +1

我要给这篇文章打分:

可以不填写评论, 而只是打分. 如果发表评论, 你可以给的分值是-5到+5, 否则, 你只能评-1, +1两种分数. 你的评论可能需要审核.

评价列表(1)

  • +1 guest voted at 2012-02-06 17:21:51