清单 3 所示代码对传统 I/O、基于 Byte 的 NIO、基于内存映射的 NIO 三种方式进行了性能上的对比,使用一个有 400 万数据的文件的读、写操作耗时作为评测依据。
  清单 3. I/O 的三种方式对比试验
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
public class NIOComparator {
public void IOMethod(String TPATH){
long start = System.currentTimeMillis();
try {
DataOutputStream dos = new DataOutputStream(
new BufferedOutputStream(new FileOutputStream(new File(TPATH))));
for(int i=0;i<4000000;i++){
dos.writeInt(i);//写入 4000000 个整数
}
if(dos!=null){
dos.close();
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println(end - start);
start = System.currentTimeMillis();
try {
DataInputStream dis = new DataInputStream(
new BufferedInputStream(new FileInputStream(new File(TPATH))));
for(int i=0;i<4000000;i++){
dis.readInt();
}
if(dis!=null){
dis.close();
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
end = System.currentTimeMillis();
System.out.println(end - start);
}
public void ByteMethod(String TPATH){
long start = System.currentTimeMillis();
try {
FileOutputStream fout = new FileOutputStream(new File(TPATH));
FileChannel fc = fout.getChannel();//得到文件通道
ByteBuffer byteBuffer = ByteBuffer.allocate(4000000*4);//分配 Buffer
for(int i=0;i<4000000;i++){
byteBuffer.put(int2byte(i));//将整数转为数组
}
byteBuffer.flip();//准备写
fc.write(byteBuffer);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println(end - start);
start = System.currentTimeMillis();
FileInputStream fin;
try {
fin = new FileInputStream(new File(TPATH));
FileChannel fc = fin.getChannel();//取得文件通道
ByteBuffer byteBuffer = ByteBuffer.allocate(4000000*4);//分配 Buffer
fc.read(byteBuffer);//读取文件数据
fc.close();
byteBuffer.flip();//准备读取数据
while(byteBuffer.hasRemaining()){
byte2int(byteBuffer.get(),byteBuffer.get(),byteBuffer.get(),byteBuffer.get());//将 byte 转为整数
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
end = System.currentTimeMillis();
System.out.println(end - start);
}
public void mapMethod(String TPATH){
long start = System.currentTimeMillis();
//将文件直接映射到内存的方法
try {
FileChannel fc = new RandomAccessFile(TPATH,"rw").getChannel();
IntBuffer ib = fc.map(FileChannel.MapMode.READ_WRITE, 0, 4000000*4).asIntBuffer();
for(int i=0;i<4000000;i++){
ib.put(i);
}
if(fc!=null){
fc.close();
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println(end - start);
start = System.currentTimeMillis();
try {
FileChannel fc = new FileInputStream(TPATH).getChannel();
MappedByteBuffer lib = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size());
lib.asIntBuffer();
while(lib.hasRemaining()){
lib.get();
}
if(fc!=null){
fc.close();
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
end = System.currentTimeMillis();
System.out.println(end - start);
}
public static byte[] int2byte(int res){
byte[] targets = new byte[4];
targets[3] = (byte)(res & 0xff);//低位
targets[2] = (byte)((res>>8)&0xff);//次低位
targets[1] = (byte)((res>>16)&0xff);//次高位
targets[0] = (byte)((res>>>24));//高位,无符号右移
return targets;
}
public static int byte2int(byte b1,byte b2,byte b3,byte b4){
return ((b1 & 0xff)<<24)|((b2 & 0xff)<<16)|((b3 & 0xff)<<8)|(b4 & 0xff);
}
public static void main(String[] args){
NIOComparator nio = new NIOComparator();
nio.IOMethod("c:\1.txt");
nio.ByteMethod("c:\2.txt");
nio.ByteMethod("c:\3.txt");
}
}
  清单 3 运行输出如清单 4 所示。
  清单 4. 运行输出
  1139
  906
  296
  157
  234
  125
  除上述描述及清单 3 所示代码以外,NIO 的 Buffer 还提供了一个可以直接访问系统物理内存的类 DirectBuffer。DirectBuffer 继承自 ByteBuffer,但和普通的 ByteBuffer 不同。普通的 ByteBuffer 仍然在 JVM 堆上分配空间,其大内存受到大堆的限制,而 DirectBuffer 直接分配在物理内存上,并不占用堆空间。在对普通的 ByteBuffer 访问时,系统总是会使用一个“内核缓冲区”进行间接的操作。而 DirectrBuffer 所处的位置,相当于这个“内核缓冲区”。因此,使用 DirectBuffer 是一种更加接近系统底层的方法,所以,它的速度比普通的 ByteBuffer 更快。DirectBuffer 相对于 ByteBuffer 而言,读写访问速度快很多,但是创建和销毁 DirectrBuffer 的花费却比 ByteBuffer 高。DirectBuffer 与 ByteBuffer 相比较的代码如清单 5 所示。
  清单 5. DirectBuffer VS ByteBuffer
import java.nio.ByteBuffer;
public class DirectBuffervsByteBuffer {
public void DirectBufferPerform(){
long start = System.currentTimeMillis();
ByteBuffer bb = ByteBuffer.allocateDirect(500);//分配 DirectBuffer
for(int i=0;i<100000;i++){
for(int j=0;j<99;j++){
bb.putInt(j);
}
bb.flip();
for(int j=0;j<99;j++){
bb.getInt(j);
}
}
bb.clear();
long end = System.currentTimeMillis();
System.out.println(end-start);
start = System.currentTimeMillis();
for(int i=0;i<20000;i++){
ByteBuffer b = ByteBuffer.allocateDirect(10000);//创建 DirectBuffer
}
end = System.currentTimeMillis();
System.out.println(end-start);
}
public void ByteBufferPerform(){
long start = System.currentTimeMillis();
ByteBuffer bb = ByteBuffer.allocate(500);//分配 DirectBuffer
for(int i=0;i<100000;i++){
for(int j=0;j<99;j++){
bb.putInt(j);
}
bb.flip();
for(int j=0;j<99;j++){
bb.getInt(j);
}
}
bb.clear();
long end = System.currentTimeMillis();
System.out.println(end-start);
start = System.currentTimeMillis();
for(int i=0;i<20000;i++){
ByteBuffer b = ByteBuffer.allocate(10000);//创建 ByteBuffer
}
end = System.currentTimeMillis();
System.out.println(end-start);
}
public static void main(String[] args){
DirectBuffervsByteBuffer db = new DirectBuffervsByteBuffer();
db.ByteBufferPerform();
db.DirectBufferPerform();
}
}
  运行输出如清单 6 所示。
  清单 6. 运行输出
  920
  110
  531
  390
  由清单 6 可知,频繁创建和销毁 DirectBuffer 的代价远远大于在堆上分配内存空间。使用参数-XX:MaxDirectMemorySize=200M –Xmx200M 在 VM Arguments 里面配置大 DirectBuffer 和大堆空间,代码中分别请求了 200M 的空间,如果设置的堆空间过小,例如设置 1M,会抛出错误如清单 7 所示。
  清单 7. 运行错误
  Error occurred during initialization of VM
  Too small initial heap for new size specified
  DirectBuffer 的信息不会打印在 GC 里面,因为 GC 只记录了堆空间的内存回收。可以看到,由于 ByteBuffer 在堆上分配空间,因此其 GC 数组相对非常频繁,在需要频繁创建 Buffer 的场合,由于创建和销毁 DirectBuffer 的代码比较高昂,不宜使用 DirectBuffer。但是如果能将 DirectBuffer 进行复用,可以大幅改善系统性能。清单 8 是一段对 DirectBuffer 进行监控代码。
  清单 8. 对 DirectBuffer 监控代码
import java.lang.reflect.Field;
public class monDirectBuffer {
public static void main(String[] args){
try {
Class c = Class.forName("java.nio.Bits");//通过反射取得私有数据
Field maxMemory = c.getDeclaredField("maxMemory");
maxMemory.setAccessible(true);
Field reservedMemory = c.getDeclaredField("reservedMemory");
reservedMemory.setAccessible(true);
synchronized(c){
Long maxMemoryValue = (Long)maxMemory.get(null);
Long reservedMemoryValue = (Long)reservedMemory.get(null);
System.out.println("maxMemoryValue="+maxMemoryValue);
System.out.println("reservedMemoryValue="+reservedMemoryValue);
}
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchFieldException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}