1、传统的IO
在JAVA传统的IO系统中,读取磁盘文件数据的过程如下:
以 FileInputStream
类为例,该类有一个 read(byte b[])
方法,byte b[]
是我们要存储读取到用户空间的缓冲区。参看 read(byte b[])
方法的源码,可知,它会在内部再调用 readBytes(b, 0, b.length)
方法,而且 readBytes(b, 0, b.length)
方法是一个native方法(即本地方法),最终通过这个本地方法来发起一次系统调用,即调用系统内核的read()方法,内核从磁盘读取数据到内核缓冲区,这个过程由磁盘控制器通过DMA操作将数据从磁盘读取取内核缓冲区,此过程不依赖于CPU。然后用户进程再将数据从内核缓冲区拷贝到用户空间缓冲区。用户进程再从用户空间缓冲区中读取数据。因为用户进程是不可以直接访问硬件的。所以需要通过内核来充当中间人的作用来实现文件的读取。整个过程如下图所示:
2、NIO – FileChannel类
自从JAVA 1.4以后,JAVA在NIO在引入了文件通道的概念,在API中有提供了一个FileChannel类。该类与传统的IO流进行关联。可以由FileInputStream或FileOutputStream获取该文件通道,我们可以通过通道对文件进行读写操作。
3、NIO 直接内存
JAVA NIO中还引入了文件内存映射的概念:现代操作系统大都支持虚拟内存映射,这样,我们可以把内核空间地址与用户空间的虚拟地址映射到同一个物理地址,这样,DMA 硬件(只能访问物理内存地址)就可以填充对内核与用户空间进程同时可见的缓冲区了。如下图所示:
这样做的好处是,我们在读取磁盘文件时,再也不用通过内核缓冲区到用户进程缓冲区的来回拷贝操作了。操作系统会通过一些页面调度算法来将磁盘文件载入对分页区进行高速缓存的物理内存。我们就可以通过映射后物理内存来读取磁盘文件了。
4、三种不同方式文件复制比较
示例:
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 = "D:/movie.avi";
String destPath1 = "D:/dir2/movie1.avi";
String destPath2 = "D:/dir2/movie2.avi";
String destPath3 = "D:/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();
fos.close();
fis.close();
}
//NIO文件通道文件复制
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();
fos.close();
fis.close();
}
//传统IO方法实现文件
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 = new byte[1024];
int len = 0;
while ((len = fis.read(buf)) != -1) {
fos.write(buf, 0, len);
}
fis.close();
fos.close();
}
}