您好,我是湘王,這是我的博客園,歡迎您來,歡迎您再來~ 為瞭解決標準Java I/O令人難以忍受的效率問題,從JDK1.4開始,NIO出現了(Non-blocking I/O,官方稱之為New I/O)。NIO不但新增加了許多全新的類,而且還對原來的很多類進行了改寫。之所以是NIO,是因為使用它的場 ...
您好,我是湘王,這是我的博客園,歡迎您來,歡迎您再來~
為瞭解決標準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中比較重要的內容,說多了容易搞暈。慢慢來~
感謝您抽空品鑒!技術、產品、運營和管理問題,可隨時留言私信,歡迎騷擾~