您好,我是湘王,這是我的博客園,歡迎您來,歡迎您再來~
為了解決標準Java I/O令人難以忍受的效率問題,從JDK1.4開始,NIO出現了(Non-blocking I/O,官方稱之為New I/O),NIO不但新增加了許多全新的類,而且還對原來的很多類進行了改寫,之所以是NIO,是因為使用它的場景眾多,譬如開發中必不可少的Tomcat,以及大名鼎鼎的Netty,而Netty更是把NIO發揮到了極致,成為了RPC技術事實上的標準,所以它在JDK1.7中又升級為了AIO(NIO2),
NIO主要有三大核心部分:
- Channel(通道)
- Buffer(緩沖區)
- Selector(選擇器/多路復用器)
傳統I/O基于位元組流或字符流進行操作,而NIO基于新的Channel和Buffer進行操作,這是它們的比較:

至于原理,不用記,可以這么來理解(我始終秉持的態度是:如果你在大廠是自研類RPC系統或類MQ中間件的,那這個一定要精通;否則理解就好,不必死磕):
可以看到,I/O就像個直腸子,直來直去,對資料流完全是來者不拒,來多少接多少,也不管能不能處理得了,這樣極容易造成執行緒阻塞,也就是電腦卡頓,
而NIO就有點彎彎繞了,它告訴執行緒:“如果我忙不過來就別等我,你先忙你的”,所以,按照這個約定,如果執行緒發現它不搭理自己的時候就會去忙別的,不會造成資訊堵車,
Channel介面最重要的實作可以分為兩大類:用于本地檔案和用于網路的Channel,
- FileChannel:用于本地檔案資料的讀寫
- DatagramChannel:用于網路UDP資料的讀寫
- SocketChannel:客戶端用于實作網路TCP資料的讀寫
- ServerSocketChannel:服務端用于監聽網路TCP的連接請求,每個請求會創建會一個SocketChannel(即客戶端連接)
這是和Channel相關的繼承結構圖:

I/O本就枯燥,如果只是空洞說技術原理就更毫無價值,還是上代碼,把NIO和IO比較一下,
創建一個普通的Java專案:


然后隨便在網上或者自己電腦上找一個大檔案,比如小電影之類的,寫這樣的代碼:
// 把file1中的內容寫到file2中去,看看耗時 // I/O讀寫 long start = System.currentTimeMillis(); try { FileInputStream fis = new FileInputStream("你電腦上已經存在的檔案路徑,例如C:\\file1"); FileOutputStream fos = new FileOutputStream("你電腦上還不存在的檔案路徑,例如C:\\file2"); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(fis), 1024); String line = null; while ((line = bufferedReader.readLine()) != null) { fos.write(line.getBytes()); } fis.close(); fos.close(); } catch (IOException e) { e.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println(end - start);
然后再稍稍改進一下,看看byte[]相對于BufferedReader的不同:
// 把file1中的內容寫到file3中去,看看耗時 // I/O讀寫(改進) start = System.currentTimeMillis(); try { FileInputStream fis = new FileInputStream("你電腦上已經存在的檔案路徑,例如C:\\file1"); FileOutputStream fos = new FileOutputStream("你電腦上還不存在的檔案路徑,例如C:\\file3"); byte[] b = new byte[1024]; int len = 0; while ((len = fis.read(b)) != -1) { fos.write(b, 0, len); } fis.close(); fos.close(); } catch (IOException e) { e.printStackTrace(); } end = System.currentTimeMillis(); System.out.println(end - start);
最后再用NIO試試看:
// 把file1中的內容寫到file4中去,看看耗時 // NIO讀寫 start = System.currentTimeMillis(); try { FileChannel fis = new FileInputStream("你電腦上已經存在的檔案路徑,例如C:\\file1").getChannel(); FileChannel fos = new FileOutputStream("你電腦上還不存在的檔案路徑,例如C:\\file4").getChannel(); ByteBuffer bytedata = ByteBuffer.allocate(1024); while (fis.read(bytedata) != -1) { // 讀寫交叉進行 bytedata.flip(); fos.write(bytedata); bytedata.clear(); } fis.close(); fos.close(); } catch (IOException e) { e.printStackTrace(); } end = System.currentTimeMillis(); System.out.println(end - start);
在main()方法中分別執行這三個方法,看看耗時上有啥不同,盡量找很大的檔案,比如幾個G的那種,因為現在計算機的配置都比較高,檔案太小,一會就讀完了,根本看不出來差別,
另外,另外,在NIO中如果一個channel是FileChannel型別的,那么可以直接把FileChannel的資料傳輸到另一個Channel,就像這樣:

SocketChannel、ServerSocketChannel和DatagramChannel的使用也比較簡單,就不堆代碼了,
Channel還提供了一種被稱為Scatter/Gather(分散/聚集)的新功能(也稱為Vectored I/O,矢量I/O),它在多個Buffer上實作一個簡單的I/O操作,說人話就是:Scatter是把單個Channel的資料發給多個Buffer(分散),而Gather則是把多個Buffer的資料發給單個Channel(聚集),就像這樣:

同樣可以用代碼來演示一下:
// Scattering reads分散程序 ByteBuffer buffer1 = ByteBuffer.allocate(1024); ByteBuffer buffer2 = ByteBuffer.allocate(1024); ByteBuffer[] bufferArray1 = { buffer1, buffer2 }; FileChannel channel1 = new FileInputStream("/testfile1").getChannel(); channel1.read(bufferArray1); // Gathering writes聚集程序 ByteBuffer buffer3 = ByteBuffer.allocate(1024); ByteBuffer buffer4 = ByteBuffer.allocate(1024); ByteBuffer[] bufferArray2 = { buffer1, buffer2 }; FileChannel channel2 = new FileInputStream("/testfile1").getChannel(); channel2.write(bufferArray2);
好了,NIO也屬于Java中比較重要的內容,說多了容易搞暈,慢慢來~
感謝您抽空品鑒!技術、產品、運營和管理問題,可隨時留言私信,歡迎騷擾~
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/517555.html
標籤:其他
