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。