1 NIO的一些基礎預備知識
Java中IO流類的體系中BIO與NIO:https://blog.csdn.net/ZGL_cyy/article/details/104326458
Java IO體系與NIO和BIO體系面試題 :https://blog.csdn.net/ZGL_cyy/article/details/122836368
為什么使用NIO:因為傳統IO文件傳輸速率低,所以選擇了NIO進行文件的下載操作。NIO還有一個好處就是其中零拷貝可以實現減少內存中數據的重復,減少CPU操作的效果。所以相對于傳統IO,NIO有著效率高點的優勢。
2 NIO為何較傳統的io速度較快
就拿單個io過程來看,首先時間主要花在了用戶態和內核態的轉換上,其次,考慮將多個io的“合并”為一個io,這不就節省時間了嗎
相應的NIO主要做了兩方面的提升
1、避免了用戶態和內核態的交換,直接操作內存,用戶態和內核態的轉換是很費時的,傳統的io寫入磁盤時,用戶態的接口不能直接操作內存,而是通過操作系統調用內核態接口來進行io。
2、利用buffer減少io的次數,buffer化零為整”的寫入方式因為大大減小了尋址/寫入次數,所以就降低了硬盤的負荷。
3、IO 是基于流來讀取的,而NIO則是基于塊讀取,面向流 的 I/O 系統一次一個字節地處理數據。一個輸入流產生一個字節的數據,一個輸出流消費一個字節的數據。為流式數據創建過濾器非常容易。鏈接幾個過濾器,以便每個過濾器只負責單個復雜處理機制的一部分,這樣也是相對簡單的。不利的一面是,面向流的 I/O 通常相當慢。
一個 面向塊 的 I/O 系統以塊的形式處理數據。每一個操作都在一步中產生或者消費一個數據塊。按塊處理數據比按(流式的)字節處理數據要快得多。但是面向塊的 I/O 缺少一些面向流的 I/O 所具有的優雅性和簡單性。
4、非阻塞IO 和 異步IO的支持, 減少線程占有的棧空間,以及上下文切換
5、IO 多路復用的支持
6、Buffer 支持,所有讀寫操作都是基于 緩沖 來實現
7、NIO 支持 Direct Memory, 可以減少一次數據拷貝
8、Netty 零拷貝的支持
3 NIO實戰上傳下載
3.1 url下載文件
java NIO包提供了無緩沖情況下在兩個通道之間直接傳輸字節的可能。
為了讀來自URL的文件,需從URL流創建ReadableByteChannel :
ReadableByteChannel readableByteChannel = Channels.newChannel(url.openStream());
從ReadableByteChannel 讀取字節將被傳輸至FileChannel:
FileOutputStream fileOutputStream = new FileOutputStream(FILE_NAME); FileChannel fileChannel = fileOutputStream.getChannel();
然后使用transferFrom方法,從ReadableByteChannel 類下載來自URL的字節傳輸到FileChannel:
fileOutputStream.getChannel().transferFrom(readableByteChannel, 0, Long.MAX_VALUE);
transferTo() 和 transferFrom() 方法比簡單使用緩存從流中讀更有效。依據不同的底層操作系統,數據可以直接從文件系統緩存傳輸到我們的文件,而不必將任何字節復制到應用程序內存中。
在Linux和UNIX系統上,這些方法使用零拷貝技術,減少了內核模式和用戶模式之間的上下文切換次數。
工具類:
/**NIO文件下載工具類 * @author olalu */ public class NioDownloadUtils { /** * @description: * @param file: 要下在文件 * @return: void */ public static void downloadDoc(File file,HttpServletResponse response) throws IOException { OutputStream outputStream = response.getOutputStream(); String contentType = Files.probeContentType(Paths.get(file.getAbsolutePath())); //設置響應頭 response.setHeader("Content-Type", contentType); response.setHeader("Content-Disposition", "attachment;filename="+ new String(file.getName().getBytes("utf-8"),"ISO8859-1")); response.setContentLength((int) file.length()); //獲取文件輸入流 FileInputStream fileInputStream = new FileInputStream(file); //獲取輸出流通道 WritableByteChannel writableByteChannel = Channels.newChannel(outputStream); FileChannel fileChannel = fileInputStream.getChannel(); //采用零拷貝的方式實現文件的下載 fileChannel.transferTo(0,fileChannel.size(),writableByteChannel); //關閉對應的資源 fileChannel.close(); outputStream.flush(); writableByteChannel.close(); } public static void downloadDoc(String path,HttpServletResponse response) throws IOException { File file = new File(path); if (!file.exists()){ throw new RuntimeException("文件不存在"); } downloadDoc(file,response); } }
3.2 通過NIO上傳文件
/** * 文件上傳方法 */ public static Result uploading(MultipartFile file) { //獲取文件名 String realName = file.getOriginalFilename(); String newName = null; if(realName != null && realName != ""){ //獲取文件后綴 String suffixName = realName.substring(realName.lastIndexOf(".")); //生成新名字 newName = UUID.randomUUID().toString().replaceAll("-", "")+suffixName; }else { return Result.fail("文件名不可為空"); } //創建流 FileInputStream fis = null; FileOutputStream fos = null; //創建通道 FileChannel inChannel = null; FileChannel outChannel = null; try { fis = (FileInputStream)file.getInputStream(); //開始上傳 fos = new FileOutputStream(UPLOAD_URL+"\\"+newName); //通道間傳輸 inChannel = fis.getChannel(); outChannel = fos.getChannel(); //上傳 inChannel.transferTo(0,inChannel.size(),outChannel); }catch (IOException e){ return Result.fail("文件上傳路徑錯誤"); }finally { //關閉資源 try { if (fis != null) { fis.close(); } if (fos != null) { fos.close(); } if (inChannel != null) { inChannel.close(); } if (outChannel != null) { outChannel.close(); } } catch (IOException e) { e.printStackTrace(); } } return Result.ok(newName); }