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

来源:互联网 发布:des算法c语言实现 编辑:程序博客网 时间:2024/05/17 21:51

任何网络程序框架都会面临一个问题:如何提供一个高效的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/item/6095.html

原创粉丝点击