java NIO 直接与非直接缓冲区
ByteBuffer有两个创建缓冲区的方法:
static ByteBuffer allocate(int capacity)
static ByteBuffer allocateDirect(int capacity)
这两个方法都是创建缓冲区的方法,使用直接缓冲区的时候,JVM虚拟机会直接在此缓冲区上执行本机IO操作,也就是说,在每次调用基础操作系统的一个本机IO之前或者之后,虚拟机都会避免将缓冲区的内容复制到中间缓冲区(或者从中间缓冲区复制内容)。 直接字节缓冲区使用上边方法中的allocateDirect工厂方法创建,此方法返回的缓冲区进行分配和取消分配所需要的成本往往比间接缓冲区要高,直接缓冲区的内容可以驻留 在常规的垃圾回收堆之外,因此,它们对应用程序的内容需求量造成的影响可能并不明显,所以建议将直接缓冲区主要分配给那些容易受基础系统的本 机IO操作影响的大型、持久的缓冲区。一般情况下,最好仅在直接缓冲区能在程序性能方面带来明显好处的时候分配它们。
直接缓冲区还可以使用mapping 将文件区域直接映射到内存中来创建,Java平台的实现有助于通过JNI从本机代码直接创建字节缓冲区,如果以上这些缓冲区中的某个缓冲区实例指的是不可 访问的内存区域,则视图访问该区域不会更改该缓冲区的内容,并且会在访问期间或稍候的某个时间导致抛出不确定的异常。
字节缓冲区是直接缓冲区还是非直接缓 冲区可以通过ByteBuffer的isDirect方法来确定,提供该方法是为了能够在性能关键型代码中执行显示缓冲区管理。
访问二进制数据:
此类定义除了boolean之外,读写所有其他基本类型值的方法,这些基本值可 以根据缓冲区的当前字节顺序与字节序列相互进行转换,并可以通过order方法获取和修改。特定的字节顺序由ByteOrder类的实例进行表示,字节缓 冲区的初始顺序是BIG_ENDIAN(该顺序可以参考《Java内存模型》http://blog.csdn.net/silentbalanceyh/archive/2009/10/13/4661230.aspx)的。为了访问异类二进制数据,此类还针对每种类型定义了一系列绝对和相对的put和get方法, 并针对float、 char、short、int、long和double等类型定义了相对方法,该方法可以自行参考API内容。绝对get和put方法的 index参数是根据字节定义的,而不是根据所读写的类型定义的。
为了访问同类二进制数据(即相同类型的值序列),此类还定义了可 以为指定类型的缓冲区创建视图的方法,视图缓冲区只是其内容受该字节缓冲区支持的另一种缓冲区,字节缓冲区内容的更改在视图缓冲区中是可见的,反之亦然;这两种缓冲区的位置、限制和标记 值都是独立的。使用视图缓冲区有三大优势:
视 图缓冲区不是根据字节进行索引,而是根据其特定于类型的值的大小进行索引
视 图缓冲区提供了相对批量put和get方法,这些方法可在缓冲区和数组或相同类型的其他缓冲区之间传输值的连续序列
视图缓冲区可能更高效,这是因为,当且仅当其支持的字节缓冲区为直接缓冲区时它才是直接缓冲区。
实际上ByteBuffer是继承于Buffer类,里面存放的是字节,如果要 将它们转换成字符串则需要使用Charset,Charset是字符编码,它提供了把字节流转换成为字符流(解码)和 将字符串转换成字节流(编码)的方法。该类有一下三个重要的属性:
容量(capacity):表示该缓冲区可以存放多少数据
极限(limit):表示读写缓存的位置, 不能对超过位置进行数据的读或写操作
位置(position):表示下一个缓冲区的读写单元, 每读写一次缓存区,位置都会变化,位置是一个非负整数
import java.io.FileInputStream; import java.io.FileOutputStream; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; public class QuickCopy { public static void main(String args[]) throws Exception{ FileInputStream fin = new FileInputStream("D:/work/test.txt"); FileOutputStream fout = new FileOutputStream("D:/work/output.txt"); FileChannel inChannel = fin.getChannel(); FileChannel outChannel = fout.getChannel(); ByteBuffer buffer = ByteBuffer.allocate(1024); while(true){ int ret = inChannel.read(buffer); if( ret == -1) break; buffer.flip(); //该方法为父类Buffer的方法 outChannel.write(buffer); buffer.clear(); //该方法为父类Buffer的方法 } } }
上边的代码分配了1024个字节的直接缓冲区,然后使用本地拷贝的方式进行文件拷贝,应该是比 普通的文件拷贝更高效,这里解释几个比较常用的ByteBuffer类里面的方法【这里不介绍分配缓冲区的方法 了】:
public abstract ByteBuffer compact() throws ReadOnlyBufferException:
——该方法压缩此缓冲区(可选的),将缓冲区的当前位置和界限之间的字节复制到缓冲区的开始处,即将索引p = position()处 的字节复制到索引0处,将索引n+1处的字节复制到索引1的 位置,依次类推直到索引limit() - 1处的字节复制到索引 n = limit() - 1 - p处, 然后将缓冲区的位置设置为n + 1,并将其界限设置为其容量,如果已定义了标记,则丢弃。将缓冲区的位置设置为复制的字节数,而不是零,以便调 用此方法后可以紧接着调用另一个相对put方法。
public abstract ByteBuffer duplicate():
——创建共享此缓冲区内容的新的字节缓冲区,新缓冲区的内容将为此缓冲区的内容,此缓冲区内容的更改在新缓冲 区中是可见的,反之亦然;这两个缓冲区的位置、界限和标记值是相互独立的。新缓冲区的容量、界限、位置和标记值将与此缓冲区相同。当且 仅当此缓冲区为直接时,新缓冲区才是直接的,当切仅当此缓冲区是只读时,新缓冲区才是只读的。
public abstract ByteBuffer slice():
——创建新的字节缓冲区,其内容是此缓冲区的共享子序列,新缓冲区的内容将从此缓冲区的当前位置开始,此缓冲 区内容的更改在新缓冲区中是可见的,反之亦然;这两个缓冲区的位置、界限和标记值是相互独立的。
public abstract ByteBuffer wrap(byte[] array):
——将byte数组包装到缓冲区中,新的缓冲区将由给定的byte数组支持,也就是说,缓冲区修改 将导致数组修改,反之亦然。新缓冲区的容量和界限将为array.length,其位置将为零,其标记是不确定的,其底层实现数据将为给定数组,并且其数 组偏移量将为零。
这里再提供几个基本操作的例子:
import java.nio.ByteBuffer; import java.nio.CharBuffer; public class BasicType { public static void main(String args[]) throws Exception{ // 使用字节数组创建ByteBuffer byte[] bytes = new byte[10]; ByteBuffer buffer = ByteBuffer.wrap(bytes); // 创建字符ByteBuffer ByteBuffer charBuffer = ByteBuffer.allocate(15); CharBuffer charBuffer2 = buffer.asCharBuffer(); // 设置获取字符类型的Buffer ByteBuffer charBuffer3 = ByteBuffer.allocate(100); charBuffer3.putChar((char)123); charBuffer3.flip(); char c = charBuffer3.getChar(); } }
上边是针对基本类型的操作,查阅API可以看到基本操作相关的类里面提供的方法
import java.nio.ByteBuffer; import java.nio.CharBuffer; public class StringByteBuffer { public static void main(String args[]) throws Exception{ // 使用ByteBuffer存储字符串 ByteBuffer buffer = ByteBuffer.allocate(100); CharBuffer cBuffer = buffer.asCharBuffer(); cBuffer.put("Hello World"); cBuffer.flip(); String result = cBuffer.toString(); } }
2013-01-13