1. 在JAVA传统的IO系统中,读取磁盘文件数据的过程以下:java
以FileInputStream类为例,该类有一个read(byte b[])方法,byte b[]是咱们要存储读取算法
到用户空间的缓冲区。参看read(byte b[])方法的源码,可知,它会在内部再调用readBytes(b, 0, b.length)方法,并且readBytes(b, 0, b.length)方法是一个native方法(即本地方法),最终经过这个本地方法来发起一次系统调用,即调用系统内核的read()方法,内核从磁盘读取数据到内核缓冲区,这个过程由磁盘控制器经过DMA操做将数据从磁盘读取取内核缓冲区,此过程不依赖于CPU。而后用户进程再将数据从内核缓冲区拷贝到用户空间缓冲区。用户进程再从用户空间缓冲区中读取数据。由于用户进程是不能够直接访问硬件的。因此须要经过内核来充当中间人的做用来实现文件的读取。整个过程以下图所示:windows
2. 自从JAVA 1.4之后,JAVA在NIO在引入了文件通道的概念,在API中有提供了一个缓存
FileChannel类。该类与传统的IO流进行关联。能够由FileInputStream或FileOutputStream获取该文件通道,咱们能够经过通道对文件进行读写操做。app
3.JAVA NIO中还引入了文件内存映射的概念:现代操做系统大都支持虚拟内存映射,这样,咱们能够把内核空间地址与用户空间的虚拟地址映射到同一个物理地址,这样,DMA 硬件(只能访问物理内存地址)就能够填充对内核与用户空间进程同时可见的缓冲区了。以下图所示:测试
这样作的好处是,咱们在读取磁盘文件时,不再用经过内核缓冲区到用户进程缓冲区的来回拷贝操做了。操做系统会经过一些页面调度算法来将磁盘文件载入对分页区进行高速缓存的物理内存。咱们就能够经过映射后物理内存来读取磁盘文件了。spa
3. 下面咱们经过三种不一样方式文件拷贝的案例来验证文件通道及文件内存映射在IO操作系统
系统中的做用。测试环境为windows 32位系统和JDK1.6。代码中使用的测试文件movie.avi为一个123MB的视频文件。代码以下:.net
package cn.com.hbust.nio.file; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; public class FileCopyTest { public static void main(String[] args) throws Exception { String sourcePath = "F:\\mywork\\javademo\\dir1\\movie.avi"; String destPath1 = "F:\\mywork\\javademo\\dir2\\movie1.avi"; String destPath2 = "F:\\mywork\\javademo\\dir2\\movie2.avi"; String destPath3 = "F:\\mywork\\javademo\\dir2\\movie3.avi"; long t1 = System.currentTimeMillis(); traditionalCopy(sourcePath,destPath1); long t2 = System.currentTimeMillis(); System.out.println("传统IO方法实现文件拷贝耗时:" + (t2-t1) + "ms"); nioCopy(sourcePath,destPath2); long t3 = System.currentTimeMillis(); System.out.println("利用NIO文件通道方法实现文件拷贝耗时:" + (t3-t2) + "ms"); nioCopy2(sourcePath,destPath3); long t4 = System.currentTimeMillis(); System.out.println("利用NIO文件内存映射及文件通道实现文件拷贝耗时:" + (t4-t3) + "ms"); } private static void nioCopy2(String sourcePath, String destPath) throws Exception { File source = new File(sourcePath); File dest = new File(destPath); if(!dest.exists()) { dest.createNewFile(); } FileInputStream fis = new FileInputStream(source); FileOutputStream fos = new FileOutputStream(dest); FileChannel sourceCh = fis.getChannel(); FileChannel destCh = fos.getChannel(); MappedByteBuffer mbb = sourceCh.map(FileChannel.MapMode.READ_ONLY, 0, sourceCh.size()); destCh.write(mbb); sourceCh.close(); destCh.close(); } private static void traditionalCopy(String sourcePath, String destPath) throws Exception{ File source = new File(sourcePath); File dest = new File(destPath); if(!dest.exists()) { dest.createNewFile(); } FileInputStream fis = new FileInputStream(source); FileOutputStream fos = new FileOutputStream(dest); byte [] buf = newbyte [512]; int len = 0; while((len = fis.read(buf)) != -1) { fos.write(buf, 0, len); } fis.close(); fos.close(); } private static void nioCopy(String sourcePath, String destPath) throws Exception{ File source = new File(sourcePath); File dest = new File(destPath); if(!dest.exists()) { dest.createNewFile(); } FileInputStream fis = new FileInputStream(source); FileOutputStream fos = new FileOutputStream(dest); FileChannel sourceCh = fis.getChannel(); FileChannel destCh = fos.getChannel(); destCh.transferFrom(sourceCh, 0, sourceCh.size()); sourceCh.close(); destCh.close(); } }
每执行完一次拷贝以后,将F:\mywork\javademo\dir2\目录中的内容删除掉,重复执行8次。观察测试结果以下:时间单位为ms(毫秒)code
由上表可知,传统IO方式平均拷贝完成时间约为1968ms,NIO文件通道方式平均拷贝完成时间约为1672ms,文件内存映射及文件通道方式平均拷贝完成时间约为1418ms。